Статьи

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

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

Давайте начнем прямо сейчас!


Группа методов обработки изменений атрибутов вызывается, когда некоторые из наших атрибутов меняют значения. Начнем с добавления метода, который вызывается при showTitle атрибута showTitle ; добавьте следующий код непосредственно после _uiSetTitle() :

01
02
03
04
05
06
07
08
09
10
11
_afterShowTitleChange: function () {
    var contentBox = this.get(«contentBox»),
        title = contentBox.one(«.yui3-tweetsearch-title»);
 
    if (title) {
        title.remove();
        this._titleNode = null;
    } else {
        this._createTitle();
    }
},

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

Если узел заголовка уже существует, мы удаляем его с помощью метода remove() YUI. Мы также установили для _titleNode виджета значение null. Если узел не существует, мы просто вызываем метод _createTitle() нашего виджета, чтобы сгенерировать и отобразить его.

Далее мы можем обработать showUI атрибута showUI :

01
02
03
04
05
06
07
08
09
10
11
_afterShowUIChange: function () {
    var contentBox = this.get(«contentBox»),
        ui = contentBox.one(«.yui3-tweetsearch-ui»);
 
    if (ui) {
        ui.remove();
        this._uiNode = null;
    } else {
        this._createSearchUI();
    }
},

Этот метод почти идентичен последнему — все, что изменяется, это то, что мы ищем изменение другого атрибута и либо удаляем, либо создаем другую группу элементов. Опять же, мы устанавливаем для свойства _uiNode нашего виджета значение null , чтобы виджет знал о последнем состоянии своего пользовательского интерфейса.

Наш следующий метод вызывается после изменения атрибута term :

1
2
3
4
5
6
7
8
9
_afterTermChange: function () {
    this._viewerNode.empty().hide();
    this._loadingNode.show();
 
    this._retrieveTweets();
    if (this._titleNode) {
        this._uiSetTitle(this.get(«term»));
    }
},

Когда атрибут term изменяется, мы сначала удаляем все предыдущие результаты поиска из средства просмотра, вызывая метод empty() YUI (в частности модуля Node) empty() а затем метод hide() . Мы также показываем наш узел загрузчика для некоторой визуальной обратной связи, что что-то происходит

Затем мы вызываем наш _retrieveTweets() чтобы инициировать новый запрос к API поиска Twitter. Это вызовет каскад дополнительных методов, которые будут вызваны, в результате чего зритель будет обновлен новым набором твитов. Наконец, мы проверяем, имеет ли виджет в настоящее время _titleNode , и если это так, мы вызываем метод _uiSetTitle() , чтобы обновить субтитры новым поисковым термином.

Наш последний обработчик изменений атрибута является самым крупным и имеет дело с изменениями атрибутов tweets , которые произойдут в результате выполнения запроса к Twitter:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
_afterTweetsChange: function () {
    var x,
        results = this.get(«tweets»).results,
        not = this.get(«numberOfTweets»),
        limit = (not > results.length — 1) ?
 
    if (results.length) {
 
        for (x = 0; x < limit; x++) {
            var tweet = results[x],
                text = this._formatTweet(tweet.text),
                tweetNode = Node.create(Y.substitute(TweetSearch.TWEET_TEMPLATE, {
                    userurl: «http://twitter.com/» + tweet.from_user, avatar: tweet.profile_image_url,
                    username: tweet.from_user, text: text
                }));
 
            if (this.get(«showUI») === false && x === limit — 1) {
                tweetNode.addClass(«last»);
            }
            this._viewerNode.appendChild(tweetNode);
        }
 
        this._loadingNode.hide();
        this._viewerNode.show();
    } else {
        var errorNode = Node.create(Y.substitute(TweetSearch.ERROR_TEMPLATE, {
            errorclass: TweetSearch.ERROR_CLASS,
            message: this.get(«strings»).errorMsg
        }));
 
        this._viewerNode.appendChild(errorNode);
        this._loadingNode.hide();
        this._viewerNode.show();
    }
},

Во-первых, мы устанавливаем переменные, которые нам нужны в методе, включая переменную счетчика для использования в for loop , массив results из ответа, который хранится в атрибуте tweets , значение атрибута numberOfTweets и limit , который либо число результатов в массиве results , либо заданное количество твитов, если в массиве меньше элементов, чем количество твитов.

Оставшийся код для этого метода заключен в условное условие if которое проверяет, есть ли на самом деле результаты, что может быть не так, если бы не было твитов, содержащих поисковый термин. Если в массиве есть результаты, мы перебираем каждый из них, используя for loop . На каждой итерации мы получаем текущий твит и передаем его в служебный метод _formatTweet() , который добавит любые ссылки, имена пользователей или хеш-теги, найденные в тексте, а затем создадим новый узел для твита, используя те же принципы, которые мы рассмотрели в последней части этого урока.

