Свежие заметки :: Хранитель заметок

noteskeeper.ru

Персональный журнал для заметок Владимира Кузнецова

Свежие заметки

Структура HTML5 — div, section и article

В формате разметки HTML5 появились новые элементы, которые служат заменой для обычных блочных элементов с атрибутами class и id, например <div class="article"> или <div id="page">.

Рассмотрим три элемента, которые легко можно спутать друг с другом:

  • div — «базовый контейнер», который мы все знаем и любим. Это обычный блочный элемент без какого-либо семантического значения.
  • section — «документ или раздел приложения». Обычно содержит верхний (header) и нижний (footer) колонтитулы. Это несколько схожих по смыслу статей, подраздел одной большой статьи, главная часть страницы (например, раздел новостей) или страница интерфейса с закладками.
  • article — «независимая часть документа или сайта». Эта часть должна иметь смысл вне зависимости от остального содержания. Например, это может быть статья в блоге или форуме, комментарий. Так же как и section внутри этого тега могут размещать верхний и нижний колонтитулы.

Описание на whatwg.org

Разница между div, section и article

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

Элемент section

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

Так же этот элемент служит для разбивки текста на разделы.

Элемент article

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

Что выбрать?

Если проводить аналогию с HTML4, то можно сравнить эти теги с p и blockquote. Оба тега — блочные элементы, но blockquote, как разновидность p, имеет значение «блок цитируемого текста». То же самое и с section и article: тег section обозначает близкий по смыслу текст, а тег article обозначает осмысленную часть этого текста.

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

Чтобы определиться, какой из элементов выбрать, можно использовать алгоритм:

  1. Будет ли содержимое иметь осмысленное значение само по себе, например, при публикации в RSS-потоке? Если да, то выбираем article
  2. Если части содержимого объединены общим значением, то выбираем section
  3. Наконец, если нет никакого семантического значения, то выбираем div

Элемент section, за редким исключением, не должен использоваться, если у него нет заголовка. Если при автоматическом построении содержания документа будут встречаться не именованные разделы, то, вероятно, разметка сделана не верно. Однако наличие не именованных элементов nav и aside может быть вполне типичным явлением.

Итак, элемент section не стоит использовать если:

  1. требуется блочный элемент для декорирования. Это функция элемента div
  2. в этом месте для разметки содержимого больше по смыслу подходят элементы article, aside или nav
  3. у раздела нет естественного заголовка

Элементы section и article используются аналогично div в HTML4. Они не должны встречаться внутри p, blockquote или address.

Пример использования

<header>
    <h1>Название сайта</h1>
    <nav>
        <ul>
            <li><a href=”page1.html”>Страница 1</a></li>
            <li><a href=”page2.html”>Страница 2</a></li>
            <li><a href=”page3.html”>Страница 3</a></li>
        </ul>
    </nav>
</header>
<section>
    <h2>Свежие статьи</h2>
    <article>
        <h2>Заголовок статьи 1<h2>
        <p>Текст статьи</p>
    </article>
    <article>
        <h2>Заголовок статьи 2<h2>
        <p>Текст статьи</p>
        <aside>Дополнительная информация, относящаяся к статье 2</aside>
    </article>
</section>
<aside>
    <section>
        <h3>Blogroll</h3>
    </section>
    <section>
        <h3>Реклама</h3>
    </section>
</aside>
<footer>
    <p>Копирайты<p>
</footer>

По мотивам http://oli.jp/2009/html5-structure1/

Исправляем дефект CSS-селектора «child»

,

В спецификации CSS 2.1 есть замечательное правило «child selector». Но оно пока не получило популярности из-за того, что не работает в IE6. Первая и, к сожалению, не без дефектов реализация в этой линейке браузеров была сделана в IE7.

Однако есть трюк, который эмулирует действие этого правила.

<div id="article">
    <h1>Header</h1>
    <p>First level paragraph</p>
    <div>
        <p>Paragraph inside div element</p>
    </div>
</div>
<p>Copyright info</p>

p {
    font-size: 14px;
}
#article > p {
    font-size: 18px;
    font-weight: bold;
}

