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

Мне как всегда не сидится на месте, в этот раз я взялся за 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');

/**
 * &amp;quot;Вешаем&amp;quot; свой обработчик используя метод goog.events.listen
 *
 * @param {EventTarget|goog.events.EventTarget} src
 * @param {string|Array.&amp;lt;string&amp;gt;} 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, // == &amp;quot;click&amp;quot;
    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, {&amp;quot;color&amp;quot;:color});
};

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

&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;js/closure/goog/base.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot; src=&amp;quot;js/simple.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt;
var simple = tutorial.simple(document.getElementById(&amp;quot;header&amp;quot;))
     simple.red();
&amp;lt;/script&amp;gt;

Если у вас прописаны зависимости, то вторая строчка преобразуется в лаконичную 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&amp;amp;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,    /* событие, либо константа, либо просто строкой &amp;quot;click&amp;quot; */
        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');

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

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

    // создаем очередь анимации
    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) {
    // получаем идентификатор &amp;quot;цели&amp;quot;
    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){
        // находим следующий элемент - это &amp;lt;em&amp;gt;
        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){
        // находим следующий элемент - это &amp;lt;em&amp;gt;
        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>:

&amp;lt;ul class=&amp;quot;menu&amp;quot;&amp;gt;
    &amp;lt;li&amp;gt;
        &amp;lt;a href=&amp;quot;http://anton.shevchuk.name&amp;quot; title=&amp;quot;Personal Blog&amp;quot;&amp;gt;Anton Shevchuk&amp;lt;/a&amp;gt;
    &amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;
        &amp;lt;a href=&amp;quot;http://hohli.com&amp;quot; title=&amp;quot;My open source projects&amp;quot;&amp;gt;My Projects&amp;lt;/a&amp;gt;
    &amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;
        &amp;lt;a href=&amp;quot;http://photo.hohli.com&amp;quot; title=&amp;quot;Full size photos under CC&amp;quot;&amp;gt;Photoblog&amp;lt;/a&amp;gt;
    &amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;

Теперь посредством 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');

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

  • :
    goog.array.forEach(goog.dom.$$('li'), function(el){
        goog.events.listen(el, goog.events.EventType.CLICK, function(ev){
            // находим первую ссылку в контексте нашего &amp;lt;li&amp;gt;
            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');
    

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

    // рисуем &amp;quot;зебру&amp;quot;, каждому 2-му элементу добавляем класс &amp;quot;alt&amp;quot;
    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){
            // получаем родителя элемента, которые &amp;lt;div class=&amp;quot;pane&amp;quot;&amp;gt;
            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){
            // получаем родителя элемента, которые &amp;lt;div class=&amp;quot;pane&amp;quot;&amp;gt;
            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){
            // получаем родителя элемента, которые &amp;lt;div class=&amp;quot;pane&amp;quot;&amp;gt;
            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(&amp;quot;This comment will be deleted!&amp;quot;);
    
            // получаем родителя элемента, которые &amp;lt;div class=&amp;quot;pane&amp;quot;&amp;gt;
            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){
            // событие &amp;quot;по умолчанию&amp;quot; нам не нужно
            ev.preventDefault();
            // заменяем атрибуты &amp;quot;большой&amp;quot; картинки на данные из тега &amp;lt;a&amp;gt;
            goog.dom.setProperties(goog.dom.$(&amp;quot;largeImg&amp;quot;), { src: this.href, alt: this.title });
            // так же заменяем текст тега &amp;lt;em&amp;gt; в заголовке
            goog.dom.query('h2 em')[0].innerHTML = &amp;quot;(&amp;quot;+this.title+&amp;quot;)&amp;quot;;
        }, 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: &amp;quot;_blank&amp;quot; });
    });
    

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

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

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

    P.S.

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

  • 38 thoughts on “Google Closure руководство для начинающих”

    1. Фундаментально, лаконично и с расстановкой :) Впрочем как всегда!
      Было интересно и конечно же полезно прочитать.

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

      пример не работает

      1. Таки да, в IE чего не заработал, есть подозрения что причина в селекторе, надо будет пофиксить…

    3. Спасибо за обзор. Но зачем оно нужно? библиотек итак хватает. Типа сделали чтоб свое юзать в своих проектах вместо чужого? Резон так сразу непонятен..

      1. Хм) Гугл безусловно хотел использовать своё) И насколько я понял библиотека была создана довольно давно(намного раньше чем её показали всем), когда многих инструментов ещё не было, так что это не “типа в своих проектах юзать своё”, а корпоративная JS библиотека попавшая в Open Source.

      1. только в этой табличке нету Google Closure Tools

      2. Фишкой библиотеки можно назвать строгую иерархию классов которая очень помогает если использовать совместно с Closue Complier`ом. Всю библиотеку совсем не обязательно подключать сразу. Есть питоновский скрипт который исходя из кода соберёт зависимости и в сжатом виде предоставит вам файл подключения которого будет достаточно для работы страницы. Кроме того меня радует система зависимостей и громадный функционал. Сложно даже сравнивать с чем либо по охвату возможностей. Тут тебе всё всё всё что может пригодиться в разработке, и даже чуть больше, но ВСЁ лишнее легко выкинуть.

    4. И да, те кто говорит, что по Dojo мало документации, то вы наверное не читали документацию по Closure ;)

      Благодаря тебе документации больше становится:)

      1. Тем что это не UI либа. Это многофункциональная библиотека для решения кучи возможностей. Сравнивать её с jQuery сложно так же как сравнивать калькулятор с компьютером, и то и то считает но где то достаточно калькулятора, а иногда не обойтись без компа.

    5. честн соглащусь с последним комментом, в чем преимущество, зачем столько писанины и лишнего кода?? или это чисто принципиально якобы каждому свое? не стал углублятся остановился уже на том что goog.dom.getElementByClass(‘myclass’) чтобы выбрать элементы класса надо написать такую громадину) смысл ?? когда jQuery и даже MooTools позволяют сократить объемы написанного кода?? хотя каждому свое если кому-то нравится пользуйтесь наздоровье)

    6. Спасибо за обзор – отлично, полно и по все делу, все ражевано.
      Что по скорости, при решении одинаковых задач, быстрее будет jQuery или Dojo?

      (за применение данных стилей отвечает сама пакет)

      – ошибочка

      1. В интерфейсе сложно сказать. Чисто субъективно – быстрее. Но только при собранной(специальным скриптом на питоне) версии JS.

    7. Интересно бы увидеть сравнение производительности jQuery vs. Dojo vs. Closure

    8. goog.dom.getElementByClass(‘test’); – чем оно лучше, чем
      document.querySelector(‘.test’) ????
      У нативного querySelector, даже длина кода меньше :):)
      Не понял смысла библиотеки!
      Автор может быть раскроет смысл в следующей статье???

    9. Не нашёл, где оставить FeedBack, поэтому пишу здесь… Очень не хватает ссылки Home и ссылки Читать далее

      p.s. не заставляйте меня думать, пожалуйста…

    10. Мне кажется дабы не смущать публику, стоит подчеркнуть что библиотека совсем не из той же категории что и jQ и задачи, которые она решает с областью применения jQ не пересекаются. Это чтобы не было комментариев а ля “не понятно в чем преимущество перед jQuery UI …”.

      1. Как это не пересекаются?
        1. Примеры все те же самые что и jQ.
        2. Все те же самые задачи, те же селекторы, та же работа с DOM, та же работа со стилями, тот же AJAX и пр.
        3. В этом срезе интересует именно сравнение производительности и др.

        1. Примеры то действительно те же самые — в этом, на мой взгляд и проблема. Взять хотя бы тот факт, что Closure Library __расчитана__ на использование вместе с Closure Compiler, для которого надо писать jsdoc аннотации (чтобы оно все максимально эффективно компилировалось). Где об этом в статье? Более того, селекторы / работа с DOM / etc — только маленькая часть того, что реализовано в библиотеке, посему она совершенно свободно может быть использована server side (что кстати и делает например mongodb-native-driver).

      2. +1 Удивляюсь тому что некоторые программисты не удосужившись посмотреть на либу требуют сравнения. Сам когда то почитал в каком то блоге про CL и моментально влюбился в неё, из за красивой и удобной иерерхии и куче возможностей. Сложно было возвращаться на jQuery с отсутствующей чуть менее чем полностью иерархией, после некоторого срока использования CL…

    11. В статье есть:

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

      Т.е. setPosition может принимать всего 2 параметра(1-й сам элемент, 2-й результат Coordinate)?
      В первом примере setPosition принимает 3 параметра. А какой будет результат Coordinate?
      Как то не логично или Вы ошиблись в написании? Выходит что setPosition не обязательно ждет 3-й параметр top, ему достаточно 2-го, что бы понять как установить top?

      1. Ошибки нет, метод работает именно так, как я описал, собственно вот его листинг:

        /**
         * Sets the top/left values of an element.  If no unit is specified in the
         * argument then it will add px.
         * @param {Element} el Element to move.
         * @param {string|number|goog.math.Coordinate} arg1 Left position or coordinate.
         * @param {string|number=} opt_arg2 Top position.
         */
        goog.style.setPosition = function(el, arg1, opt_arg2) {
          var x, y;
          var buggyGeckoSubPixelPos = goog.userAgent.GECKO &&
              (goog.userAgent.MAC || goog.userAgent.X11) &&
              goog.userAgent.isVersion('1.9');
        
          if (arg1 instanceof goog.math.Coordinate) {
            x = arg1.x;
            y = arg1.y;
          } else {
            x = arg1;
            y = opt_arg2;
          }
        
          // Round to the nearest pixel for buggy sub-pixel support.
          el.style.left = goog.style.getPixelStyleValue_(
              /** @type {number|string} */ (x), buggyGeckoSubPixelPos);
          el.style.top = goog.style.getPixelStyleValue_(
              /** @type {number|string} */ (y), buggyGeckoSubPixelPos);
        };
        
    12. Привет. Антон, подскажи как с closure library реализовать аналог классического $(document).ready(function() {}); ? (т.е. событие загрузки документа до картинок и т.п.)
      Варианты вида ясное дело не подходят. Интересует гугловский goog.events.listen.
      Заранее спасибо :)

      1. В статье я отписал, что аналога в Closure нет, но вы можете использовать <body onload=”my_func()”>

      2. В крайнем случае можно посмотреть как с этим справляется jQuery и написать свою обёртку на CL.

    13. Спасибо за статью! Мне как новичку в Google Closure она очень полезна

    14. получится пишется без мягкого знака

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

    16. Антон, не подскажете, в чем может быть проблема?
      Подключаю библиотеку и делаю простейшие вещи:
      goog.require(‘goog.dom’);
      goog.dom.getElement(‘header’);
      Получаю ошибку “Uncaught TypeError: Cannot call method ‘getElement’ of undefined”.
      Но если вывожу в консоль объект goog, то вижу в нем объект dom, а внутри него функцию getElement().
      Может я что-то неправильно делаю?

      1. Впишите goog.require() в отдельный (это важно!) блок на целевой странице, а после этого уже подключайте внешний файл JavaScript, в котором будет использование CL. Тогда подключение модулей произойдёт правильно.

    17. А как же google closure templates? Которые развязывают руки верстальщикам, и вручают мощный инструментарий.

    Comments are closed.