Хочу поделиться с вами одной техникой организации кода при массивной работе с 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
.
Пожалуй, на этом все.
Лушее понимание того, что и как работает, вы сможете получить, почитав тесты и заглянув в исходник на гитхабе. Наверняка также будет полезной и общая документация.
Буду рад, если вы найдете данную технику и описанную выше конвенцию применимой и в ваших проектах. Особенно буду рад вашим пожеланиям и идеям, с радостью отвечу на вопросы.