Антон Шевчук // Web-разработчик

Google Closure руководство для начинающих // JavaScript

Мне как всегда не сидится на месте, в этот раз я взялся за Google Closure Library.

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

Подключение библиотеки

Для подключения Closure нам понадобиться скачать библиотеку с домашней странички проекта на GitHub, и подключить используя простой синтаксис:

<script type="text/javascript" src="js/closure/goog/base.js"></script>

Насчет создания единого CDN хранилища для своей библиотеки Google ответил кратко:

Closure Library differs from a lot of other JavaScript libraries in that it’s very broad and large and, in general, every class or namespace is stored in its own file, so just what is needed can be included in an app. As such, it’s infeasible to require users to pull all of Closure as they would another library in the AJAX APIs, as the download would be very large and split over a lot of individual requests. So we don’t have any immediate plans for AJAX API support.

Хотя, если посмотреть внимательно на тот же Dojo Toolkit, то при похожей архитектуре у них есть возможность организации CDN из коробки.

Подключение пакетов

Closure состоит из отдельных пакетов, которые необходимо подключать по отдельности:

goog.require('goog.dom');
goog.require('goog.events');
goog.require('goog.events.EventType');

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

Поиск DOM элементов

Тут все просто и очень похоже на … обычный JavaScript, т.е. универсального врапера который нам предоставляет jQuery у нас нет, а есть пару пакетов:

// подключаем DOM пакет
goog.require('goog.dom');
// еще один, который базируется на коде Dojo, и обеспечивает подмену QuerySelectorAll
goog.require('goog.dom.query');

Ну теперь непосредственно о выборках:

// получаем элемент по ID
goog.dom.getElement('header')
// или
goog.dom.$('header')

// получаем первый элемент с определенным классом
goog.dom.getElementByClass('myclass')
// или все
goog.dom.getElementsByClass('myclass')

Очень часто придется использовать метод getElementsByTagNameAndClass, т.к. он достаточно универсален:

// получаем все DOM элементы
goog.dom.getElementsByTagNameAndClass()

// получаем все <p class="myclass">
goog.dom.getElementsByTagNameAndClass('p','myclass')

// или используя алиас
goog.dom.$$('p','myclass')

// получаем все <div> элементы
goog.dom.$$('div')

// получаем все элементы с class="myclass"
goog.dom.$$(null, 'myclass')

// получаем все <div> с классом "myclass" внутри элемента id="id" 
goog.dom.$$('div', 'myclass', goog.dom.$('id'))

Если вам и этого показалось мало, тут на помощь приходит document.QuerySelectorAll, ну а точнее его врапер, который основывается на коде Dojo Toolkit (кому нужны эти подробности?):

// так работает
goog.dom.query('h1+h2, a:last-child')

Еще стоит упомянуть поиск элементов среди предков:

// все предки
goog.dom.getAncestorByTagNameAndClass(goog.dom.$('id'))

// лишь <div> элементы
goog.dom.getAncestorByTagNameAndClass(goog.dom.$('id'), 'div')

// все предки с классом class="myclass"
goog.dom.getAncestorByTagNameAndClass(goog.dom.$('id'), null, 'myclass')

// все <div> элементы с классом class="myclass"
goog.dom.getAncestorByTagNameAndClass(goog.dom.$('id'), 'div', 'myclass')

Манипуляции с DOM

Кроме как взять готовое, можно создать DOM элемент ручками:

// создаем элемент
var newHeader = goog.dom.createDom('h2', {'style':'color:red;', 'class':'title'}, 'Hello World!');
// или
var newHeader = goog.dom.$dom('h2', {'style':'color:red;', 'class':'title'}, 'Hello World!');
// добавляем к DOM
goog.dom.appendChild(document.body, newHeader);

Изменяем классы и стили

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

// нам понадобится goog.dom.classes
goog.require('goog.dom.classes');

// устанавливаем имя класса(-ов) элементу
goog.dom.classes.set(goog.dom.$('id'), 'className') 

// получаем все классы элемента
goog.dom.classes.get(goog.dom.$('id'))