Когда searchUI не виден, мы должны немного изменить стиль виджета, чтобы предотвратить двойную границу внизу виджета. Мы проверяем, установлен ли для атрибута showUI значение false и является ли последний обрабатываемый твит, и если это так, добавьте имя класса last в твит, используя метод addClass() YUI. Затем мы добавляем вновь созданный узел в узел просмотра, чтобы отобразить его в виджете.

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

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


Мы добавили все методы, которые поддерживают изменение значений атрибутов. На данный момент у нас есть только один дополнительный метод для добавления; метод _formatTweet() который мы _formatTweet() из цикла for loop только что добавленного метода. Этот метод заключается в следующем:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
_formatTweet: function (text) {
 
    var linkExpr = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig,
        atExpr = /(@[\w]+)/g,
        hashExpr = /[#]+[A-Za-z0-9-_]+/g,
        string = text.replace(linkExpr, function (match) {
            return match.link(match);
        });
 
    string = string.replace(atExpr, function (match) {
        return match.link(«http://twitter.com/» + match.substring(1));
    });
    string = string.replace(hashExpr, function (match) {
        return match.link(«http://twitter.com/search?q=» + encodeURI(match));
    });
 
    return string;
}

Этот метод принимает один аргумент, который представляет собой текст из элемента ‘current’ массива results который мы хотим связать / atify / hashify. Мы начнем с определения трех регулярных выражений, первое из которых будет соответствовать любым ссылкам в тексте, которые начинаются с http, https или ftp и содержат любые символы, которые разрешены в URL. Второй будет соответствовать любым именам пользователей Twitter (любые строки, начинающиеся с символа @), а последний будет соответствовать любым строкам, которые начинаются с символа #.

Затем мы устанавливаем переменную с именем string, которая используется для хранения преобразованного текста. Сначала мы добавляем ссылки. Функция JavaScript replace() принимает регулярное выражение для сопоставления ссылок в качестве первого аргумента и функцию в качестве второго аргумента — функция будет выполняться каждый раз, когда найдено совпадение, и ей передается соответствующий текст в качестве аргумента. Затем функция возвращает совпадение, преобразовав его в элемент ссылки с помощью функции JavaScript link() . Эта функция принимает URL-адрес, который используется для ссылки на полученную ссылку. Соответствующий текст используется для href .

Затем мы снова используем функцию replace() для строки, но на этот раз мы передаем регулярное выражение @ match в качестве первого аргумента. Эта функция работает так же, как и раньше, но также добавляет URL-адрес Twitter к началу href который используется для переноса соответствующего текста. Строковая переменная затем обрабатывается таким же образом, чтобы сопоставлять и преобразовывать любые хэшированные слова, но на этот раз URL-адрес API поиска в Твиттере используется для создания ссылок. После обработки текста мы возвращаем полученную строку.

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

Неттутс + Имидж

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

01
02
03
04
05
06
07
08
09
10
11
12
.yui3-tweetsearch-title { padding:1%;
.yui3-tweetsearch-title h1, .yui3-tweetsearch-title h2 { margin:0;
.yui3-tweetsearch-title h1 { padding-left:60px;
.yui3-tweetsearch-title h2 { padding-top:5px;
.yui3-tweetsearch-content { margin:1%;
.yui3-tweetsearch-viewer article, .yui3-tweetsearch-ui { padding:1%;
.yui3-tweetsearch-viewer img { width:48px;
.yui3-tweetsearch-viewer h1 { margin:0;
.yui3-tweetsearch-label { margin-right:1%;
.yui3-tweetsearch-input { padding:0 0 .3%;
.yui3-tweetsearch-title:after, .yui3-tweetsearch-viewer article:after,
.yui3-tweetsearch-ui:after { content:»»;

Сохраните эту таблицу стилей как tweet-search-base.css в папке css . Как видите, мы нацеливаемся на все элементы в виджете, используя имена классов, которые мы создали в первой части. На одной странице может быть несколько экземпляров виджета, и мы не хотим, чтобы наши стили влияли на любые другие элементы на странице за пределами нашего виджета, поэтому использование имен классов таким способом действительно является единственным надежным решением.

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

Далее мы можем добавить файл скина; добавьте следующий код в другой новый файл:

01
02
03
04
05
06
07
08
09
10
.yui3-skin-sam .yui3-tweetsearch-content { border:1px solid #A3A3A3;
.yui3-skin-sam .yui3-tweetsearch-title { border-bottom:1px solid #A3A3A3;
.yui3-skin-sam .yui3-tweetsearch-title span { color:#EB8C28;
.yui3-skin-sam .yui3-tweetsearch-loader, .yui3-skin-sam .yui3-tweetsearch-error { padding-top:9%;
.yui3-skin-sam .yui3-tweetsearch-error { background-image:url(/img/error.png);
.yui3-skin-sam .yui3-tweetsearch article { border-bottom:1px solid #A3A3A3;
.yui3-skin-sam .yui3-tweetsearch article.last { border-bottom:none;
.yui3-skin-sam .yui3-tweetsearch a { color:#356DE4;
.yui3-skin-sam .yui3-tweetsearch a:hover { color:#EB8C28;
.yui3-skin-sam .yui3-tweetsearch-ui { border-top:1px solid #fff;

Сохраните этот файл как tweet-search-skin.css в папке css . Хотя мы также используем здесь сгенерированные имена классов, каждому правилу yui3-skin-sam имя класса yui3-skin-sam поэтому правила применяются только тогда, когда используется тема Sam по умолчанию. Это позволяет очень легко изменить общий вид виджета. Однако это означает, что разработчику-исполнителю потребуется добавить имя yui3-skin-sam к элементу на странице, обычно , но это, вероятно, уже используется, если используются другие модули библиотеки.

Как и раньше, мы добавили довольно легкий стиль, хотя у нас есть немного больше свободы выражения с файлом скина, отсюда и тонкие тонкости, такие как скругленные углы и градиенты css. Мы также должны порекомендовать, чтобы таблицы стилей YUI css-reset, css-fonts и css-base также использовались при реализации нашего виджета, поскольку это является одной из причин, по которым пользовательские таблицы стилей, используемые виджетом, хороши и малы.


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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html lang=»en»>
    <head>
        <title>YUI3 Twitter Search Client</title>
        <link rel=»stylesheet» href=»http://yui.yahooapis.com/combo?3.4.1/build/cssreset/cssreset-min.css&3.4.1/build/cssfonts/cssfonts-min.css&3.4.1/build/cssbase/cssbase-min.css»>
        <link rel=»stylesheet» href=»css/tweet-search-base.css» />
        <link rel=»stylesheet» href=»css/tweet-search-skin.css» />
    </head>
    <body class=»yui3-skin-sam»>
        <div id=»ts»></div>
        <script src=»//yui.yahooapis.com/3.4.1/build/yui/yui-min.js»></script>
        <script src=»js/tweet-search.js»></script>
        <script>
            YUI().use(«tweet-search», function (Y) {
                var myTweetSearch = new Y.DW.TweetSearch({
                    srcNode: «#ts»
                });
                myTweetSearch.render();
            });
        </script>
    </body>
</html>

Единственный файл сценария YUI, на который мы должны ссылаться, — это исходный файл YUI, который устанавливает глобальный объект YUI и загружает необходимые модули.

Сохраните этот файл в корневом каталоге проекта. Прежде всего, мы ссылаемся на объединенную таблицу стилей YUI reset, base и fonts, размещенную на CDN, а также на две наши собственные таблицы стилей, которые мы только что создали. Мы также добавляем имя класса yui3-skin-sam к страницы, чтобы подобрать стиль темы для нашего виджета. На странице мы добавляем контейнер для нашего виджета и присваиваем ему атрибут id для легкого выбора.

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

В последнем элементе скрипта мы создаем глобальный объект YUI и вызываем метод use() указывающий имя нашего виджета (не статическое NAME используемое внутри нашего виджета, а имя, указанное в методе add() оболочки нашего виджета) как первый аргумент.

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

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

В функции обратного вызова мы создаем новый экземпляр нашего виджета, хранящийся в переменной. Конструктор нашего виджета доступен через пространство имен, которое мы указали в классе виджета, который присоединен к экземпляру YUI как свойство. Конструктор нашего виджета принимает объект конфигурации в качестве аргумента; мы используем это, чтобы указать контейнер, в который мы хотим визуализировать наш виджет, в данном случае пустой <div> мы добавили на страницу. Указанный элемент станет contentBox нашего виджета. Наконец, мы вызываем метод render() для переменной, в которой хранится экземпляр нашего виджета, который отображает HTML-код нашего виджета в указанный контейнер.

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

1
2
3
4
5
{
    srcNode: «#ts»,
    showTitle: false,
    showUI: false
}

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

01
02
03
04
05
06
07
08
09
10
{
    srcNode: «#ts»,
    strings: {
        title: «Twitter Search Widget»,
        subTitle: «Mostrando resultados de:»,
        label: «Término de búsqueda»,
        button: «Búsqueda»,
        errorMsg: «Lo siento, ese término de búsqueda no ha obtenido ningún resultado. Por favor, intente un término diferente»
    }
}

Теперь, когда мы запускаем виджет, весь видимый текст (кроме, конечно, твитов) для виджета на испанском языке:

Неттутс + Имидж

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

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