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

White-space, word-wrap и их друзья

Для разного контента нужны разные правила форматирования: фрагменты кода должны сохранять все переносы строк и табуляцию; текст, написанный случайным посетителем сайта, не должен «порвать» вёрстку и т.п. Форматирование можно контролировать с помощью CSS.

Свойство white-space

CSS свойство white-space описывает то, как будут обрабатываться пробельные символы внутри элемента.

 Новая строкаПробелы и табуляцияПеренос текста
normalсхлопываетсясхлопываютсяесть
nowrapсхлопываетсясхлопываютсянет
preостаётсяостаютсянет
pre-wrapостаётсяостаютсяесть
pre-lineостаётсясхлопываютсяесть

Значения pre-wrap и pre-line доступны во всех современных версиях браузеров и в IE начиная с версии 8.0.

Подробнее o white-space на w3.org

Свойство word-wrap

Изначально это свойство появилось в линейке браузеров IE и только потом перекочевало в другие браузеры, так и не появившись в спецификации CSS2. В CSS3 аналогичное поведение закреплено за свойством overflow-wrap, а word-wrap останется в качестве псевдонима.

Это свойство может принимать значения:

normal

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

break-word

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

Это свойство можно использовать для переноса строк в теге <pre> даже в старых версиях IE:

pre {
  word-wrap: break-word; /* IE 5.5-7 */
  white-space: pre-wrap; /* current browsers */
}

Подробнее o word-wrap (overflow-wrap) на w3.org

Свойство word-break

Когда нужно применить «грубую силу» и переносить любую строку в любом месте (я даже не представляю себе где это может потребоваться), то в дело вступает word-break.

.text_user-generated_yes {
  word-break: break-all;
}

Это свойство имеет больший приоритет, чем word-wrap (overflow-wrap) и будет разрывать слова даже там, где это особо и не требуется.

В браузерах на Webkit это свойство имеет ещё одно нестандартное значениеbreak-word, которое по своему действию аналогично word-wrap .

Подробнее o word-break на w3.org

Свойство hyphens

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


.text {
  -webkit-hyphens: auto;
  -moz-hyphens: auto;
  hyphens: auto;
}

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

Подробнее o hyphens на w3.org

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

Особенности микроразметки microdata

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

itemscope

Группа свойств ключ-значение.

itemtype

Тип объекта. Фактически это ссылка на страницу с описанием в свободной форме всех названий ключей, которые применимы к описываемому объекту. Этот атрибут неприменим к элементам без атрибута itemscope.

itemprop

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

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

Несколько свойств у одного элемента

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

<p itemscope itemtype="http://schema.org/Person">
  Автор:
  <a itemprop="name url" href="http://noteskeeper.ru/about/">
    Владимир Кузнецов
  </a>
</p>

Ссылка на свойства

Иногда так получает, что данные, относящиеся к размечаемому объекту, находятся за пределами «корневого» элемента. Специально для этого случая предусмотрен атрибут itemref . Он применяется к элементу с itemscope и содержит id другого DOM-элемента, где находятся остальные свойства. Можно указать через пробел идентификаторы нескольких элементов.

Например, из-за особенностей оформления страницы, автор и комментарии к статье физически не могут находиться внутри <article>.


<div itemscope itemtype="http://schema.org/Person" itemprop="author" id="author">
  <a itemprop="name url" href="http://noteskeeper.ru/about/">
    Владимир Кузнецов
  </a>
</div>

<article itemscope itemtype="http://schema.org/Article" itemref="author comments">
  <header>
    <h2 itemprop="name">Особенности микроразметки microdata</h2>
    <link itemprop="url" href="http://noteskeeper.ru/758/">
  </header>
  <div itemprop="articleBody">
    ... статья ...
  </div>
</article>

<section id="comments">
  <div itemprop="comment" itemscope itemtype="http://schema.org/UserComments">
    <div itemprop="name commentText">
      ... комментарий 1 ...
    </div>
  </div>
  <div itemprop="comment" itemscope itemtype="http://schema.org/UserComments">
    <div itemprop="name commentText">
      ... комментарий 2 ...
    </div>
  </div>
</section>

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