Всем параграфам документа назначается высота шрифта 14px. Параграфы, которые являются непосредственными потомками блока с идентификатором article, выделяется жирным шрифтом и высотой шрифта 18px.

Для IE используем следующие правила.

#article p {
    font-size: 18px;
    font-weight: bold;
}
#article * p {
    font-size: 14px;
    font-weight: normal;
}

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

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

Sizzle задом наперед

В состав jQuery входит очень мощная библиотека Sizzle, которая отвечает за получение списка DOM-элементов, соответствующих заданным условиям. В ней реализованы все селекторы CSS3 и некоторые другие, которые не включены в спецификацию.

Хотя в некоторых современных браузерах у DOM-элементов есть специальные методы querySelector и querySelectorAll, которые реализуют этот функционал, тем не менее актуальность этой библиотеки остаётся неизменной из-за поддержки старых браузеров и браузеров, в которых нет эти методов.

Чтобы полностью раскрыть скоростной потенциал Sizzle нужно понимать, как на самом деле происходит фильтрация элементов документа.

Хорошим тоном при составлении правила будет первым селектором использовать получение элемента по атрибуту id, так как эта функция работает очень быстро и реализована во всех браузерах. Как вариант, можно задавать контекст поиска, если он известен, что сильно сокращает количество элементов, которые придется перебирать.

$("#data div.frame p span.dashed");

$("#data").find("div.frame p span.dashed");

$("div.frame p span.dashed", $("#data"));

В каждом правиле (за исключением поиска по id) стоит указывать тег, которому оно соответствует. Функция поиска по тегу тоже присутствует по всех браузерах. Так запрос

$("div.frame p span.dashed");

выполнится быстрее чем

$(".frame p .dashed");

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

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

$("div.frame p span.dashed");

В этом примере сначала будут найдены все элементы span в документе. Затем отфильтрованы элементы, которые содержат класс dashed. После этого для каждого найденного элемента будет предпринята попытка найти у него предка с тегом p, затем предка с тегом div и классом frame.

Такой, казалось бы, не очевидный подход позволяет резко сократить количество операций на поиск элементов.

Как и везде, в алгоритме Sizzle есть исключение. Запрос типа $("#data span.dashed") будет обработан особым образом — селекторы в этом случае традиционно применятся слева направо.

Блоки с выноской на поля

,

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

Пример блоков с выноской

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

Более гибкий способ — это использование отрицательных значений margin. В этом случае достаточно будет определить 1 класс и добавлять его тем блокам, котором это необходимо.

.block {
    margin-left: -15px;
    margin-right: -15px;
    padding-left: 15px;
    padding-right: 15px;
    position: relative;
    zoom: 1;
}

Величина отступа с отрицательным значением задается в margin, и тут же компенсируется таким же положительным значением в padding.

Для IE 6 и 7 добавляются еще position: relative и свойство zoom: 1, которое включает блоку hasLayout. Замечу, что у родительского блока тоже обязательно должен быть включен hasLayout для корректного отображения.

Сжатие файлов на томах HFS+ в Snow Leopard

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

Текущий интерфейс Файндера или какой-либо другой системной программы с GUI не позволяет управлять компрессией. Однако, Apple все же предоставила способ сжимать и распаковывать файлы через командную строку.

У команды ditto в 10.6 появилось несколько опций. Например,

--hfsCompression

When copying files or extracting content from an archive,if the destination is an HFS+ volume that supports compression, all the content will be compressed if appropriate. This is only supported on Mac OS X 10.6 or later, and is only intended to be used in installation and backup scenarios that involve system files. Since files using HFS+ compression are not readable on versions of Mac OS X earlier than 10.6, this flag should not be used when dealing with non-system files or other user-generated content.

Значит, чтобы включить сжатие HFS+, нужно всего лишь выполнить в командной строке

ditto --hfsCompression [src] [dst]

В качестве [src] указывается файл или папка, которая будет сжиматься, а [dst] — путь, где будут размещены сжатые файлы. Команда не заменяет файлы, а создает их сжатые копии в другом месте.

