Заметки за 2011 год

Блоговский движок под системой контроля версий Git

Все файлы блоговского движка я храню в Git. Я не стану всем и каждому советовать сразу переходить на систему контроля версий. Очевидно, что для её развертывания потребуется как минимум VPS, навыки администрирования и, собственно, умение пользоваться ею. Иначе все плюсы обратятся в сплошные минусы.

Однако, преодолев все эти трудности можно получить некоторые преимущества.

Редактирование темы и плагинов

Благодаря Git редактировать файлы можно как на домашнем компьютере, так и прямо на сервере. При этом точно знаю, что ничего не «случайно» не потеряется или перезатрется.

Если один и тот же файл был изменен в разных местах, то система сама попробует решить конфликт. А если её это окажется не под силу, то предоставит вам эту возможность.

Обновление версии движка

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

Чтобы сохранить свои исправления я выработал такую схему работы

Дерево коммитов Git

В одной ветке (wp31) я храню исходные файлы дистрибутива и все новые версии разворачиваю только туда. А свои правки делаю в основной ветке (master).

После обновления я объединяю эти ветки. Если повезет, то никаких конфликтов не появляется, и в основной ветке я получаю новую версию движка. Чаще всего конфликты в коде все же есть. Но они, как правило, не значительные и легко решаются с помощью соответствующих утилит.

Развертывание изменений на сервере

Все действия сводятся к одной простой команде.

git pull

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

Резервное копирование

Каждый Git-репозиторий может стать резервной копией для других. Фактически у меня 3 идентичных копии: продакшин, внешний репозиторий и локальный репозиторий на домашнем компьютере. В случае повреждения одной из них, восстановить её можно будет из двух других копий.

54307730.19286420.1324395758.c7d8ac3d6d9658c7c572ebaca98a9ceb

Комментарии к заметке: 1

Исправляем структуру HTML5-документа в старых браузерах

В старых версиях Internet Explorer теги, которых нет в HTML 4.01, правильно будут стилизоваться только, если они предварительно созданы через JavaScript.

document.createElement("article");

Если JavaScript будет отключен, то для таких тегов не только не будут применяться стили, но и сама структура документа будет нарушена.

Пример правильной структуры документа в Firefox 8

Правильная структура HTML5-документа

А так этот же документ будет разобран в Internet Explorer 6

Структура документа нарушена из-за новых тегов

Из-за нарушения общей структуры в таком документе становятся бессмысленными и каскадные правила CSS.

Наверное, сейчас верстка под IE6 с отключенным JS уже не является актуальнейшей проблемой. Тем не менее, поправить структуру документа можно с помощью дополнительных блочных или строчных (в зависимости от контекста) тегов на каждый тег, которого не было в спецификации HTML 4.01.

Исправленная структура документа

Комментарии к заметке: 3

Навигация по ссылкам в списках

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

Список авторов статей в блоге

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

<ul class="authors">
  <li class="author">
    <div class="author-userpic">
      <a class="author-archive-link" title="Vicki Moyes" href="/author/vicki/">
        <img class="author-pic photo" alt="Vicki Moyes" src="/img/users/vicki-moyes.jpg">
      </a>
    </div>
    <p class="author-title">
      <a href="/author/vicki/" class="author-title-archive-link">Vicki Moyes</a>
    </p>
    <p class="author-bio">Director of Moyes Gliders.
      Organizer of annual Forbes competition.</p>
  </li>
</ul>

Используем tabindex=-1

Элементы, обрамленные ссылками

При переключении фокуса с помощью клавиши «Tab» пользователь каждый раз сначала попадает на ссылку с фотографии, затем на ссылку с имени.

Проектируя интерфейс, первостепенно внимание уделяется пользователям, работающим с мышью или touch-интерфейсом. Кому-то из них привычнее и удобнее кликать по картинкам, а кто-то кликает только по явно обозначенным ссылкам. Поэтому удалить ссылку, обрамляющую фотографию, нельзя. Но зато можно сделать так, чтобы она не участвовала в последовательности переключения фокуса с клавиатуры. Для этого добавим ей атрибут tabindex="-1".

Переходы только по именам авторов

Такой нехитрый прием позволяет значительно улучшить впечатления пользователя от интерфейса и сделать его поведение более предсказуемым.

Одна ссылка

В этом примере два находящиеся рядом объекта (фотография и имя автора) должны вести на одну и ту же страницу. Технически их можно обернуть одной ссылкой. В HTML5 внутри строчного элемента a могут находиться как строчные, так и блочные элементы.

<a href="page.html">
    <div><img src="img1.jpg" alt="Page image"></div>
    <p>Page title</p>
</a>

Фрагмент кода валиден на 100%, только если используется в HTML5 документе.

Комментарии к заметке: 4

Оповещение модулей через jQuery.Callbacks

