Статьи

Создание масштабируемого виджета с использованием YUI3: часть 2

В первой части этой серии мы рассмотрели некоторые необходимые конструкции, которые нужно использовать при создании виджета с YUI3. Мы рассмотрели статические свойства, которые нам нужно было установить, конструктор класса и пространство имен, и кратко рассмотрели метод extend() .

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

Прежде чем мы начнем, давайте просто напомним себе о методе сейчас, так как этот метод содержит весь код ниже:

1
2
3
TweetSearch = Y.extend(TweetSearch, Y.Widget, {
 
});

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


Несколько методов выполняются в разных точках жизненного цикла экземпляров виджета. Первым из них является метод initializer (не забудьте добавить этот код в метод extend() показанный выше):

1
2
3
initializer: function () {
    this._retrieveTweets();
},

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

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

Все, что должен сделать наш виджет на данный момент — это получить результаты поиска из Twitter. Мы упаковываем это как отдельную функцию (которую мы рассмотрим более подробно чуть позже), вместо того, чтобы просто извлекать результаты непосредственно в initializer чтобы мы могли повторно использовать функциональность и извлекать результаты поиска в любое время, когда пожелаем. Метод _retrieveTweets() использует соглашение о подчеркивании, чтобы указать, что метод должен рассматриваться как закрытый, и не вызываться напрямую разработчиком. Конечно, его можно вызвать напрямую, но это может привести к странностям.

Следующим методом жизненного цикла, унаследованным от Widget, является renderUI() , который мы можем использовать для выполнения любых необходимых настроек, создания и вставки новых элементов и т. Д., Требуемых нашим виджетом. Добавьте этот код сразу после того, как показано выше:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
renderUI: function () {
    var contentBox = this.get(«contentBox»),
        strings = this.get(«strings»),
        viewer = Node.create(Y.substitute(TweetSearch.VIEWER_TEMPLATE, { viewerclass: TweetSearch.VIEWER_CLASS })),
        loadingNode = Node.create(Y.substitute(TweetSearch.LOADER_TEMPLATE, { loaderclass: TweetSearch.LOADER_CLASS }));
 
    if (this.get(«showTitle»)) {
        this._createTitle();
    }
    this._loadingNode = contentBox.appendChild(loadingNode);
    this._viewerNode = contentBox.appendChild(viewer);
 
    if (this.get(«showUI»)) {
        this._createSearchUI();
    }
 
    contentBox.addClass(«yui3-widget-content»);
},

Когда виджет инициализируется, YUI автоматически создает элемент-обертку для элемента, который был передан в конструктор.

В renderUI() мы сначала сохраняем ссылку на атрибут contentBox виджета. contentBox представляет собой внутренний контейнер виджета и является одним из атрибутов, автоматически унаследованных от Widget, подобно srcNode который мы кратко рассмотрели в части 1. Когда инициализируется виджет, YUI автоматически создает элемент-обертку для элемента, который был передается в конструктор, с внутренним элементом становится contentBox . Оболочка известна как ограничивающий прямоугольник (доступный как атрибут boundingBox ).

Мы также получаем ссылку на атрибут strings который содержит локализуемые строки, используемые элементами, созданными виджетом. Затем мы создаем два новых элемента; средство просмотра, которое будет содержать список твитов, возвращаемых поисковым API Twitter, и элемент загрузки, который будет отображаться во время выполнения запроса.

Мы используем метод create() модуля YUI Node для создания наших новых элементов. Этот элемент может принять строковое представление элемента, который он затем создаст. Однако вместо того, чтобы передавать ему строку, мы используем метод YUI substitute() для замены шаблонов с токенами, которые мы создали в первой части этого урока.

Метод substitute() принимает два аргумента;

  • первая строка для замены.
  • второй — это объект, чьи ключи отображаются непосредственно на токены в строке.

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

1
«<div class={viewerclass}></div>»

Объект, передаваемый в качестве второго аргумента методу substitute() используемому для создания узла средства просмотра, содержит ключ под названием viewerclass , поэтому значение этого ключа будет viewerclass соответствующим токеном в исходной строке. В этом случае мы используем имя сохраненного класса в качестве подстановки, поэтому зрителю будет дано имя класса yui3-tweetsearch-viewer (имена классов были созданы и сохранены в нашем экземпляре виджета в первой части).

Затем мы проверяем, установлено ли для атрибута showTitle нашего виджета значение true , что по умолчанию, но может быть отключено разработчиком-разработчиком. Если для атрибута установлено значение true мы вызываем пользовательский (т.е. не унаследованный) _createTitle() . Причина, по которой мы упаковываем это как отдельную единицу кода, а не просто создаем виджет, заключается в том, что атрибут showTitle может быть установлен в любое время кем-то, реализующим наш виджет, поэтому он не может просто находиться в методе жизненного цикла. Мы подробно рассмотрим наши пользовательские методы после рассмотрения унаследованных методов жизненного цикла.