// добавляем класс или несколько
goog.dom.classes.add(goog.dom.$('id'), 'className', 'anotherClass') 

// удаляем классы или несколько
goog.dom.classes.remove(goog.dom.$('id'), 'className', 'anotherClass') 

// переключаем класс с одного на другой
goog.dom.classes.swap(goog.dom.$('id'), 'fromClass', 'toClass') 

// добавляем и удаляем классы одним махом
goog.dom.classes.addRemove(goog.dom.$('id'), 'addClass', ['removeClass']) 

// проверяем наличие класса
goog.dom.classes.has(goog.dom.$('id'), 'className') 

// добавляем/удаляем класс по флагу
goog.dom.classes.enable(goog.dom.$('id'), 'className', true||false) 

// "мигаем" классом - если есть удаляем, нет - добавляем
goog.dom.classes.toggle(goog.dom.$('id'), 'className')

Работа со стилями тоже не многим сложнее:

// подключаем
goog.require('goog.style');

// получаем значение атрибута
goog.style.getStyle(goog.dom.$('id'), 'color') 

// устанавливаем значения
goog.style.setStyle(goog.dom.$('id'), 'color', '#ff0000') 
// или
goog.style.setStyle(goog.dom.$('id'), { 'margin-top':'20px', 'margin-bottom':'20px' }) 

Поиграемся с размерами:

// получение размеров
goog.style.getSize(goog.dom.$('id')) 
// задание размеров
goog.style.setSize(goog.dom.$('id'), width, height) 
// или
goog.style.setSize(goog.dom.$('id'), new goog.math.Size(width, height)) 

// задаем высоту
goog.style.setHeight(goog.dom.$('id'), height) 
// задаем ширину
goog.style.setWidth(goog.dom.$('id'), width) 

Расположение элементов:

// указываем top и left 
goog.style.setPosition(goog.dom.$('id'), left, top) 
// или 
goog.style.setPosition(goog.dom.$('id'), new goog.math.Coordinate(x, y)) 

// получаем offsetLeft и offsetTop свойства элементов
// нам вернется объект goog.math.Coordinate 
goog.style.getPosition(goog.dom.$('id')) Opacity manipulation: 

Прозрачность и видимость элементов:

// устанавливаем значение opacity 
goog.style.setOpacity(goog.dom.$('id'), 0.5) 
// устанавливаем значение opacity 
goog.style.getOpacity(goog.dom.$('id')) 

// прячем элемент
goog.style.setStyle(goog.dom.$('id'), "display", "none"); 
// или
goog.style.showElement(goog.dom.$('id'), false); 

// показываем
goog.style.setStyle(goog.dom.$('id'), "display", ""); 
// или
goog.style.showElement(goog.dom.$('id'), true); 

Работаем с событиями

Если вы работали с событиями в JavaScript’е или в jQuery, то для вас тут будет все знакомо, пожалуй стоит упомянуть, что привычного по jQuery DOM ready в Closure нет:

The short story is that we don’t want to wait for DOMContentReady (or worse the load event) since it leads to bad user experience. The UI is not responsive until all the DOM has been loaded from the network. So the preferred way is to use inline scripts as soon as possible.

Хотя, есть возможность повесить обработчик, но его не принято использовать:

goog.events.listen(window, goog.events.EventType.LOAD, function() {
    // тут код вашей функции
});

Это немного сбивает с толку, и вы даже можете встретить использование <body load=”…”>. В остальном, в Closure велосипед не изобрели и работа с событиями похожа на большинство существующих фреймворков, вот что нам необходимо сделать:

// подключаем 
goog.require('goog.events');
goog.require('goog.events.EventType');

/**
 * "Вешаем" свой обработчик используя метод goog.events.listen
 *
 * @param {EventTarget|goog.events.EventTarget} src
 * @param {string|Array.<string>} type
 * @param {Function|Object} listener
 * @param {boolean=} opt_capture
 * @param {Object=} opt_handler
 */
goog.events.listen(src, type, listener, opt_capture, opt_handler);

