Продолжим изучение Google Closure Library. В данной статье я постараюсь отклонится от «jQuery way» и продемонстрировать как удобны в использовании и просты в создании компоненты фреймворка.
Давненько я технических статей не писал, отвыкли небось, но отвлекать не буду, читайте на здоровье ;)
Это все же, в большей степени, перевод статьи из официальной документации, но он слегка сокращен и адаптирован
Много вкусного несет в себе пакет goog.ui, я совсем чуть-чуть рассказывал о нем в предыдущей статье, но напомню — этот пакет содержит в себе готовые компоненты для реализации интерфейсов: кнопки, меню, календари, и т.д.
Основой всех компонентов является класс goog.ui.Component, и для создания своих компонентов без него не обойтись. Он требует от нас определенную структуры класса состоящую из шести методов, которые описывают жизненный цикл компонента (в хронологическом порядке):
Метод | Описание метода (жизненного цикла) |
---|---|
constructor | создание компонента |
createDom() | создание DOM структуры компонента |
decorateInternal() (optional) | |
enterDocument() | после создания DOM структуры будет запущен данный метод (обработчики событий вешаем тут) |
exitDocument() | перед удалением DOM структуры (чистим обработчики событий) |
dispose() | удаление компонента |
Отдельно особняком стоит метод canDecorate(existingElement)
— он расскажет нам можно ли создать компонент основываясь на уже существующих элементах (existingElement). По умолчанию, суперкласс всегда возвращает true.
И так, перейдем к созданию своего компонента…
Инициализация
Начинается все с шага ноль — подключения goog.ui.Component
, и объявления имени нашего компонента.
goog.provide('tutorial.component'); goog.require('goog.ui.Component');
Теперь шаг первый — это создание конструктора компонента:
tutorial.component = function(opt_label, opt_domHelper) { // вызов конструктора предка goog.ui.Component.call(this, opt_domHelper); /** * Заголовок виджета * @type String * @private */ this.initialLabel_ = opt_label || 'Link'; /** * Наш обработчик событий * @type goog.events.EventHandler * @private */ this.eh_ = new goog.events.EventHandler(this); /** * Обработчик клавиатуры, у нас в компоненте не используется, но правила именования следует соблюдать * * @type {goog.events.KeyHandler?} * @private */ this.kh_ = null; }; // наследуем goog.ui.Component goog.inherits(tutorial.component, goog.ui.Component);
Создание DOM структуры
После создания экземпляра компонента необходимо создать соответствующую DOM структуру, для этого потребуется реализовать метод createDom()
и decorateInternal()
(опционально):
createDom() | создание DOM структуры компонента, и сохранение в this.element_ |
decorateInternal() (optional) | создаем компонент используя уже существующую DOM структуру, и так же сохраняем в this.element_ |
Ну и пример кода:
/** * создаём необходимую нам DOM структуру * в нашем примере необходим лишь один div */ tutorial.component.prototype.createDom = function() { // вызываем decorateInternal который внесет изменения в новосозданный элемент this.decorateInternal(this.dom_.createElement('div')); }; /** * изменяем существующую структуру под наши нужды * * @param {HTMLElement} element DIV который мы изменяем */ tutorial.component.prototype.decorateInternal = function(element) { tutorial.component.superClass_.decorateInternal.call(this, element); var elem = this.getElement(); goog.dom.classes.add(elem, 'tutorial-component'); goog.dom.setTextContent(elem, this.initialLabel_); };
Еще один шаг — после того как построили DOM
Зачастую, инициализация компонента требует уже заранее подготовленную структуру DOM. К примеру, вы создаете popup меню и вам необходимо повесить обработчик на div с id=”menu”, но этот элемент мы создаем лишь на этапе createDom
, таким образом у нас получается неувязочка, но не стоит беспокоится, создатели фреймворка приберегли на этот случай метод enterDocument()
:
enterDocument() | в данном методе реализуют функционал, который требует наличие структуры компонента в DOM |
Реализуя метод enterDocument()
необходимо всегда вызывать одноименный метод суперкласса, который вызовет все дочерние компоненты. Собственно, в методе enterDocument()
лучше всего описывать методы, которые вешают обработчики событий на элементы DOM, и это тем более обязательно для элементов, которые не являются частью компонента (а лишь добавляют нам пачку зависимостей =).
Но вернемся к нашему примеру:
tutorial.component.prototype.enterDocument = function() { // вызываем метод супер класса tutorial.component.superClass_.enterDocument.call(this); // простой обработчик на click this.eh_.listen( this.getElement(), goog.events.EventType.CLICK, this.onDivClicked_ ); };
Резюмируя — не хочешь проблем с обработчиками — вешай их в enterDocument().
Методы render() и decorate()
Если мы все делали правильно, то можем попробовать инициализировать свой компонент используя метод render()
:
var comp1 = new tutorial.component("Button"); comp1.render(goog.dom.$('button1'));
Метод render()
вызывает createDom()
, получившийся element_
помещает в DOM и затем вызывает enterDocument()
. Если метод render()
дергать с параметром Element (в примере выше это “button1”), то element_
будет помещен внутрь него, иначе станет потомком body
.
В примере есть и второй способ вызова компонента:
var comp2 = new tutorial.component("Button Two"); comp2.decorate(goog.dom.$('button2'));
Метод decorate()
получает уже готовый element_
в качестве параметра, который и скармливает методу decorateInternal()
, и затем точно так же вызывается enterDocument()
.
Внутри компонента вы не должны вызывать методы
render()
илиdecorate()
. Хотя, если вы не используете методыrender()
илиdecorate()
, следует вызыватьenterDocument()
после того как будут добавлены все необходимые элементы компонента в DOM
«Я тебя породил, я тебя и уничтожу»
Создавая компонент не забИвайте описывать методы для подчистки DOM’а после его удаления, для этого следует подчистить все, что мы наворотили в методах createDom()
и enterDocument()
. В этом нам помогут следующие методы:
exitDocument() | вызывается перед удалением DOM структуры — тут чистим обработчики событий |
dispose() | удаление компонента и DOM элементов |
Ну и пример метода exitDocument()
:
tutorial.component.prototype.exitDocument = function() { tutorial.component.superClass_.exitDocument.call(this); this.eh_.unlisten( this.getElement(), goog.events.EventType.CLICK, this.onDivClicked_ ); this.eh_.unlisten( this.getElement(), [goog.events.EventType.MOUSEOVER, goog.events.EventType.MOUSEOUT], this.onDivHover_ ); };
Обратите внимание на вызов метода
exitDocument()
нашего суперкласса — данная махинация необходима для вызова методовexitDocument()
у всех дочерних компонентов (если таковые имеются). Этот вызов обязателен!
Теперь dispose()
:
tutorial.component.prototype.dispose = function() { if (!this.isDisposed()) { tutorial.component.superClass_.dispose.call(this); this.eh_.dispose(); if (this.kh_) { this.kh_.dispose(); } } };
Опять же — дергаем супер класс, затем подчищаем обработчики событий.
В заключение
Как видите — в создании компонента нет ничего сложного, стоит лишь следовать простым правилам, которые диктует на фреймворк, и вы не запутаетесь и все будет работать.
И да, пощупать пример можно на страничке closure-tutorials/component.html.
Читаем еще
- Introduces goog.ui.Component — первоисточник статьи
- Google Closure для начинающих
Offtopic
Я тут еще в твиттер на досуге пишу, так что следите и не пропустите ;)
Доброго дня
Не подскажете почему в вашем примере можно убрать
из кода файла js и просто вставить его в html между тегом script? (или почему этот код в 2-х местах и в js и в html)
Причем если оставить этот код в начала js файла, но убрать из тега script, то ничего не работает? (говорить что поле Component не определено).
Спасибо. Теперь я разобрался в создании компонентов. Как то программирование интереснейшие стало.
exitDocument не обязательно дергать, хватает dispose который сам дергает exitDocument(). Также можно не морочится с Unlisten если создавать listen через this.getHandler().listen(); т.к. в exitDocument суперкласса вызывается this.getHandler().removeAll() что автоматически удалит все Listen.
По опыту работы, самое важное – это добавлять правильно новый component к parent и в disposeInternal удалять все this переменные.