Графический пользовательский интерфейс (GUI) это сложный слой системы, состоящий из множества компонентов, называемых виджетами, которые отвечают за представление бизнес-модели приложения пользователю и обрабатывают логику взаимодействия с ним через это представление. В этом цикле статей мы выделим основные элементы виджета и варианты их компоновки.

Популярные JS-фреймворки используют концепцию виджетов компоную реализацию различных элементов. Как правило применяется MVC структура, позволяющая легко реализовывать и расширять библиотеку виджетов. При этом виджет состоит из следующих элементов:

  • Модель - описывает данные бизнес-модели приложения в пригодном для представления виде
  • Представление - описывает логику рендеринга (преобразования) модели в визуальный компонент интерфейса
  • Контроллер - описывает логику взаимодействия пользователя с виджетом и реакции последнего на них

Все три перечисленных элемента мы коротко рассмотрим далее, а более подробно в следующих статьях цикла.

Модель

Модель виджета чаще всего представляется в виде набора данных (массива, словаря или объекта), которыми оперирует виджет. Часто модель выделяется в отдельный класс с собственными методами, инкапсулируя не только данные, но и логику получения их от сервера.

Рассмотрим простой пример приложения, работающего с данными клиентов:

<?php
class Client{
  private $name;
}

class Product{
  private $name;
}

class OrderRow{
  private $product;
  private $count;
}

class Order{
  private $client;
  private $rows;
}

Это бизнес-модель приложения, описывающая клиентов и продукты, доступные системы, а так же заказы конкретных клиентов. Класс Order в данной модели выступает в качестве списка объектов OrderRow, каждый из которых описывает количество заказанных продуктов.

При передаче экземпляров этой модели в слой представления, ее необходимо представить в доступном для виджета виде (преобразовать PHP в JS), именно здесь и применяется модель представления, которая может быть описана с помощью следующих классов:

function Client(name){
  this.name = name;
}

function Product(name){
  this.name = name;
}

function OrderRow(product, count){
  this.product = product;
  this.count = count;
}

function Order(client){
  this.client = client;
  this.rows = [];
}

Order.prototype.addRow = function(row){
  this.rows.push(row);
};

Данные классы могут быть расширены методами, облегчающими работу с ними для слоя представления.

Представление

Элементом, преобразующим модель виджета в конкретные графические элементы интерфейса (на пример в HTML), обычно выступает специальный класс или функция, содержащая логику этого преобразования. Для описанного выше примера может использоваться два рендера:

function OrderRowRender(row){
  this.row = row;
}

OrderRowRender.prototype.render = function(){
  return '<li>' + this.row.product.name + ' | ' + this.row.product.count + '</li>';
};

function OrderRender(order){
  this.order = order;
}

Order.prototype.render = function(){
  var result = '<ul>';
  for(var i in this.order.rows){
    var rowRender = new OrderRowRender(this.order.rows[i]);
    result += rowRender.render();
  }
  result += '</ul>';
};

Здесь рендер OrderRowRender служит для представления одной строки заказа в виде элемента li, а OrderRender отвечает за представления всего заказа и использует OrderRowRender в качестве вспомогательного в цикле для представления каждой строки заказа. В результате может быть получен следующий код:

<ul>
  <li>Гвозди | 10</li>
  <li>Молоток | 2</li>
  <li>Заклепки | 10</li>
</ul>

Контроллер

Контроллер виджета отвечает за обработку событий взаимодействия пользователя с представлением и изменение модели на основании этих взаимодействий. В нашем примере контроллер может быть встроен в класс виджета:

function OrderWidget(order, el){
  this.order = order;
  this.el = el;
}

OrderWidget.prototype.bind = function(){
  $(this.el).on('click', 'li', this.onSelectRow);
};

// Метод контроллера, обрабатывающий событие выбора строки продукта пользователем.
OrderWidget.prototype.onSelectRow = function(e){
  $(e.target).toggleClass('selected');
};

OrderWidget.prototype.render = function(){
  var render = new OrderRender(this.order);
  el.html(render.render());
  this.bind();
};

В данном примере метод bind служит для добавления обработчиков событий сформированного представления, а метод onSelectRow обрабатывает эти события.

Послесловие

В данной статье мы галопом пробежались по понятию виджета и его элементам. Возможно статья оставила вам больше вопросов, нежели ответов, но не спешите, в следующих частях цикла мы подробно рассмотрим каждый элемент и приведем примеры их реализаций, потому все встанет на свои места.