// пример
goog.events.listen(
    goog.dom.$('id'), 
    goog.events.EventType.CLICK, // == "click"
    function(event){ 
         /*...*/
    }
);  

Чуть-чуть пояснений:

  • src — источник события, обычно это некий DOM элемент, но по факту любой объект с методом dispatchEvent()
  • type — имя события, или массив имен
  • listener — наша функция-обработчик события
  • opt_capture — необязательны булевый параметр, отвечает за то, в какой момент будет происходить перехват события — всплытия или погружения
  • opt_handler — необязательный параметр, позволяет перебиндить this для нашей функции (так которая listener)

Еще немного примеров:

// если нам необходимо обработать событие лишь единожды
goog.events.listenOnce(goog.dom.$('id'), 'click', function(event){ /*...*/ } );

// добавляем обработчик
var key = goog.events.listen(goog.dom.$('id'), 'click', myFunction); 
// и удаляем
goog.events.unlistenByKey(key); 

// добавляем
goog.events.listen(goog.dom.$('id'), 'click', myFunction, true); 
// и удаляем
goog.events.unlisten(goog.dom.$('id'), 'click', myFunction, true);

// удаляем все обработчики
goog.events.removeAll(goog.dom.$('id')); 
// со всего
goog.events.removeAll(); 

Ух, много текста, но вы не отчаивайтесь, сохраните лучше шпаргалку в избранном ;)

Разрабатываем пакет

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

// это имя нашего пакета
goog.provide('tutorial.simple');

// наши зависимости
goog.require('goog.dom');
goog.require('goog.style');

/**
 * constructor и этим все сказано
 */
tutorial.simple = function() {
    /* code here */
};

Теперь стоит немного расширить функционал, надо бы получить какой-то элемент и провести с ним какие-нибудь махинации:

/**
 * @param {Element} el
 */
tutorial.simple = function(el) {
    this.el = el;
};

/**
 * @type {Element}
 */
tutorial.simple.prototype.el = null;

/**
 * меняем цвет текста элемента на красный
 */
tutorial.simple.prototype.red = function(color) {
    goog.style.setStyle(this.el, {"color":color});
};

Теперь как работать с этим кодом-то:

<script type="text/javascript" src="js/closure/goog/base.js"></script>
<script type="text/javascript" src="js/simple.js"></script>
<script type="text/javascript">
var simple = tutorial.simple(document.getElementById("header"))
     simple.red();
</script>

Если у вас прописаны зависимости, то вторая строчка преобразуется в лаконичную goog.require(‘tutorial.simple’);

AJAX

Как же это, писать статью о JavaScript фреймворке и не упомянуть работу с AJAX — конечно же тут есть свой врапер, работа с ним дело не хитрое и каждому по плечу, вот пример:

// потребуется
goog.require('goog.net.XhrIo');

// ну и пример загрузки контента
// воспользуемся статическим методом goog.net.XhrIo.send
// первым параметром идет URL, затем callback
// имя метода и данные передаваемые на сервер
goog.net.XhrIo.send('ajax/response.json', function(ev){
    // берем JSON 
    var data = ev.target.getResponseJson();

    // находим некий контейнер на страничке
    var container = goog.dom.$('articles');

    // идем в цикле по статьям из ответа сервера
    goog.array.forEach(data.articles, function(article){
          // создаем элементы
          var title = goog.dom.$dom('h2', {'innerHTML':article.title});
          var content = goog.dom.$dom('p', {'innerHTML':article.content});
                
          // добавляем их в контейнер
          goog.dom.appendChild(container, title);
          goog.dom.appendChild(container, content);
    })
}, 'POST', 'give=data&or=lost');

Хватит наверное уже вступительной информации, пора переходить к примерам… Хотя вся приведенная информация удобно представлена в виде шпаргалки

Выдвижная панель

Простой пример, для начала — слайд-панель, она у нас будет двигаться вверх/вниз по клику на ссылке (см. пример):

Для реализации данного функционала нам понадобится пакет “AnimatedZippy“:

goog.require('goog.dom');

