Объекты страницы: описание одной техники

Хочу поделиться с вами одной техникой организации кода при массивной работе с DOM-элементами. Несколько лет назад, когда еще вовсе не было бэкбона и MVVC, мы писали старый добрый джаваскрипт без фреймворков: создавали объекты и заставляли их плясать на странице в общем танце. Такая практика, без сомнения, до сих пор оправдывает себя, и техника, о которой пойдет речь, применима именно к ней.

Мой рассказ — о маленькой библиотечке PageObject.js (текущая версия v0.14, 2.6K) и о том, как с ее помощью можно упростить себе жизнь.

Это не плагин jQuery, хотя, уверен, многие захотят ее назвать именно так. Это всего лишь функция, которая использует селекторы и кое-какие утилиты из jQuery, чтобы создать удобство в чтении и написании кода. По сути, это простая jQuery утилитка.

Суть техники

В создании объектов, которые манипулируют DOM-элементами, и их подключении на страницу ничего нового не придумаешь: создаем конструктор, сперва рендерим в нем определенный шаблон, затем разбираем его результат на части, «вешаем» на эти части обработчики событий, программируем остальную логику и встраиваем все это добро в наше приложение.

function Calculator() {
  if (this.constructor.name !== 'Calculator') { throw “No way, buddy!”; }
  // 1. отрендерить шаблон
  // 2. разобрать на части
  // 3. повесить обработчики
  // 4. остальная логика
}

PageObject.js помогает с первыми двумя шагами.

function Calculator() {
  if (this.constructor.name !== 'Calculator') { throw “No way, buddy!”; }
  var calc = this;
  $.turnToPageObject(calc, {
    template: $('#tmplCalculator').html(),
    containerClass: 'calc',
    context: {
      caption: "Calculator"
    },
    selectors: {
      buttons: [ ':button', Calculator.getButtonName ],
      led: 'p'
    }
  });
  // 3. повесить обработчики
  // 4. другая логика
}
var calc = new Calculator;
$('body').append(calc.DOM.container);

После того, как отработает функция $.turnToPageObject (“превратить в объект страницы”), у объекта calc появится свойство calc.DOM — неймспейс, заполненый DOM-элементами, которые будут соответствовать указанным селекторам, и еще появится calc.DOM.container — именно та легко встраиваемая в приложение часть объекта — контейнер всего-всего.
Вот, пожалуйста, полный работающий пример с калькулятором. Мне же осталось рассказать подробнее о всех возможностях утилитки.

$.turnToPageObject

Первый аргумент должен быть объектом, у которого в последствии появится заполненый составными HTML-елементами неймспейс DOM. Второй аргумент — опции.

Если НЕ указать опцию container, то контейнер будет создан (без него никак), и это будет такой же элемент, как иcontainerElement (по умолчанию — DIV).

Если указать containerClass, то контейнеру будет присвоен класс.

Если указать template, то будет отрендерен шаблон и его результат будет помещен внутрь контейнера.

Если НЕ указать context, то шаблон будет отрендерен с пустым контекстом {}.

template может быть либо строкой, либо функцией (интеграция с Jammit JST).

Когда template — функция, он принимает только контекст и должен возвратить строку.

Когда template — строка, шаблон рендерится при помощи templateEngine, которая автоматически настроится по умолчанию на использование шаблонизатора _.template, если underscore присутствует.

Если в вашем проекте нет underscore, вам нужно сконфигурировать templateEngine.

// вот так меняются умолчания:
$.turnToPageObject.configure({
  templateEngine: window.tmpl, // http://ejohn.org/blog/javascript-micro-templating
  containerElement: 'strong'
});

templateEngine принимает два аргумента — строку шаблона и контекст — и также должен возвратить строку.

Если указана опция hide, то контейнер будет спрятан, что часто бывало удобно.

И главное — селекторы. Если указать опцию selectors (объект), то соответствующие селекторам найденые в содержимом контейнера HTML-элементы будут по аналогичным ключам помещены в неймспейс DOM. Если опция template была указана, то селекторы будут искать в уже отрендеренном шаблоне.

Если по селектору не будет найдено ни одного элемента — будет крик.
Если по селектору будет найдено более одного элемента — также будет исключение.
Если все же нужно, чтобы по селектору были найдены и помещены в массив более одного элемента, нужно к значению селектора дописать [] (пустые квадратные скобки) — такое выделение себя оправдывает.

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

Стоит отметить, что дополнительные неймспейсы в селекторах образуют неймспейсы с теми же именами внутри свойстваDOM.

Пожалуй, на этом все.

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

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