После того, как мы создаем или не создаем (в зависимости от конфигурации) узел заголовка, мы вставляем новые элементы в DOM, добавляя их как дочерние узлы contentBox . Обратите внимание, что мы также сохраняем новые элементы в экземпляре виджета, чтобы потом можно было легко ссылаться на них.

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

Наконец, мы добавляем имя класса yui3-widget-content в contentBox . Это не является строго необходимым, так как разработчик-исполнитель, возможно, не использует какие-либо таблицы стилей YUI (базовые, шрифты, сброс и т. Д.), Но поскольку имя класса не добавляется для нас автоматически, мы должны включить его в случае, если Разработчик хочет использовать некоторые стили, предоставляемые библиотекой.

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

01
02
03
04
05
06
07
08
09
10
11
bindUI: function () {
    if (this.get(«showUI»)) {
 
        Y.on(«click», Y.bind(this._setTerm, this), this._buttonNode);
        this.after(«termChange», this._afterTermChange);
    }
 
    this.after(«showTitleChange», this._afterShowTitleChange);
    this.after(«showUIChange», this._afterShowUIChange);
    this.after(«tweetsChange», this._afterTweetsChange);
},

Первое, что мы делаем, это проверяем, showUI ли атрибут showUI ; если он был отключен, нам не нужно беспокоиться о добавлении обработчиков событий для него. Если он включен, мы используем метод on() YUI, чтобы добавить обработчик кликов, привязанный к пользовательскому _setTerm() . Мы гарантируем, что экземпляр виджета остается связанным с ключевым словом this в обработчике событий, передавая this (который в данный момент ссылается на экземпляр виджета) в качестве второго аргумента метода bind() .

Мы также используем метод after() который автоматически присоединяется библиотекой к нашему экземпляру виджета, чтобы добавить слушателя, который реагирует на изменение term . Слушатель может быть связан с любым из наших пользовательских атрибутов, просто добавляя суффикс After к любому имени атрибута. term атрибут будет меняться только в том случае, если включен пользовательский интерфейс поиска. Затем мы добавляем прослушиватели для каждого из других атрибутов, которые мы должны отслеживать; showTitle , showUI и tweets , соединяя их с соответствующими обработчиками.

Примечание. Существует другой метод жизненного цикла, предоставляемый классом Widget, но в этом конкретном примере нам не нужно его использовать. Этот метод является destructor , который будет вызван непосредственно перед уничтожением виджета. Он используется для очистки после виджета, но его нужно использовать только в том случае, если элементы добавляются в DOM за пределами boundingBox (внешней оболочки) виджета.


Помните валидатор, который мы указали как часть объекта ATTRS в первой части этого урока? Метод, который мы указали в качестве значения этого свойства, будет вызываться автоматически при каждой попытке обновить атрибут. Давайте посмотрим на это сейчас; добавьте следующий код сразу после bindUI() :

1
2
3
_validateTerm: function (val) {
    return val !== this.get(«term»);
},

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

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


Затем мы можем начать добавлять наши собственные методы, которые добавят больше функциональности нашему виджету. Первая функция, на которую мы ссылались в методе initializer была _retrieveTweets() , поэтому сначала мы рассмотрим это:

01
02
03
04
05
06
07
08
09
10
_retrieveTweets: function () {
    var that = this,
        url = [this.get(«baseURL»), «&q=», encodeURI(this.get(«term»)), «&rpp=», this.get(«numberOfTweets»)].join(«»),
        handler = function (data) {
        that.set(«tweets», data);
    },
    request = new Y.JSONPRequest(url, handler);
 
    request.send();
},

Сначала мы устанавливаем несколько переменных; ключевое слово this больше не будет указывать на наш экземпляр виджета в обратном вызове success, который мы укажем при выполнении запроса к Twitter, поэтому мы сохраняем ссылку на него в переменной с именем that , как того требует соглашение.

Мы также создаем URL запроса; мы извлекаем атрибуты baseURL , term и numberOfTweets , сохраняя каждый из них как элемент в массиве, а затем используя функцию JavaScript join() чтобы join() их все в строку. Использование массива и метода join() намного быстрее, чем объединение строк с оператором + .

Затем мы определяем наш обратный вызов успеха; все, что нужно сделать этой простой функции — установить атрибут tweets виджета в ответ, полученный от запроса. Ответ будет автоматически передан в функцию обратного вызова.