/* zippy */
goog.require('goog.ui.AnimatedZippy');
goog.require('goog.ui.Zippy');
goog.require('goog.ui.ZippyEvent');

После создания DOM элементов, т.е. внизу HTML страницы пишем код, который должен состоять из одной строки, если бы не мои комментарии:

new goog.ui.AnimatedZippy(
    'button',  // id элемента/элемент который отвечает за управление
    'panel',   // id элемента/элемент который будет открываться/закрываться
    false      // состояние по умолчанию - false - закрыт
);

Для того, чтобы у нас был индикатор со стрелочкой, необходимо описать следующий CSS (за применение данных стилей отвечает сама пакет):

/* открыто */
.goog-zippy-expanded {
  background: url(images/white-arrow.gif) no-repeat right 12px;
}
/* закрыто */
.goog-zippy-collapsed {
  background: url(images/white-arrow.gif) no-repeat right -50px;
}

Магические исчезновения

Еще один незамысловатый пример код — пришел, увидел, скрыл с глаз долой:

Логика элементарна — при клике не картинке с class=”delete” находим родительский элемент и скрываем его:

// инициализация
goog.require('goog.dom');
goog.require('goog.array');  // не обязательно, но стоит знать о данном пакете, и уметь использовать
goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.fx');
goog.require('goog.fx.dom');

/* ... */
goog.array.forEach(goog.dom.getElementsByClass('delete'), function(el) {
    // обработчик событий
    goog.events.listen(
        el,   /* DOM ELement*/
        goog.events.EventType.CLICK,    /* событие, либо константа, либо просто строкой "click" */
        function(ev) {                   /* функция, будет вызвана по событию */
            var anim = new goog.fx.dom.FadeOutAndHide(el.parentNode, 400);
                anim.play();
            ev.preventDefault(); // отменяем событие по умолчанию
        });
});

Анимация

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

Для этого нам понадобятся следующие пакеты:

goog.require('goog.fx');
goog.require('goog.fx.dom');
/* реализация очередей для анимации */
goog.require('goog.fx.AnimationQueue');
goog.require('goog.fx.AnimationSerialQueue');
goog.require('goog.fx.AnimationParallelQueue');

Теперь непосредственно код, который будет отвечать за все превращения и перемещения:

// функция, которая вызывает при клике на ссылку "Run"
function run() {
    // берем наш box
    var elem = goog.dom.getElement("box");

    // создаем очередь анимации
    var queue = new goog.fx.AnimationSerialQueue();
        // изменение прозрачности за 1.2 секунды с 1 до 0.1
        queue.add(new goog.fx.dom.Fade(elem, 1, 0.1, 1200));
        // перемещаем элемент из текущего положение до координат 400,100 за 2 секунды
        queue.add(new goog.fx.dom.SlideFrom(elem,
                [400, 100], 2000,
                goog.fx.easing.easeOut
              ));
        // анимация из функции animationInner, листинг ниже
        queue.add(animationInner(elem));
        // анимация из функции animationInner2, листинг ниже
        queue.add(animationInner2(elem));
        // опять перемещение, до координат 100,100
        queue.add(new goog.fx.dom.SlideFrom(elem,
                [100, 100], 500,
                goog.fx.easing.easeOut
              ));
        // запускаем анимацию
        queue.play();
}

function animationInner(elem){
    // создаем очередь анимаций, которые будут происходить параллельно
    var innerAnim = new goog.fx.AnimationParallelQueue();
        // прозрасность
        innerAnim.add(new goog.fx.dom.Fade(elem, 0.1, 0.5, 2000));
        // перемещение
        innerAnim.add(new goog.fx.dom.SlideFrom(elem,
                [400, 300], 2000,
                goog.fx.easing.easeOut
              ));
        // изменение размеров с 100х100 до 20х20
        innerAnim.add(new goog.fx.dom.Resize(elem,
                [100, 100], [20, 20], 2000,
                goog.fx.easing.easeOut
              ));
    // возвращаем анимацию, дабы поставить ее в другую очередь
    return innerAnim;
}

