Заметки за 2011 год (страница 2)

Работа с тегами в Git

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

Создание тега

  1. Помечаем локальный коммит

    git tag 12345
    
  2. Отправляем его во внешний репозиторий

    git push origin 12345
    

Удаление тега из репозитория

  1. Удаляем тег локально

    git tag -d 12345
    
  2. Удаляем его во внешнем репозитории

    
    git push origin :refs/tags/12345
    
  3. Оповещаем каким-либо способом коллег, чтобы они сделали у себя команду

    
    git fetch --tags
    

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

Перемещение тега на другой коммит

  1. Принудительно перезаписываем тег

    
    git tag -f 12345
    
  2. Отправляем во внешний репозиторий с принудительной перезаписью

    
    git push --force origin 12345
    
  3. Оповещаем коллег как и в случае удаления тега.

Дополнение

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

git tag 12345 6ff87c4664981e4397625791c8ea3bbb5f2279a3

Отправить во внешний репозиторий все теги текущей ветки можно одной командой.

git push --tags
Комментарии к заметке: 4

Оглавление HTML5 документа

Механизм построения оглавления (outline) страницы базируется на тегах, используемых при разметке документа. Например, главная страница моего блога может иметь такое оглавление:

  1. Свежие заметки :: Хранитель заметок
    1. Свежие заметки
      1. Убираем неоднородности на повторяющейся текстуре
      2. Deferred Object
      3. Возвращаясь к проверке типа данных
      4. Тень у полей ввода в мобильном Safari
        1. Очень простое решение
      5. Таблица без таблицы или display: table-cell для всех браузеров
    2. Навигация по сайту
      1. Облако тегов
      2. Категории
      3. Ссылки
    3. Архив заметок по месяцам

Только два типа тегов влияют на оглавление страницы: заголовки (h1-h6 и hgroup) и структурные теги (section, article, aside и nav).

Влияние заголовков на построение оглавления


<div>
    <h1>Свежие заметки :: Хранитель заметок</h1>
    <!-- начинаем новый подраздел -->
    <h2>Свежие заметки</h2>
    <!-- начинается статья -->
    <h3>Убираем неоднородности на повторяющейся текстуре</h3>
    <!-- статья про неоднородности и как их убрать -->
    <p>Повторяющиеся текстуры бумаги или ткани могут быть с
        разными неоднородностями по площади.</p>
    ...
    <!-- статья закончилась и начинается новая -->
    <h3>Deferred Object</h3>
    <!-- статья про отложенные объекты -->
    <p>Термин «отложенный объект» тесно связан с событийной
        моделью создания компонент и модулей приложения.</p>
    ...
    <h3>Возвращаясь к проверке типа данных</h3>
    ...
    <h3>Тень у полей ввода в мобильном Safari</h3>
    ...
    <h4>Очень простое решение</h4>
    ...
    <h3>Таблица без таблицы или display: table-cell
        для всех браузеров</h3>
    ...
    <!-- раздел свежих заметок закончился и начинается новый раздел -->
    <h2>Навигация по сайту</h2>
    <h3>Облако тегов</h3>
    <ul>
        <li class="rank-9">jquery</li>
        <li class="rank-6">ui</li>
        <li class="rank-4">utility</li>
        <li class="rank-4">hint</li>
        <li class="rank-4">css</li>
    </ul>
    <h3>Категории</h3>
    <ul>
        <li>Вёрстка</li>
        <li>JavaScript</li>
        <li>Apple</li>
        <li>Разное</li>
    </ul>
    <h3>Ссылки</h3>
    ...
    <h2>Архив заметок по месяцам</h2>
    ...
</div>

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

Учет разделов при построении оглавления

Добавим в разметку страницы некоторые структурные элементы.