Последняя переменная, которую мы определяем, предназначена для самого запроса, который инициализируется с помощью метода JSONPRequest() YUI. Этот метод принимает два аргумента; первый — это URL-адрес, на который нужно сделать запрос, а второй — функция обратного вызова, которая вызывается в случае успеха. Наконец, чтобы инициировать запрос, мы просто вызываем метод send() .

Наш следующий пользовательский метод — _createTitle() , который мы вызываем из renderUI() :

01
02
03
04
05
06
07
08
09
10
11
_createTitle: function () {
    var strings = this.get(«strings»),
        titleNode = Node.create(Y.substitute(TweetSearch.TITLE_TEMPLATE, {
            titleclass: TweetSearch.TITLE_CLASS,
            title: strings.title,
            subtitle: strings.subTitle,
            term: this.get(«term»)
        }));
 
    this._titleNode = this.get(«contentBox»).prepend(titleNode);
},

Мы также храним ссылку на атрибут strings для использования внутри функции. Заголовок создается с использованием тех же принципов, что и раньше, хотя на этот раз у нас есть еще несколько токенов для замены в нашем методе substitute() . Этот метод вызывается только в том случае, если для атрибута showTitle установлено значение true . Обратите внимание, что метод get() является цепным, поэтому мы можем вызвать метод prepend() чтобы вставить заголовок сразу после него.

Код здесь очень похож на тот, что использовался ранее, как и в случае нашего следующего метода _createSearchUI() :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
_createSearchUI: function () {
 
       var contentBox = this.get(«contentBox»),
           strings = this.get(«strings»),
           ui = Node.create(Y.substitute(TweetSearch.UI_TEMPLATE, { uiclass: TweetSearch.UI_CLASS })),
           label = Node.create(Y.substitute(TweetSearch.LABEL_TEMPLATE, { labelclass: TweetSearch.LABEL_CLASS, labeltext: strings.label })),
           input = Node.create(Y.substitute(TweetSearch.INPUT_TEMPLATE, { inputclass: TweetSearch.INPUT_CLASS })),
           button = Node.create(Y.substitute(TweetSearch.BUTTON_TEMPLATE, { buttonclass: TweetSearch.BUTTON_CLASS, buttontext: strings.button }));
 
       this._uiNode = ui;
 
       this._labelNode = this._uiNode.appendChild(label);
       this._inputNode = this._uiNode.appendChild(input);
       this._buttonNode = this._uiNode.appendChild(button);
 
       this._uiNode.appendTo(contentBox);
   },

Опять же, очень похоже на то, что мы видели раньше. Помните, единственная причина, по которой это происходит в отдельной функции, заключается в том, что пользовательский интерфейс может быть включен или выключен в любой точке в течение жизненного цикла виджета. Этот метод вызывается только в том случае, если для атрибута showUI установлено значение true .

Далее следует метод _setTerm() , который вызывается прослушивателем событий, прикрепленным к _buttonNode при нажатии кнопки:

1
2
3
_setTerm: function () {
       this.set(«term», this._inputNode.get(«value»));
   },

В этом простом методе мы просто пытаемся установить атрибут term для строки, введенной в <input> . При попытке установить атрибут будет вызван наш валидатор, который обновит атрибут только в том случае, если значение отличается от текущего значения атрибута.

Последний из наших пользовательских методов — это еще один простой метод, используемый для обновления субтитров в заголовке виджета до нового поискового термина; добавьте следующий код:

1
2
3
_uiSetTitle: function (val) {
       this._titleNode.one(«h2 span»).setContent(val);
       },

Этот метод получит новое значение в качестве аргумента (мы вызовем этот метод вручную из метода обработки изменения атрибута, который мы рассмотрим в следующей части этой серии). Мы вызываем метод one() YUI на нашем заголовочном узле, чтобы выбрать <span> внутри субтитра, а затем используем метод setContent() для обновления его внутреннего текста.


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

Хотя все методы, которые мы добавили, похожи по структуре, между ними есть различия; например, методы жизненного цикла получают больше «защиты», чем те методы, которые мы добавляем сами, и поэтому эти методы не имеют префикса с подчеркиванием. Эти методы, в отличие от наших пользовательских, не могут быть вызваны напрямую разработчиком.

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

Наконец, мы посмотрели на пользовательские методы-прототипы, которые нам нужны для того, чтобы заставить наш виджет функционировать. Мы увидели, что мы можем легко использовать встроенные методы get() и set() для получения и установки атрибутов, и что в каждом методе ключевое слово this полезно для экземпляра нашего виджета, чтобы мы могли легко получать и манипулировать различными аспекты виджета.

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

Если у вас есть какие-либо вопросы, пожалуйста, дайте мне знать в разделе комментариев ниже. Большое спасибо за чтение!