function animationInner2(elem){
    var innerAnim = new goog.fx.AnimationParallelQueue();
        // прозрачность
        innerAnim.add(new goog.fx.dom.FadeIn(elem, 1200));
        // перемещение
        innerAnim.add(new goog.fx.dom.SlideFrom(elem,
                [100, 300], 1200,
                goog.fx.easing.easeOut
              ));
        // изменение размеров
        innerAnim.add(new goog.fx.dom.Resize(elem,
                [20, 20], [100,100], 1200,
                goog.fx.easing.easeOut
              ));
    // возвращаем анимацию, дабы поставить ее в другую очередь
    return innerAnim;
}

Вот такой код, как мы можем заметить, кода поболее чем при использовании jQuery.

Гармошка

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

Для реализации, нам потребуются следующие пакеты:

goog.require('goog.ui.AnimatedZippy');
goog.require('goog.ui.Zippy');
goog.require('goog.ui.ZippyEvent');

Ну и сама реализация с комментариями:

var panels = goog.dom.$$('h3');
var anims  = {};
var open   = null;

// для каждой панельки (в данном случае это теги H3)
goog.array.forEach(panels, function(pane){
    // создаем анимацию, знакомую нам по примеру с панелькой
    var animation = new goog.ui.AnimatedZippy(pane, goog.dom.getNextElementSibling(pane));

    // создаем обработчик события и вешаем его на событие анимации 
    // листинг функции чуть ниже
    goog.events.listen(animation, goog.ui.Zippy.Events.TOGGLE, zippyToggle);

    // сохраняем идентификатор анимации - он нам еще понадобится
    anims[goog.getUid(animation)] = animation;
});

function zippyToggle(event) {
    // получаем идентификатор "цели"
    var uid = goog.getUid(event.target);

    // реализуем логику скрытия других панелей
    if (event.expanded) {
        if (open) {
            anims[open].setExpanded(false);
        }
        open = uid;
    }
}

Гармошка, вариант 2

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

// открытый элемент (отсчет с 0, если кто забыл)
var def    = 2;

goog.array.forEach(panels, function(pane, ind /* добавляем порядковый номер в цикл */){
    // добавляем проверку на совпадение ind и def
    var animation = new goog.ui.AnimatedZippy(pane, goog.dom.getNextElementSibling(pane), (def==ind));
    // отслеживаем другое событие
    goog.events.listen(animation, goog.fx.Animation.EventType.BEGIN, zippyToggle);
    anims[goog.getUid(animation)] = animation;
});

Анимация для события hover

Данный пример реализует анимацию для события hover — в меру симпатично и функционально:

Для реализации потребуется:

// DOM
goog.require('goog.dom');
goog.require('goog.dom.query');
// forEach
goog.require('goog.array');
// события
goog.require('goog.events');
goog.require('goog.events.EventType');
// анимация
goog.require('goog.fx');
goog.require('goog.fx.dom');
goog.require('goog.fx.AnimationQueue');
goog.require('goog.fx.AnimationSerialQueue');
goog.require('goog.fx.AnimationParallelQueue');

Код реализации:

// для каждой ссылки в элементе с class=menu
goog.array.forEach(goog.dom.query('.menu a'), function(el){
   // скрыть следующий элемент в доме
   goog.dom.getNextElementSibling(el).style.display = 'none';

   // вешаем обработчик на событие MOUSEOVER
   goog.events.listen(el, goog.events.EventType.MOUSEOVER, function(ev){
        // находим следующий элемент - это <em>
        var em = goog.dom.getNextElementSibling(this);
        // создаем очередь анимации
        var anim = new goog.fx.AnimationParallelQueue();
            anim.add(new goog.fx.dom.FadeInAndShow(em, 1500));
            anim.add(new goog.fx.dom.Slide(em,
                    [-15, -85], [-15, -75], 1500,
                    goog.fx.easing.easeOut
                  ));
        // запускаем
        anim.play();
    }, false,  el);

   // вешаем обработчик на событие MOUSEOUT
    goog.events.listen(el, goog.events.EventType.MOUSEOUT, function(ev){
        // находим следующий элемент - это <em>
        var em = goog.dom.getNextElementSibling(this);
        // создаем очередь анимации
        var anim = new goog.fx.AnimationParallelQueue();
            anim.add(new goog.fx.dom.FadeOutAndHide(em, 500));
            anim.add(new goog.fx.dom.Slide(em,
                    [-15, -75], [-15, -85], 500,
                    goog.fx.easing.easeOut
                  ));
        // запускаем
        anim.play();
    }, false,  el);
})