<div>
    <h1>Свежие заметки :: Хранитель заметок</h1>
    <section>
        <h2>Свежие заметки</h2>
        <article>
            <h3>Убираем неоднородности на повторяющейся текстуре</h3>
            <p>Повторяющиеся текстуры бумаги или ткани могут быть с
                разными неоднородностями по площади.</p>
            ...
        </article>
        <article>
            <h3>Deferred Object</h3>
            <p>Термин «отложенный объект» тесно связан с событийной
                моделью создания компонент и модулей приложения.</p>
            ...
        </article>
        <article>
            <h3>Возвращаясь к проверке типа данных</h3>
            ...
        </article>
        <article>
            <h3>Тень у полей ввода в мобильном Safari</h3>
            ...
            <h4>Очень простое решение</h4>
            ...
        </article>
        <article>
            <h3>Таблица без таблицы или display: table-cell
                для всех браузеров</h3>
            ...
        </article>
    </section>
    <nav>
        <h2>Навигация по сайту</h2>
        <section>
            <h3>Облако тегов</h3>
            <ul>
                <li class="rank-9">jquery</li>
                <li class="rank-6">ui</li>
                <li class="rank-4">utility</li>
                <li class="rank-4">hint</li>
                <li class="rank-4">css</li>
            </ul>
        </section>
        <section>
            <h3>Категории</h3>
            <ul>
                <li>Вёрстка</li>
                <li>JavaScript</li>
                <li>Apple</li>
                <li>Разное</li>
            </ul>
        </section>
        <section>
            <h3>Ссылки</h3>
            ...
        </section>
    </nav>
    <nav>
        <h2>Архив заметок по месяцам</h2>
        ...
    </nav>
</div>

Фактически структура оглавления ни сколько не поменялась. Но теперь разделы заданы явно с помощь тегов section, article, nav.

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

В спецификации HTML5 указано:

Sections may contain headings of any rank, but authors are strongly encouraged to either use only h1 elements, or to use elements of the appropriate rank for the section’s nesting level.

Разделы могут содержать заголовки любого уровня. Но авторам настоятельно рекомендуется использовать только элементы h1 или элементы, соответствующие уровню вложенности раздела.

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

Смешанный подход

Неявные разделы могут появляется внутри явных разделов (но не наоборот).


<div>
    <h1>Свежие заметки :: Хранитель заметок</h1>
    <section>
        <h2>Свежие заметки</h2>
        <article>
            <h3>Тень у полей ввода в мобильном Safari</h3>
            ...
            <h4>Очень простое решение</h4>
            ...
        </article>
    </section>
</div>

Этот фрагмент документа, очевидно, выдаст следующую структуру:

  1. Свежие заметки :: Хранитель заметок
    1. Свежие заметки
      1. Тень у полей ввода в мобильном Safari
        1. Очень простое решение

Заголовок h4 образует неявный раздел внутри статьи, заданной явным образом тегом article.

Разделы без названия

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

Оставлять же без названия разделы section и article не стоит. Наличие такого раздела, например, может быть связано с не правильным выбором тега между div, section и article . Если для блока нельзя придумать заголовок, то его стоит просто разметить с помощью тега div.

Кстати говоря, элемент body так же получает название из ближайшего заголовка, а не из содержимого тега title , как можно предположить. И если такого заголовка не окажется, то документ останется без названия. Это серьезная проблема и таких ситуаций следует избегать, планируя разделы документа.

<body>
    <article>
        <h1>Blog post title</h1>
        <p>Blog post content</p>
    </article>
</body>

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

hgroup

Элемент hgroup может содержать только заголовки и его назначение – это убрать все заголовки из оглавления кроме заголовка самого высокого уровня.

Инструменты и документация

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

Убираем неоднородности на повторяющейся текстуре

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

Допустим у нас имеется повторяющаяся текстура. Она прекрасно стыкуется по краям.

Повторяющаяся текстура

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

Повторяющаяся текстура не стыкуется при наложении

