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

Ещё один вариант загрузки набора SVG-изображений

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

Цепочка проб и ошибок привела нас к такой схеме:

  1. Преобразуем SVG-файл в строку текста и присваиваем её какой-либо переменной.
  2. Объединяем JavaScript файл, который мы получили на предыдущем этапе, с другими JS-файлами, которые подключаются в <head>.
  3. В до первого использования SVG добавляем пустой элемент — плейсхолдер и вставляем в него содержимое SVG-строки.

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

<svg xmlns="http://www.w3.org/2000/svg" class="icon_circle">
  <use xlink:href="#circle"></use>
</svg>

Опишу все проделанные шаги подробнее.

Конвертирование SVG в JS

Этой задачей занимается Grunt.

grunt.registerTask('elements', 'Transform a SVG sprites to a JS file',
  function () {
    var LINE_LENGTH = 100, svg = [], i, l, content;

    content = grunt.file.read('assets/images/elements.svg');
    content = content.replace(/'/g, "\\'");
    content = content.replace(/>\s+</g, "><").trim();
    l = Math.ceil(content.length / LINE_LENGTH);

    for (i = 0; i < l; i++) {
      svg.push("'" + content.substr(i * LINE_LENGTH, LINE_LENGTH) + "'");
    }

    grunt.file.write('assets/_/js/elements.js',
      'var SVG_SPRITE = ' + svg.join('+\n') + ';');
  }
);

Из файла assets/images/elements.svg получается файл assets/_/js/elements.js.

Конкатенация

Файл elements.js должен быть загружен в <head> Наверняка у вас есть ещё несколько скриптов, которые тоже требуется загружать в начале страницы. Их можно объединить между собой чтобы уменьшить количество HTTP-запросов к серверу.

Добавление на страницу

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

<div id="elements-placeholder"
  style="border: 0; clip: rect(0 0 0 0); overflow: hidden;
    margin: -1px; padding: 0; position: absolute;
    width: 1px; height: 1px;"></div>

Плейсхолдер нужен для того, чтобы безопасно модифицировать DOM-дерево в процессе загрузки страницы. У него должен быть уникальный id. Я ещё добавил некоторые inline-стили, чтобы защитить страницу от эффекта «упячки», если контуры случайно окажутся не помещены в <defs>.

Заполняем плейсхолдер.

<script>document.getElementById("elements-placeholder").innerHTML = SVG_SPRITE;</script>

Замечания

  1. При формировании JS-файла для простоты полагается, что будет создана глобальная переменная SVG_SPRITE. Чтобы не загрязнять глобальную область, я рекомендую сохранить содержимое SVG в пространство имён приложения.
  2. Имена файлов жёстко прописаны в Grunt-задаче. Есть смысл задачу сделать конфигурируемой, если у вас в проекте несколько наборов SVG-пиктограмм.

Демонстрация

У нас в компании есть проект-шаблон, в котором можно детально посмотреть эту технику подключения SVG.

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

Шпаргалка по созданию RESTful API

RESTful API может создаваться не только для сторонних сервисов. Он может использоваться одностраничными приложениями для работы с бэк-эндом. Вот несколько основных моментов, которые нужно знать при проектировании интерфейса.

URLs и действия

Ключевым принципом REST является деление вашего API на логические ресурсы. Управление этими ресурсами происходит с помощью HTTP-запросов с соответствующим методом — GET, POST, PUT, PATCH, DELETE.

Ресурс должен описываться существительным во множественном числе. Действия над ресурсами, обычно, определяются стратегией CRUD и соответствуют HTTP-методам следующим образом:

  • GET /api/users — получить список пользователей;
  • GET /api/users/123 — получить указанного пользователя;
  • POST /api/users — создать нового пользователя;
  • PUT /api/users/123 — обновить все данные указанного пользователя;
  • PATCH /api/users/123 — частично обновить данные пользователя;
  • DELETE /api/users/123 — удалить пользователя.

Если ресурс существует только в контексте другого ресурса, то URL может быть составным:

  • GET /api/posts/9/comments — получить список комментариев к записи №9;
  • GET /api/posts/9/comments/3 — получить комментарий №3 к записи №9.

Когда действие над объектом не соответствует CRUD операции, то его можно рассматривать как составной ресурс:

  • POST /api/posts/9/like — отметить запись №9 как понравившуюся;
  • DELETE /api/posts/9/like — снять отметку «понравилось» с записи №9.

Действия по созданию и обновлению ресурсов должны возвращать ресурс

Методы POST, PUT или PATCH могут изменять поля ресурса, которые не были включены в запрос (например, ID, дата создания или дата обновления). Чтобы не вынуждать пользователя API выполнять ещё один запрос на получение обновлённых данных, такие методы должны вернуть их в ответе.

Фильтры, сортировка и поиск

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

  • GET /api/users?state=active — список активных пользователей;
  • GET /api/tasks?state=open&sort=priority,-created_at — список невыполненных задач, отсортированных по приоритету и дате создания (сперва новые задачи).

Постраничная навигация

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

Пример заголовка:

Link: <http://domain.tld/api/users?page=5&per_page=100>; rel="next",
    <http://domain.tld/api/users?page=3&per_page=100>; rel="prev",
    <http://domain.tld/api/users?page=1&per_page=100>; rel="first",
    <http://domain.tld/api/users?page=10&per_page=100>; rel="last"

Возможные значения rel:

  • next — следующая страница результатов;
  • prev — предыдущая страница результатов;
  • first — первая страница результатов;
  • last — последняя страница результатов.

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

Переопределение HTTP-метода

Для совместимости с некоторыми серверами или клиентами, которые не поддерживают другие HTTP-методы кроме GET и POST, может быть полезным их эмуляция. Значение метода передаётся в заголовке X-HTTP-Method-Override, а сам он выполняется как POST-метод. GET-запросы не должны менять состояние сервера!

Коды HTTP-статуса

  • 200 OK — ответ на успешный запрос GET, PUT, PATCH или DELETE.
  • 201 Created — ответ на POST запрос, в результате которого произошло создание нового объекта. Ответ так же должен сопровождаться заголовком Location, указывающий на URL ресурса.
  • 204 No Content — ответ на успешно выполненный запрос, который ничего не возвращает (например, DELETE).
  • 404 Not Found — запрашиваемый объект не найден.
  • 500 Internal Server Error — ошибка на сервере.

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

Метаданные

Любые данные, которые лишь опосредованно относятся к результату запроса, но могут быть полезны клиенту, стоит отдавать в виде HTTP-заголовков. Создание обёрток над данными ломает совместимость с принципами REST.

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

Миксины для модификаторов блоков

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

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

Примеры

// namespace
@block: ~".cool-widget";

// block
@{block} {
  // primary styles
  position: relative;
  &__title {
    font: bold 20px/1 sans-serif;
  }
  &__body {
    font: normal 14px/1.4 sans-serif;
    color: white;
  }
}

Добавим «тему», изменяющую оформление некоторых элементов.

@{block} {
  // red modifier
  &_red {
    @{block} {
      .cool-widget_red();
    }
  }
}

// red theme
.cool-widget_red() {
  &__title {
    color: red;
  }
  &__body {
    background: red;
  }
}

К одному модификатору можно подмешать несколько миксинов.

@{block} {
  // blue modifier
  &_blue {
    @{block} {
      .cool-widget_big-title();
      .cool-widget_blue();
    }
  }
}

А так же можно создать стили для «глобального» модификатора.

@{block} {
  // global modifiers
  .ie6 & {
    .cool-widget_ie();
  }
  .no-js & {
    .cool-widget_no-js();
  }
}

Исходный LESS-файл и результат — https://gist.github.com/mistakster/619b94f50ad7b6c861ba

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

Автоматизируем запуск QUnit тестов

QUnit очень старый фреймворк для тестирования браузерного JavaScript. Чтобы запустить тесты, нужно создать небольшой HTML-файл с несколькими div-ами, стилями и скриптами QUnit и, собственно, тестовыми скриптами. Затем этот файл можно открывать в разных браузерах и смотреть отчёт о выполнении тестов.

Запуск этих тестов можно поручить PhantomJS. Для этого нужно воспользоваться скриптом QUnit PhantomJS Runner.

$ phantomjs runner.js test/index.html

Если у вас используется Grunt для запуска различных задач, то для него есть плагин grunt-contrib-qunit. Он так же использует PhantomJS для прогона тестов.

grunt.initConfig({
  qunit: {
    all: ['test/index.html']
  }
});
Оставте свой комментарий

Декомпозиция блоков в стиле БЭМ

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

/* Плохой пример! Не повторяйте такого */
.block {
    &__element {
    }
    &__child {
        color: black;
    }
    &__element_active &__child {
        color: red;
    }
}

Если приглядеться внимательно к получившейся конструкции, то можно найти аналогию с модификатором блока. Так оно и есть. Опишем элемент как новый блок с именем block-element и всё встанет на свои места.

/* Хороший пример. Декомпозиция упростила
   взаимоотношения между элементами */
.block {
    &__element {
    }
}
.block-element {
    &__child {
        color: black;
    }
    &_active &__child {
        color: red;
    }
}

На одних и тех же «физических» DOM-элементах мы разместили два «логических» блока.

<div class="block">
    <div class="block__element block-element">
        <span class="block-element__child">Some text</span>
    </div>
</div>

Миксин для генерации Блока

<article class="block">
    <h1 class="block__title">
        <span class="block__title-wrap">My cool title</span>
    </h1>
    …
</article>

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

.cool-title(@color: yellow) {
    font-size: normal 24px/1 sans-serif;
    &-wrap {
        background: @color;
    }
}

.block {
    &__title {
        .cool-title();
    }
}

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

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

.cool-title(@color: yellow) {
    font-size: normal 24px/1 sans-serif;
    &__wrap {
        background: @color;
    }
}

.block-title {
    .cool-title();
}

Опять у нас на одних и тех же DOM-элементах прекрасно уживаются два блока.

<article class="block">
    <h1 class="block__title block-title">
        <span class="block-title__wrap">My cool title</span>
    </h1>
    …
</article>
Комментарии к заметке: 2