Анимация для события hover. Вариант 2

Наличие лишнего тега <em> лишь для подсказки меня не радует, куда как лаконичней использовать атрибут title тега <a>:

<ul class="menu">
    <li>
        <a href="http://anton.shevchuk.name" title="Personal Blog">Anton Shevchuk</a>
    </li>
    <li>
        <a href="http://hohli.com" title="My open source projects">My Projects</a>
    </li>
    <li>
        <a href="http://photo.hohli.com" title="Full size photos under CC">Photoblog</a>
    </li>
</ul>

Теперь посредством Javascript’a создадим элемент <em>, и добавим в существующий код:

goog.array.forEach(goog.dom.query('.menu a'), function(el){
    goog.dom.append(el.parentNode, goog.dom.$dom('em', {'style':'display:none;', 'innerHTML':el.title}));

    /* код из предыдущего листинга */

});

Результат не отличим визуально от предыдущего.

Кликабельные блоки

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

Подключаем пакеты:

goog.require('goog.dom');
goog.require('goog.array');
goog.require('goog.events');
goog.require('goog.events.EventType');

Создаем обработчик события для каждого <li>:

goog.array.forEach(goog.dom.$$('li'), function(el){
    goog.events.listen(el, goog.events.EventType.CLICK, function(ev){
        // находим первую ссылку в контексте нашего <li>
        var link = goog.dom.$$('a', null, this)[0];
        // переходим по ссылке
        window.location = link.href;
        ev.preventDefault();
    }, false,  el);
});

Смотрим на пример в живую.

Складывающиеся панельки

Данный пример чем-то схож с «гармошкой», но добавлено немного полезны фич:

Задача требует от нас следующих действий:

  • скрываем все элементы <div class=message_body> после первого
  • скрываем все элементы <li> после пятого
  • клик по <p class=message_head> — вызывает изменение отображение (показываем/прячем) для следующего элемента <div class=message_body>
  • клик по <a class=collpase_all_message> — скрывает все <div class=message_body>
  • клик по <a class=show_all_message> — скрывает элемент, и отображает <a class=show_recent_only>, так же скрывает все элементы <li> после пятого
  • клик по <a class=show_recent_only> — скрывает элемент, и отображает <a class=show_all_message>, так же отображает все <li> после пятого

Нам потребуются:

goog.require('goog.dom');
goog.require('goog.dom.query');
goog.require('goog.array');

// события
goog.require('goog.events');
goog.require('goog.events.EventType');

// работа со стилями
goog.require('goog.style');

// анимация Zippy
goog.require('goog.ui.AnimatedZippy');
goog.require('goog.ui.Zippy');
goog.require('goog.ui.ZippyEvent');

Ну и листинг кода с комментариями:

// функция, которая скрывает/отображает элементы после 5-го
function showMore(flag) {
    // тут используется селектор из CSS3, лучше понимать что он делает ;)
    goog.array.forEach(goog.dom.query('#messages li:nth-child(n+6)'), function(el){
        // скрываем/отображаем элемент
        el.style.display = (flag?'':'none');
    });

    // заменяем кнопочку управления
    goog.style.showElement(goog.dom.$('show_recent_only'), flag);
    goog.style.showElement(goog.dom.$('show_all_message'), !flag);

}
// скроем элементы
showMore(false);

// покажем кнопку id=show_recent_only
goog.style.showElement(goog.dom.$('show_recent_only'), false);