Скопируем слой и применим к нему фильтр «Gaussian Blur». Параметры фильтра нужно выбрать такими, чтобы исчезли все детали текстуры и остались только паразитные пятна. В моем случае радиус размытия был равен 10 (чем он будет больше, тем больше деталей останется в результате). Понизим немного яркость этому слою. Это удобно сделать с помощью Adjustment Layer, так как аналогичную операцию придётся сделать ещё раз. Поместим оба слоя в отдельную группу.

Размытая текстура

Теперь создадим новый слой и зальем его сплошным цветом, который хотим получить в результате. Так же поместим его в отдельную группу и добавим туда копию корректирующего яркость слоя. Теперь первой группе с размытой текстурой назначаем режим смешивания «Subtract» (вычитаем его из оригинальной текстуры), а второй группе со сплошным цветом – «Linear Dodge» (добавляем его к разнице).

Слои и настройки

В результате получим оригинальную текстуру, но без неоднородностей.

Текстура без неоднородностей

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

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

Таким же образом можно исправить и темную текстуру. Только нужно внести два изменения:

  1. Группы с размытым слоем и сплошным цветом меняем местами — сначала добавляем, а потом вычитаем. Это важно сделать из-за зануления отрицательных значений.
  2. Корректирующие слои становятся не нужны, так как при их использовании есть вероятность потерять детали в самых темных участках.
Оставте свой комментарий

Deferred Object

Термин «отложенный объект» тесно связан с событийной моделью создания компонент и модулей приложения. По сути, представляет собой интерфейс, с помощью которого можно в одном модуле подписываться на события другого модуля. Основной отличительной чертой deferred object является то, что если ожидаемое событие уже наступило (например, окончилась ajax-загрузка), то подписчики все равно будут уведомлены о результате, как только они добавят свои функции обратного вызова. В обычной событийной модели такого поведения не предполагается.

Для полного понимания приведу пример фабрики отложенных объектов Дугласа Крокфорда из его презентации «Act III: Function the Ultimate»


function make_promise() {
    var status = 'unresolved',
        outcome,
        waiting = [],
        dreading = [];

    function vouch(deed, func) {
        switch (status) {
            case 'unresolved':
                (deed === 'fulfilled' ? waiting : dreading).push(func);
                break;
            case deed:
                func(outcome);
                break;
        }
    }

    function resolve(deed, value) {
        if (status !== 'unresolved') {
            throw new Error('The promise has already been resolved:' + status);
        }
        status = deed;
        outcome = value;
        (deed == 'fulfilled' ? waiting : dreading)
            .forEach(function (func) {
                try {
                    func(outcome);
                } catch (ignore) {}
            });
        waiting = null;
        dreading = null;
    }

    return {
        when: function (func) {
            vouch('fulfilled', func);
        },
        fail: function (func) {
            vouch('smashed', func);
        },
        fulfill: function (value) {
            resolve('fulfilled', value);
        },
        smash: function (string) {
            resolve('smashed', string);
        },
        status: function () {
            return status;
        }
    };
}

Состояние объекта можно изменить только один раз каким-либо методом: fulfill или smash . После этого будет выполнены функции, которые были зарегистрированы методами when и fail, соответственно.

Стоит заметить, что отложенные объекты применимы только к разовым событиям. После того, как объект был установлен в какое-то состояние, изменить его уже нельзя.

Эту концепцию команда разработчиков jQuery реализовала в своем фреймворке и начала использовать при работе с AJAX. Далее я буду использовать именно эту реализацию.

Рассмотрим пример приложения, с использованием отложенных объектов: «По клику на элемент появляется селектор с мероприятиями, содержимое которого загружается из внешнего сервиса. Когда пользователь выбирает мероприятие, оно добавляется в список».

Начнем с функции, отслеживающей клики. Она ничего не знает о селекторе и списке кроме того, что они существуют и поддерживают интерфейс jQuery.Deferred.


$(function () {
    // отслеживаем нажатие элемента
    $("span.show-selector").bind("click", function () {
        var ele = $(this), select;
        // ничего не делаем, если уже есть блок с селектором
        if (ele.hasClass("busy")) {
            return;
        }
        ele.addClass("busy");

        // создаем селектор
        select = buildSelect();
        // передаем отложенный объект селектора в функцию обновления списка
        updateList(select);

        // как только в селекторе было выбрано мероприятие снимаем флаг
        select.always(function () {
            ele.removeClass("busy");
        });
    });
});

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