Обмен событиями между отдельными компонентами программы можно построить различными способами. В зависимости от потребностей это можно сделать:

  1. через интерфейс Observable или произвольные DOM-события;

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

  2. через глобальный объект;

    Экземпляры компонент ничего не знают друг о друге. Все события пересылаются через один и тот же объект.

  3. через объекты событий.

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

Интерфейс Observable удобен тем, что события одного экземпляра ни как не пересекаются с событиями другого экземпляра одной и той же компоненты.

$("div.primary div.widget").on("update", function () {
    alert("update in primary block");
}

$("div.secondary div.widget").on("update", function () {
    alert("update in secondary block");
}

Два экземпляра одного и того же виджета, но расположенных в разных блоках генерируют разные события «update».

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

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

var Topic = (function () {
    var topics = {};
    return function (name) {
        var callbacks, method, topic;
        if (!name) {
            throw new Error("topic must be specified");
        }
        topic = topics[name];
        if (!topic) {
            callbacks = jQuery.Callbacks();
            topic = {
                publish: callbacks.fire,
                subscribe: callbacks.add,
                unsubscribe: callbacks.remove
            };
            topics[name] = topic;
        }
        return topic;
    };
}());

Замыкание содержит в себе список всех зарегистрированных событий. Для каждого из них создается менеджер очереди колбеков jQuery.Callbacks, который регистрирует и вызывает их.

Наружу возвращается объект с методами экземпляра jQuery.Callbacks, но именованными в соответствии с действиями, за которые они отвечают: subscribe, unsubscribe и publish.

Topic("archiveAdd").subscribe(function (id) {
    console.log("add file with id = " + id + " to archive");
});
Topic("archiveCreate").subscribe(function () {
    console.log("start archiving");
});

И в другой компоненте:

$(root).on("click", "li.file button.add", function () {
    Topic("archiveAdd").publish($(this).data("fileId"));
});
$(root).on("click", "button.create", function () {
    Topic("archiveCreate").publish();
});

В некоторых случаях может быть полезным для каждого события создать свой отдельный объект и использовать ссылку на него в компонентах

var EventObject = function () {
    var callbacks = jQuery.Callbacks();
    return {
        publish: callbacks.fire,
        subscribe: callbacks.add,
        unsubscribe: callbacks.remove
    };
};

Такая реализация может быть близка сторонникам процедурных языков, но мне кажется чужеродным для функционального стиля JavaScript.

Оставте свой комментарий

Очередь функций обратного вызова jQuery.Callbacks

В jQuery 1.7 появился новый объект для создания и управления очередью колбеков – jQuery.Callbacks. Он был предложен на рассмотрение команде разработчиков еще 6 месяцев назад. Менеджер было успешно опробован и пошел в релиз. На его основе был переписан jQuery.Deferred, который в свою очередь лежит в основе системы колбеков jQuery.ajax.

Конструктор jQuery.Callbacks можно вызывать как с оператором new, так и без него. В результате будет создан объект с несколькими методами: add(), remove(), fire(), disable() и др.

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

Вызов конструктора с различными флагами

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

var c = $.Callbacks();
c.add(function (val) {
    console.log("1: " + val);
});
c.fire("foo");
c.add(function (val) {
    console.log("2: " + val);
});
c.fire("bar");

После выполнения этого кода в консоль будет выведено:

1: foo

1: bar

2: bar

Метод add() добавляет в очередь новую функцию обратного вызова, а метод fire() по очереди вызывает все функции из очереди с указанными параметрами.

Флаг once указывает на то, что метод fire() будет выполнен только один раз для такой очереди.

var c = $.Callbacks("once");
c.add(function (val) {
    console.log("1: " + val);
});
c.fire("foo");
c.add(function (val) {
    console.log("2: " + val);
});
c.fire("bar");

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

1: foo

Флаг memory предписывает запоминать последнее значение, с которым был вызван метод fire() и автоматически вызывать вновь добавляемые колбеки с этим значением.

var c = $.Callbacks("memory");
c.add(function (val) {
    console.log("1: " + val);
});
c.fire("foo");
c.add(function (val) {
    console.log("2: " + val);
});
c.fire("bar");

Когда вызывается метод fire("foo"), то второго колбека еще нет в очереди. Но как только он будет добавлен, так сразу вызовется с параметром "foo".

1: foo

2: foo

1: bar

2: bar

Флаги можно комбинировать. Так одновременное использование once и memory даст результат аналогичный по свой сути работе jQuery.Deferred – вызов колбеков будет сделан только один раз, а новые колбеки сразу будут вызываться при их добавлении.

var c = $.Callbacks("once memory");
c.add(function (val) {
    console.log("1: " + val);
});
c.fire("foo");
c.add(function (val) {
    console.log("2: " + val);
});
c.fire("bar");

1: foo

2: foo

Если будет установлен флаг unique, то одна и та же функция обратного вызова в очередь будет добавлена только один раз.

И последний флаг stopOnFalse прерывает выполнение очереди, если какой-либо колбек вернет значение false.

Применение

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

Комментарии к заметке: 2