Внимание! Нельзя сжимать файлы, если необходимо подключать этот том в предыдущих версиях Mac OS X. Старые версии не повредят такие файлы, так как они просто не будут доступны для чтения.

На практике выходит не плохая экономия места. Например, сжав папку Developer с загруженной документацией, удалось высвободить около 3Гб (это около 40% от исходного объема) на диске.

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

Замечательно, что программы для Mac OS X с минимальными затратами для разработчика можно перевести на любой язык мира. Только не всегда интерфейс с фразами на различных языках выглядит одинаково гармонично. Часто слова приходится сокращать, если они не помещаются в отведенные им места.

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

Чтобы раз и навсегда выбрать предпочитаемый язык для какого-то конкретного приложения можно добавить соответствующее свойство через команду defaults. Во всей системе принято использовать свойство AppleLanguages для задания предпочитаемой последовательности языков.

defaults write -g AppleLanguages -array ru en de

Эта команда задает последовательность «Русский», «Английский», «Немецкий» для всей системы. Аналогичный эффект достигается в пульте «Язык и текст» настроек.

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

defaults write com.apple.iTunes AppleLanguages -array en

Так мы указываем, что iTunes должен использовать только английский. Хочу подчеркнуть, что это свойство имеет тип «массив» даже если указывается только один язык.

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

Предпочтения удаляются командой

defaults delete com.apple.iTunes AppleLanguages

JSONP для инициализации страницы

Блоки с динамическим содержимым уже давно принято обновляться асинхронными запросами на сервер. А вот для наполнения их начальными данными эта техника не слишком хорошо подходит из-за видимой задержки в появлении внешнего оформления блока и самого содержимого блока. Чтобы минимизировать эту задержку данные уже должны находиться на странице во время ее загрузки. Очевидно, что при этом HTML захламляется вкраплениями JavaScript, что не слишком хорошо. Компромиссным вариантом может быть загрузка данных с помощью тега script.

Предположим, что на сервере есть контроллер, который отдает модель данных в виде JSON. Пусть URL, по которому можно получить эти данные, будет http://example.com/data/?page=1, а сами данные

{sites: [
    {id: 1, name: "Yandex.ru", url: "http://yandex.ru/"},
    {id: 2, name: "Google", url: "http://google.com/"},
    {id: 3, name: "NotesKeeper", url: "http://noteskeeper.ru/"}
]}

Этот URL используется в асинхронном запросе, чтобы получить новую страницу блока. Если его подставить в тег script, то он, конечно же, будет загружен и исполнен, но возникнет синтаксическая ошибка, так как объект не был использован в операции присвоения или при вызове функции.

Добавим в параметры к URL еще один http://example.com/data/?page=1&callback=App.render и модифицируем контроллер на сервере так, чтобы этот параметр (при его наличии) подставлялся в выходные данные

App.render( {sites: [
    {id: 1, name: "Yandex.ru", url: "http://yandex.ru/"},
    {id: 2, name: "Google", url: "http://google.com/"},
    {id: 3, name: "NotesKeeper", url: "http://noteskeeper.ru/"}
]} );

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

Контроллер интерфейса может иметь, например, такую структуру

var App = (function () {
    // скрытые переменные приложения
    return {
        // публичные методы приложения
        render: function (model) {
            // действия, которые можно выполнить до полной загрузки документа
            $(function () {
                // изменения в документе, который выполняются только
                // после его полной загрузки
                App.update(model);
            });
        },
        update: function (model) {
            // обновляем содержимое блока
        }
    };
}());

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

Эта техника и называется JSONP (JSON with padding). Основное применение она нашла в загрузке данных сторонних сервисов, например, таких как Flickr, Yahoo, Google и многих других. Из-за того, что асинхронные запросы для других доменов блокируются политикой безопасности браузера, использование этой техники наряду с iframe является, пожалуй, единственным возможным вариантом.

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

Закомментировать код

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

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

/*

alert("Error!!!");

// */

Этот блок сейчас закомментирован. Но добавив всего-лишь один слеш в первой строке комментария мы раскомментируем весь блок целиком.

//*