article
  itemType = http://schema.org/Article
  author
    person
      itemType = http://schema.org/Person
      name
        href = http://noteskeeper.ru/about/
        text = Владимир Кузнецов
      url
        href = http://noteskeeper.ru/about/
        text = Владимир Кузнецов
  comment
    usercomments
      itemType = http://schema.org/UserComments
      name = ... комментарий 1 ...
      commenttext = ... комментарий 1 ...
  comment
    usercomments
      itemType = http://schema.org/UserComments
      name = ... комментарий 2 ...
      commenttext = ... комментарий 2 ...
  name = Особенности микроразметки microdata
  url = http://noteskeeper.ru/758/
  articlebody = ... статья ...

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

Примешивать свойства с помощью itemref можно даже для элементов, которые не имеют содержимого. Например,


<article itemscope itemtype="http://schema.org/Article">
  <meta itemprop="aggregateRating" itemref="rating" itemscope
      itemtype="http://schema.org/AggregateRating">
</article>
<meta itemprop="ratingValue" content="5" id="rating">

Объект AggregateRating требует обязательного наличия свойства ratingValue, но его нельзя передать в атрибуте content. Зато можно указать ссылку на другой элемент с нужными атрибутами.

На практике, пожалуй, этот случай лучше разметить с помощью обычных вложенных элементов с соответствующими атрибутами.

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

Боремся с большой вложенностью анонимных колбеков

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

Вот несколько советов, как улучшить читаемость такого кода.

Декомпозиция

Самым очевидным решением будет использовать в качестве колбеков не анонимные функции, а именованные. Да, простая декомпозиция может значительно повысить читаемость кода.

Возможно, в именованные функции стоит вынести только бизнесс-логику, а обработку ошибок от предыдущей функции оставить в анонимном колбеке.

Step

Когда столкнулся с тем, что основная часть бизнесс-логики у меня оказывалась глубоко закопана в «ифах» и колбеках, то первой, что попалось мне на глаза, оказалась библиотека step.

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


var step = require("step");

function findRecentPostsOfActiveUsers(callback) {
  step(
    // получаем список активных пользователей
    function () {
      var nextStep = this;
      User.find({active: true}, nextStep);
    },
    // получаем последнюю публикацию каждого из ранее найденных пользователей
    function (err, users) {
      if (err) throw err;
      var group = this.group();
      users.forEach(function (user) {
        Post.findOne({userId: user.id})
          .sort({date: -1})
          .exec(group());
      });
    },
    // на последнем шаге передаем найденые публикации в колбек
    // function (err, posts) { … }
    callback
  );
}

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

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

Async

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

When

Отдельно хочу отметить библиотеку when, в которой реализован немного другой подход, чем в step и async. Управление последовательностью выполнения кода осуществляются на базе отложенных объектов.

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

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

Замечание

Описанные библиотеки можно и нужно применять не только на сервере, но и в браузере.

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

Тестирование promise с помощью mocha

В своём «домашнем» проекте для тестирования кода я использую фреймворк mocha . В тестах все утверждения проверяются библиотекой should.js , которая выбрасывает соответствующие исключения, отлавливаемые mocha.

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


describe("Listener", function () {
    it("should reject data", function (done) {
        var connection = new MockStream();
        var promise = listener(connection);
        connection.write("foo bar baz");
        connection.end();

        promise.then(
            function (obj) {
                done(new Error("unexpected fulfill"));
            },
            function (obj) {
                obj.should.be.a("object").and
                  .have.property("valid", false);
                done();
            }
        );

    });
});

В данном тесте я ожидаю, что promise будет отклонён с объектом, в качестве параметра, имеющим поле valid равное значению false. Если утверждение не будет соответствовать действительности, то тест не завершится естественным образом (т.е. вызов done() , который идет за утверждением, не будет выполнен).

Благо, каждый вызов метода then() возвращает другой promise-объект. Он выполняет или отклоняется так же как и его «предок», но в параметры колбеков будут передаваться совершенно другие значения. Этим можно воспользоваться для корректного завершения теста.


describe("Listener", function () {
    it("should reject data", function (done) {
        var connection = new MockStream();
        var promise = listener(connection);
        connection.write("foo bar baz");
        connection.end();

        promise.then(
            function (obj) {
                // throw new Error("unexpected fulfill");
                return new Error("unexpected fulfill");
            },
            function (obj) {
                obj.should.be.a("object").and
                  .have.property("result", false);
            }
        ).then(done, done);

    });
});

При любом изменении состояния исходного promise-объекта будет вызываться done(). Отличаться будет только параметр, с которым он будет вызван:

  • объект ошибки в случае неверного состояния или выбрасывания исключения;
  • undefined при удачном завершении теста.

Именно такие значения параметра определяют состояние теста в mocha.

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