function buildSelect() {

    var select = $.Deferred(),
        fetcher = fetchEvents();

    // когда данные о мероприятиях будут получены, рисуем селектор
    fetcher.done(function (data) {
        var ele = [], cache = {};

        $.each(data.query.results.event, function (index, upcomingEvent) {
            ele.push('<option value="' + upcomingEvent.id + '">');
            ele.push(upcomingEvent.name);
            ele.push('</option>');
            cache[upcomingEvent.id] = upcomingEvent;
        });

        $("p.title").append('<div id="upcoming-events">' +
            '<select>' + ele.join('') + '</select>' +
            '<button class="btn-done">Done</button>' +
            '<button class="btn-cancel">Cancel</button>' +
            '</div>');

        // отслеживаем нажания на кнопки, чтобы выбрать
        // мероприятие или отменить выбор
        $("#upcoming-events").delegate("button", "click", function () {
            var upcomingEvent;
            if ($(this).hasClass("btn-done")) {
                // мероприятие было выбрно
                upcomingEvent = cache[$("#upcoming-events select").val()];
                // меняем состояние отложенного объекта
                // и передаем подписчикам информацию о мероприятии
                select.resolve(upcomingEvent);
            } else if ($(this).hasClass("btn-cancel")) {
                // выбор был отменен пользователем
                select.reject("user");
            }
        });

    });

    // ошибка при получении списка мероприятий
    fetcher.fail(function () {
        // отменяем выбор из-за сбоя связи с сервисом YQL
        select.reject("network");
    });

    // удаляем селектор, когда было выбрано какое-то мероприятие
    // или выбор был отменен по каким-то причинам
    select.always(function () {
        $("#upcoming-events").remove();
    });

    // возвращаем отложенный объект селектора
    return select;
}

Внутри она сама подписывается на изменение статуса для того, чтобы удалить селектор со страницы. Так как в общем случае deferred object предполагает двустороннюю коммуникацию, то ничего не мешает отменить выбор из вне.

Функция получения списка мероприятий запрашивает их через YQL . Сейчас она реализована в самом простом варианте без кеширования полученных результатов. Но благодаря тому, что используются унифицированный deferred object, а не функции обратного вызова в ajax-запросе, функциональность можно легко дополнить.


function fetchEvents() {
    // получаем список мероприятий через сервис YQL
    var jqXHR = $.ajax({
        url: "http://query.yahooapis.com/v1/public/yql",
        data: {
            q: 'select * from upcoming.events where tags="birthday"',
            format: 'json'
        },
        dataType: 'jsonp'
    });
    // возвращаем отложенный объект ajax-загрузки
    return jqXHR;
}

И наконец последняя функция, рисующая выбранное мероприятие, просто подписывается на событие выбора.


function updateList(select) {
    // в селекторе было выбрано какое-то мероприятие
    select.done(function (upcomingEvent) {
        // получаем элемент списка или создаем его
        var list = $("dl.list");
        if (!list.length) {
            list = $('<dl class="list"></dl>').appendTo("body");
        }

        // добавляем новую запись о мероприятии в список
        list.append('<dt>' + upcomingEvent.name + '</dt>' +
            '<dd>' + upcomingEvent.description + '</dd>');
    });
}

Посмотреть работу примера.

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

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

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

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

Возвращаясь к проверке типа данных

Об альтернативе typeof я уже писал в заметке «Проверка типа данных в JavaScript». А недавно на просторах интернета нашел ещё один аналогичный вариант такой функции.

var toType = function (obj) {
    return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase();
}

Стоить только заметить, что он примерно в 2 раза медленнее, чем вариант со slice . Всё-таки регулярные выражения не спасает даже кеширование.

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