// инициализируем для каждого сообщения AnimatedZippy
var zippy = [];
goog.array.forEach(goog.dom.$$('h4',null,goog.dom.$('messages')), function(pane, index){
    zippy.push(new goog.ui.AnimatedZippy(pane, goog.dom.getNextElementSibling(pane), (index==0)));
});

// обработчик события click для кнопки скрытия текста сообщений
goog.events.listen(goog.dom.$('collpase_all_message'), 'click', function(ev){
    // методом перебора всех элементов из массива zippy
    goog.array.forEach(zippy, function(anim){
        // собственно - складывание
        anim.setExpanded(false);
    });
    ev.preventDefault();
});

// обработчик для кнопки отображения всех сообщений (т.е. всех после 5-го)
goog.events.listen(goog.dom.$('show_all_message'), 'click', function(ev){
    showMore(true);
    ev.preventDefault();
});

// обработчик для кнопки скрытия всех сообщений кроме первых пяти
goog.events.listen(goog.dom.$('show_recent_only'), 'click', function(ev){
    showMore(false);
    ev.preventDefault();
});

Управление комментариями (аля WordPress)

Еще один пример — управление комментариями в стиле wordpress:

Для этого потребуется:

goog.require('goog.dom');
goog.require('goog.dom.query');
goog.require('goog.dom.classes');
goog.require('goog.array');
goog.require('goog.fx');
goog.require('goog.fx.dom');
goog.require('goog.events');
goog.require('goog.events.EventType');

Ну и листинг кода:

// рисуем "зебру", каждому 2-му элементу добавляем класс "alt"
goog.array.forEach(goog.dom.query('.pane:nth-child(2n)'), function(el) {
    goog.dom.classes.add(el, 'alt');
});

// функция, которая создает обработчик для кнопки unapprove
function unapprove(el) {
    // вешаем обработчик на click
    goog.events.listen(el, goog.events.EventType.CLICK, function(event){
        // получаем родителя элемента, которые <div class="pane">
        var pane = goog.dom.getAncestorByTagNameAndClass(event.target, 'div', 'pane');

        // изменяем цвет элемента
        goog.fx.dom.bgColorFadeIn(pane, [255, 240, 120], 2000);
        // добавляем класс
        goog.dom.classes.add(pane, 'spam');

        event.preventDefault();

        // создаем кнопку approve и вешаем на нее соответствующий обработчик
        var button = goog.dom.createDom('a', {'href':'#','class':'btn-approve','innerHTML':'Approve'});
        // обработчик
        approve(button);
        
        // заменяем кнопку unapprove на созданную approve
        goog.dom.replaceNode(button, event.target);
    });
}

// функция, которая создает обработчик для кнопки approve
function approve(el) {
    // вешаем обработчик на click
    goog.events.listen(el, goog.events.EventType.CLICK, function(event){
        // получаем родителя элемента, которые <div class="pane">
        var pane = goog.dom.getAncestorByTagNameAndClass(event.target, 'div', 'pane');

        // изменяем цвет элемента
        goog.fx.dom.bgColorFadeIn(pane, [200, 240, 180], 2000);
        // добавляем класс
        goog.dom.classes.remove(pane, 'spam');

        event.preventDefault();

        // создаем кнопку unapprove и вешаем на нее соответствующий обработчик
        var button = goog.dom.createDom('a', {'href':'#','class':'btn-unapprove','innerHTML':'Unapprove'});
        // обработчик
        unapprove(button);

        // заменяем кнопку approve на созданную unapprove
        goog.dom.replaceNode(button, event.target);
    });
}


goog.array.forEach(goog.dom.query('.pane .btn-unapprove'), unapprove);
goog.array.forEach(goog.dom.query('.pane .btn-approve'), approve);