alert("Error!!!");

// */

Этот трюк сильно упрощает включение и выключение целых блоков кода.

Инверсия управления

Инверсия управления (Inversion of Control) — принцип программирования, который уменьшает связанность между программными компонентами. Разрыв зависимости достигается благодаря некому абстрактному интерфейсу, через который один компонент взаимодействует с другим. Отличной реализацией такого абстрактного интерфейса может быть система событий. Один компонент генерирует события, а другой отслеживает их появление.

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

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

Есть задача сделать список объектов и панель инструментов для работы с этими объектами.

Ни один объект в списке не выбран

Когда в списке выбран один объект, то его можно отредактировать или удалить.

В списке выбран один объект

Если выбрано несколько объектов, то их можно только удалить.

В списке выбрано несколько объектов

В списке есть дополнительная «галочка» для выбора всех объектов в списке.

В списке выбраны все объекты

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

Для связи компонент списка и панели инструментов важно отслеживать 3 состояния списка:

  • ни один объект не выбран (selected-zero)
  • выбран один объект (selected-one)
  • выбрано несколько объектов (selected-many)

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

var btnEdit = $("#toolbar input.btn-edit"),
    btnDelete = $("#toolbar input.btn-delete");

$("#list")
    .bind("selected-zero", function () {
        btnEdit.attr("disabled", true);
        btnDelete.attr("disabled", true);
    })
    .bind("selected-one", function () {
        btnEdit.attr("disabled", false);
        btnDelete.attr("disabled", false);
    })
    .bind("selected-many", function () {
        btnEdit.attr("disabled", true);
        btnDelete.attr("disabled", false);
    });

В списке перехватываем изменение состояние «галочек».

var list = $("#list"),
    checkboxes = list.find("tbody input:checkbox");

checkboxes.bind("change click", function (e) {
    $(this).closest("tr").toggleClass("sel", this.checked);
    // блокируем всплытие события click, так как уже отработали его
    e.stopPropagation();
    // оповещаем подписчиков, что изменилось состояние списка
    notify();
}).trigger("change");

«Галочка» в шапке списка передает свое состояние всем остальным

list.find("thead input:checkbox").bind("change click", function () {
    checkboxes.attr("checked", this.checked).trigger("change");
});

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

list.find("tbody tr").bind("click", function () {
    var chb = $("input:checkbox", this);
    chb.attr("checked", !chb.get(0).checked).trigger("change");
});

Диспетчер событий.

function notify() {
    var count = checkboxes.filter(":checked").length,
        type = count === 0 ? "selected-zero" : count === 1 ? "selected-one" : "selected-many";
    list.trigger(type);
}

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

Подведем итог:

  1. Связываем несколько программных компонент не прямыми вызовами их методов, а через абстрактный интерфейс событий.
  2. Генерируем столько событий, сколько нам требуется для описания состояния компонента.
  3. Исключаем дублирование кода с помощью синтетических DOM-событий.

Запомнить порядок сортировки jQuery UI Sortable

,

В сервисе Google «Вопросы и ответы» одним пользователем был задан этот вопрос и, к моему удивлению, очень долго никто не давал на него ответа. А на самом деле эта задача очень просто решается благодаря встроенному методу serialize.

Сейчас я хочу разобрать только основную функцию программы:

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

var root = $('#items');

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

$('> *', root).each(function (index) {
    this.id = 'item-' + index;
});

Инициализируем виджет Sortable и перехватываем событие update, чтобы сохранять состояние списка.

root.sortable({
    'update': function (event, ui) {
        var order = $(this).sortable('serialize');
        $.cookies.set('sortable', order);
    }
});

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

var c = $.cookies.get('sortable');
if (c) {
    $.each(c.split('&'), function () {
        var id = this.replace('[]=', '-');
        $('#' + id).appendTo(root);
    });
}

Пробегаемся по всем элементам, сохраненным в cookie, и перемещаем их в конец списка. Если был сохранен порядок для всех элементов, то они естественным образом займут свои места. Если для каких-то элементов позиция не сохранялась, то они «всплывут» наверх списка.

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