// обработчик для кнопок spam
goog.array.forEach(goog.dom.query('.pane .btn-spam'), function(el) {
    goog.events.listen(el, goog.events.EventType.CLICK, function(event){
        // получаем родителя элемента, которые <div class="pane">
        var pane = goog.dom.getAncestorByTagNameAndClass(event.target, 'div', 'pane');

        // анимация фона
        goog.fx.dom.bgColorFadeIn(pane, [250, 200, 200], 2000);
        // прячем
        new goog.fx.dom.FadeOutAndHide(pane, 500).play();
        event.preventDefault();

    });
});
// обработчик для кнопок delete
goog.array.forEach(goog.dom.query('.pane .btn-delete'), function(el) {
    goog.events.listen(el, goog.events.EventType.CLICK, function(event){
        alert("This comment will be deleted!");

        // получаем родителя элемента, которые <div class="pane">
        var pane = goog.dom.getAncestorByTagNameAndClass(event.target, 'div', 'pane');

        // анимация фона
        goog.fx.dom.bgColorFadeIn(pane, [250, 200, 200], 2000);
        // прячем
        new goog.fx.dom.FadeOutAndHide(pane, 500).play();
        event.preventDefault();
    });
});

Галерея изображений

Простейший пример реализации галереи, без перезагрузки страницы. (см. пример):

Листинг с комментариями:

// потребуется
goog.require('goog.dom');
goog.require('goog.dom.query');
goog.require('goog.array');
goog.require('goog.style');
goog.require('goog.events');

// логика
// для каждой ссылки внутри class=thumbs
goog.array.forEach(goog.dom.query('.thumbs a'), function(el) {
    // добавляем обработчик для события clikc
    goog.events.listen(el, 'click', function(ev){
        // событие "по умолчанию" нам не нужно
        ev.preventDefault();
        // заменяем атрибуты "большой" картинки на данные из тега <a>
        goog.dom.setProperties(goog.dom.$("largeImg"), { src: this.href, alt: this.title });
        // так же заменяем текст тега <em> в заголовке
        goog.dom.query('h2 em')[0].innerHTML = "("+this.title+")";
    }, false, el);
});

Стилизируем ссылки

Данный пример хорошо иллюстрирует работу с CSS селекторами, в данном случае — необходимо изменить класс ссылок в тексте в зависимости от того куда она ведет:

Для реализации даной задачи нам потребуются:

goog.require('goog.dom');
goog.require('goog.dom.query');
goog.require('goog.array'); // да я просто ленивый, мне влом писать for
goog.require('goog.style');

И следующий код:

// находим все ссылки в которых href заканчивается на pdf, и добавляем соответствующий класс
goog.array.forEach(goog.dom.query('a[href$=pdf]'), function(el){
    goog.dom.classes.add(el, 'pdf')
});

// находим все ссылки в которых href заканчивается на zip, и добавляем соответствующий класс
goog.array.forEach(goog.dom.query('a[href$=zip]'), function(el){
    goog.dom.classes.add(el, 'zip')
});

// находим все ссылки в которых href заканчивается на png, и добавляем соответствующий класс
goog.array.forEach(goog.dom.query('a[href$=png]'), function(el){
    goog.dom.classes.add(el, 'png')
});

// находим все ссылки которые ведут на внешние ресурсы
goog.array.forEach(goog.dom.query('a:not([href^=http://anton.shevchuk.name]):not([href^=#])'), function(el){
    // добавляем класс external
    goog.dom.classes.add(el, 'external');
    // изменяем target на _blank
    goog.dom.setProperties(el, { target: "_blank" });
});

Использование UI

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

Все UI пакеты имеют два метода render() и decorate() — первый создаёт все необходимые DOM элементы в указанном контейнере, второй же использует заранее созданную структуру и пытается с ней работать. Хотя на этом остановлюсь, если получиться — то о UI я расскажу в рамках следующей статьи.

P.S.

Обычно, после подобных статей, у меня спрашивают — «А лучше ли этот фреймворк, чем jQuery?». И я отвечаю — это разного уровня фреймворки, и предназначены для решения различных задач, если вы успешно справляетесь с использованием jQuery и плагинов, то не стоит сильно заморачиваться, ведь всё и так работает. Если же говорить о Dojo Toolkit — это как двоюродный брат, много общих черт, но они таки разные. И да, те кто говорит, что по Dojo мало документации, то вы наверное не читали документацию по Closure ;)

© Антон Шевчук 2007-2016