Permalink
Find file
f9d6e26 Dec 8, 2012
@arikon @salivan @vithar
2091 lines (1579 sloc) 102 KB

Введение

Данный документ представляет собой справочное руководство по шаблонизатору BEMHTML.

В документе описаны:

  • основные особенности BEMHTML, отличающие его от других шаблонизаторов;
  • синтаксис описания входных данных BEMJSON и шаблонов BEMHTML;
  • порядок обработки входных данных и генерации HTML;
  • примеры решения типовых задач средствами BEMHTML.

Целевая аудитория документа — веб-разработчики и HTML-верстальщики, использующие БЭМ-методологию.

Предполагается, что читатель знаком с:

  • HTML;
  • JavaScript;
  • CSS;
  • БЭМ.

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

Особенности шаблонизатора BEMHTML

Привязка к БЭМ-предметной области

Шаблонизатор BEMHTML входит в связку технологий, обеспечивающих создание веб-интерфейсов в рамках БЭМ-методологии.

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

Декларативные шаблоны

Императивный подход

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

Входные данные Шаблон Результат
{
  items: [
    { text: '1' },
    { text: '2' }
  ]
}        
<ul class="menu">
    [% foreach item in items %]
        <li class="menu__item">
            [% item.text %]
        </li>
    [% end %]
</ul>
        
<ul class="menu">
    <li class="menu__item">1</li>
    <li class="menu__item">2</li>
</ul>
        

В таком шаблоне неизбежна избыточность, вызванная синтаксическими требованиями HTML: вывести открывающий тег — сгенерировать содержимое — вывести закрывающий тег. Еще выше избыточность в таблицах, списках и т.п.

Декларативный подход

Декларативный подход позволяет формулировать шаблоны как набор простых утверждений вида: тип входных данных (БЭМ-сущность) — HTML-представление (тег, атрибут, и т.п.).

Входные данные Шаблон Результат
{
    block: 'menu',
    content: [
        { elem: 'item', content: '1' },
        { elem: 'item', content: '2' }
    ]
}
        
block menu {
    tag: 'ul'
    elem item, tag: 'li'
}
        
<ul class="menu">
    <li class="menu__item">1</li>
    <li class="menu__item">2</li>
</ul>
        

Декларативность шаблонов достигается за счет того, что в BEMHTML процедура генерации HTML-элемента стандартизована и выполняется шаблонизатором. Этот же подход к выполнению преобразований данных используется в XSLT и AWK.

Язык описания шаблонов — JavaScript

BEMHTML представляет собой специализированный язык (DSL), расширяющий JavaScript.

Точнее, BEMHTML является надмножеством языка шаблонов XJST, который, в свою очередь, является надмножеством JavaScript.

Синтаксис BEMHTML предоставляет лаконичный способ записи соответствия БЭМ-сущностей и генерации HTML-элементов и атрибутов. Помимо этого, в шаблонах могут использоваться любые JavaScript-конструкции.

Язык исполнения шаблонов — JavaScript

Перед выполнением BEMHTML компилируется в оптимизированный JavaScript, который принимает BEMJSON и возвращает HTML.

Такой шаблон может выполняться как на стороне сервера, так и на стороне клиента.

Ограничения на уровне соглашений

Разработчики BEMHTML стремились сделать его максимально гибким инструментом, поэтому в BEMHTML не предусмотрено технологических ограничений на операции, выполняемые в шаблонах. Фактически, в BEMHTML-коде возможно всё, что возможно в JavaScript.

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

Основные понятия

Входные данные: BEMJSON

Поскольку BEMHTML основан на JavaScript, в качестве формата представления БЭМ-дерева выбран JSON с набором дополнительных соглашений о представлении БЭМ-сущностей — BEMJSON.

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

Поэтому структура входного БЭМ-дерева должна быть ориентирована на представление (view), когда при генерации HTML-дерева не потребуется изменений в наборе и порядке блоков и элементов. Приведение БЭМ-дерева к такому развернутому виду должно производиться на уровне бэкенда, предшествующего шаблонизатору. Иллюстрацией view-ориентированного формата данных может служить пример френдленты, разобранный в разделе ((#privedenievxodnyxdannyxkformatuorientirovannomunapredstavlenie Приведение данных к формату, ориентированному на представление)).

В то же время детали организации HTML-страницы, которые являются зоной ответственности верстальщика, должны определяться только на уровне шаблонизатора. Пример такого решения приведен в разделе ((#additionbem Добавление БЭМ-сущностей для задач верстки)).

См. также:

Шаблон

Единицей программы на BEMHTML является шаблон. Шаблон BEMHTML связывает входную БЭМ-сущность (заданную именем сущности, элемента, именем и значением модификатора) и соответствующий этой сущности HTML-элемент.

Шаблон состоит из:

  • предиката — набора условий, при выполнении которых применяется шаблон. Типичный предикат описывает свойства входной БЭМ-сущности;
  • и тела — инструкций по генерации выходного HTML.

См. также:

Мода

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

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

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

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

См. также:

Контекст

В процессе обхода входного BEMJSON-дерева шаблонизатор строит контекст — структуру данных, с которой работают шаблоны. Контекст соответствует текущему элементу (узлу) входного БЭМ-дерева и включает:

  • нормализованные сведения о текущей БЭМ-сущности;
  • фрагмент входных данных без модификаций (текущий элемент BEMJSON-дерева и его потомки);
  • строковый буфер, в который записывается HTML-результат;
  • служебные поля, содержание сведения о текущем состоянии (мода, позиция во входном БЭМ-дереве и т.п.);
  • вспомогательные функции.

БЭМ-сущность, описываемая текущим контекстом, называется контекстной сущностью.

См. также:

Синтаксис BEMJSON

Типы данных

Типы данных в BEMJSON аналогичны соответствующим типам в JavaScript.

  • Строки и числа:

    • Строка 'a' "a";
    • Число 1 0.1;

      Структура данных, состоящая из строки или числа, является валидным BEMJSON.

  • Объект (ассоциативный массив) '{ключ: значение}' и остальные типы, кроме массива.

  • Массив — список, может содержать элементы различных типов (строки, числа, объекты, массивы) [ "a", 1, {ключ: значение}, [ "b", 2, ... ] ].

Специальные поля BEMJSON

Для представления данных предметной области БЭМ и HTML в BEMJSON используются объекты, в которых зарезервированы специальные имена полей.

Представление БЭМ-сущностей

БЭМ-сущности представляются в BEMJSON в виде объектов, в которых могут присутствовать следующие поля:

Поле Значение Тип значения Пример
block Имя блока Строка { block: 'b-menu' }
elem Имя элемента Строка { elem: 'item' }
mods Модификаторы блока Объект, содержащий имена и значения модификаторов в качестве пар ключ—значение: {имя_модификатора: 'значение_модификатора'}
{
  block: 'b-link', 
  mods: { pseudo: 'yes', color: 'green' }
}
        
elemMods Модификаторы элемента Объект, содержащий имена и значения модификаторов элемента в качестве пар ключ—значение: {имя_модификатора: 'значение_модификатора'}
{
  elem: 'item', 
  elemMods: { selected: 'yes' }
}
        
mix Подмешанные блоки/элементы Массив, содержащий объекты, описывающие подмешанные блоки и элементы. В качестве значения может выступать объект, который трактуется как массив, состоящий из одного элемента.
{
  block: 'b-link',
  mix: [ { block: 'b-serp-item', elem: 'link' } ]
}
        

См. также:

Представление HTML

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

В BEMJSON предусмотрены следующие поля для непосредственного управления HTML-представлением:

Поле Значение Тип значения Пример
tag HTML-тег для данной сущности String
{
  block: 'b-my-block',
  tag: 'img'
}
attrs HTML-атрибуты для данной сущности Object
{
  block: 'b-my-block',
  tag: 'img',
  attrs: { src: '//yandex.ru/favicon.ico', alt: '' }
}
cls Строка, добавляемая к HTML-атрибуту class (помимо автоматически генерируемых классов) String
{
  block: 'b-my-block',
  cls: 'some-blah-class'
}
bem Флаг — отменить генерацию БЭМ-классов в HTML-атрибуте class для данной сущности Boolean
{
  block: 'b-page',
  tag: 'html',
  bem: false
}
js Либо флаг о наличии клиентского JavaScript у данной сущности, либо параметры JavaScript Boolean
{
  block: 'b-form-input',
  mods: { autocomplete: 'yes' },
  js: {
    dataprovider: { url: 'http://suggest.yandex.ru/...' }
  }
}

Обратите внимание, что имена и смысл полей BEMJSON, управляющих HTML-представлением, совпадают с именами и смыслом соответствующих стандартных мод BEMHTML (тег, атрибуты, класс и т.п.). В случае, если какие-то из аспектов выходного HTML заданы и во входных данных, и в BEMHTML-шаблонах, то более высокий приоритет имеют значения, заданные в BEMHTML-шаблонах.

При генерации HTML будет выполнено одно из двух действий:

  • Объединение значений HTML-параметров, заданных в BEMJSON, cо значениями параметров, заданных в BEMHTML-шаблоне. Объединение значений производится только для тех параметров, для которых оно имеет очевидный смысл: attrs, js, mix.
  • Замещение значений HTML-параметров, заданных в BEMJSON, значениями, заданными в BEMHTML-шаблоне. Выполняется для всех прочих значений: tag, cls, bem, content.

NB Приоритет BEMHTML-шаблонов позволяет автору шаблонов принимать решение, какие HTML-параметры будут приоритетнее в каждом конкретном случае — заданные в BEMHTML или в BEMJSON. Значения HTML-параметров, заданных в BEMJSON, доступны в шаблонах при обращении к фрагменту входного BEMJSON-дерева в контексте (поле this.ctx).


Вложенность: content

Для представления вложенных БЭМ-сущностей (БЭМ-дерева) в BEMJSON зарезервировано поле content. В качестве значения данного поля может выступать произвольный BEMJSON:

  • Примитивный тип (строка, число). Значение используется в качестве содержимого (текста) HTML-элемента, соответствующего контекстной сущности.
  • Объект, описывающий БЭМ-дерево. Значение используется для генерации HTML-элементов, вложенных в HTML-элемент, соответствующий контекстной сущности.

Уровень вложенности дерева БЭМ-сущностей, построенного с помощью поля content, не ограничен.

См. также:

Произвольные поля

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

Примером произвольного поля может служить поле url в блоке ссылки:

{
  block: 'b-link',
  url: '//yandex.ru'
}

Пример использования данных из поля url см. в разделе: Выбор шаблона по условию.

Произвольный JavaScript в BEMJSON

BEMJSON является менее ограниченным форматом, чем JSON. Произвольные JavaScript-выражения будут валидным BEMJSON.

Специфика BEMJSON как формата данных заключается в соблюдении описанных в предшествующих разделах соглашений по именованию полей в объектах (для представления БЭМ-сущностей и HTML-представления) и правил вложения объектов.

Синтаксис BEMHTML

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

Шаблон

Шаблон состоит из двух выражений — предиката и тела, разделенных двоеточием. Допускается произвольное количество либо отсутствие пробелов до и после двоеточия:

предикат: тело

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

подпредикат1, подпредикат2, подпредикат3: тело

Запятая соответствует логическому оператору "И". Предикат шаблона истинен тогда и только тогда, когда истинны все подпредикаты. Порядок записи подпредикатов не имеет значения, порядок проверки подпредикатов не гарантируется.

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

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

подредикат1 {
  подпредикат2: тело1
  подпредикат3: тело2
}

Данная запись эквивалентна следующей:

подпредикат 1, подпредикат 2: тело1    
подпредикат 1, подпредикат 3: тело2

NB Если для данного контекста определено более одного шаблона, то выполняется последний в порядке перечисления в BEMHTML-файле. Более специфические шаблоны должны быть ниже в тексте, чем более общие.


См. также:

Подпредикаты

Предикат шаблона представляет собой набор условий, описывающих момент применения шаблона. Подпредикат шаблона соответствует элементарному условию.

В BEMHTML предусмотрены следующие типы условий:

  • Совпадение с входным БЭМ-деревом.
  • Мода.
  • Произвольное условие
Совпадение с входным БЭМ-деревом

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

Для этого в предикатах используются следующие ключевые слова:

Ключевое слово Аргументы Тип значения Пример
block имя блока идентификатор [a-zA-Z0-9-]+ или произвольное js-выражение
block b-menu, block 'b-menu', block 'b' + '-menu'
elem имя элемента идентификатор [a-zA-Z0-9-]+ или произвольное js-выражение
block b-menu, elem item
mod имя и значение модификатора блока идентификатор [a-zA-Z0-9-]+ или произвольное js-выражение
block b-head-logo, mod size big
elemMod имя и значение модификатора элемента идентификатор [a-zA-Z0-9-]+ или произвольное js-выражение
block b-head-logo, elem text, elemMod size big

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


NB: Важно не путать в предикатах модификаторы блока и модификаторы элемента.

  • block input, mod theme black, elem hint задает элемент hint, вложенный в блок input с модификатором блока theme в значении black.
  • block input, elem hint, elemMod visibility visible задает элемент hint с модификатором элемента visibility в значении visible вложенный в блок input.
  • block input, mod theme black, elem hint, elemMod visibility visible задает элемент hint с модификатором элемента visibility в значении visible вложенный в блок input с модификатором блока theme в значении black.

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


Мода

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

Для проверки стандартных мод используются ключевые слова:

  • default
  • tag
  • bem
  • mix
  • cls
  • js
  • jsAttr
  • attrs
  • content

Кроме того, любой подпредикат, состоящий только из идентификатора ([a-zA-Z0-9-]+), интерпретируется как название нестандартной моды. Например, подпредикат my-mode эквивалентен подпредикату this._mode === 'my-mode'.

Произвольное условие

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


NB Произвольные условия предпочтительно записывать в канонической форме XJST:

предикатное выражение === значение

Где

  • предикатное выражение — произвольное JavaScript-выражение, приводимое к логическому значению;
  • значение — произвольное JavaScript-выражение.

При этом количество различных предикатных выражений в подпредикатах шаблонов должно быть минимизировано. Соблюдение этих условий позволит компилятору XJST производить оптимизации над произвольными условиями шаблонов наряду с оптимизацией стандартизованных условий (БЭМ-сущности и моды).


Тело

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

  • Отдельное JavaScript-выражение:

    предикат: JS-выражение
  • Блок JavaScript-кода, заключенный в фигурные скобки:

    предикат: { JS-блок }

NB Парсер BEMHTML всегда интерпретирует фигурную скобку в начале тела шаблона как обозначение блока JavaScript-кода, поэтому если необходимо в качестве JavaScript-выражения в теле шаблона использовать хэш (объект), его следует заключать в круглые скобки:

предикат: ({name: value})

В рамках тела шаблона можно выполнить следующие действия:

  • Вычислить и вернуть значение. Если в текущей моде ожидается значение определенного типа, значение, возращаемое при вычислении тела шаблона, будет приведено к этому типу и использовано. Если шаблон не возвращает никакого значения, будет использовано значение undefined.
  • Вывести данные непосредственно в HTML-результат. Для этого в теле шаблона следует выполнить запись в буфер HTML-результата (this._buf.push(…)).
  • Производить произвольные операции.

Конструкции XJST

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

local

Конструкция local (язык XJST) используется для временного изменения контекста и переменных, также для последующих операций с ними. По синтаксису блок кода local подобен блокам while и for в JavaScript.

Возможны следующие варианты записи блока local:

  • Тело в виде js-выражения:

    local(expressions) code
  • Тело в виде js-блока:

    local(expressions) { code }

Здесь

  • expressions — это список выражений, представляющих собой операции присваивания значений переменным;
  • code — JavaScript-код, который выполняется в контексте, где значения переменных соответствуют присвоенным в блоке expressions.

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


NB Если в блоке expressions было присвоено значение переменной (полю объекта), которая не была определена на момент входа в блок local, по выходу из блока local эта переменная (поле) будет существовать и получит значение undefined.


См. также:

apply

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

Синтаксис:

apply(expressions)

Где expressions — это список выражений, модифицирующих контекст. Список может быть пуст.

Каждое выражение в списке expressions может представлять собой:

  • Операцию присваивания значений переменным. Аналогично блоку expressions в конструкции local.
  • Новое в bem-bl 0.3. Строку или приводимое к ней выражение. Означает «выставить указанную строку в качестве моды».

    Например, выражение apply('content') эквивалентно выражениюapply(this._mode = 'content').

При вычислении выражения apply выполняются следующие шаги:

  1. Выполнение выражений (присваиваний) в блоке expressions.
  2. Вызов процедуры выбора и выполнения шаблона в контексте, полученном в результате шага 1.
  3. Восстановление значений переменных.

Конструкция apply(expressions) представляет собой сокращенную запись выражения local(expressions) { apply() }.

applyNext

Новое в bem-bl 0.3.

Конструкция applyNext позволяет заново запустить процедуру применения шаблонов к текущему контексту непосредственно в теле шаблона. Результат вычисляется так, как если бы шаблона, в котором используется данная конструкция, не было. Конструкция возвращает значение, вычисленное в результате применения шаблонов к текущему контексту.

Синтаксис:

applyNext(expressions)

Где expressions — список выражений, модифицирующих контекст (операций присваивания значений переменным или строка, означающая присвоение моды). Список может быть пуст. Аналогично блоку expressions в конструкции apply.

При вызове applyNext выполняются следующие шаги:

  1. Создание в контексте флага, позволяющего избежать бесконечной рекурсии при вызове шаблонов. В качестве флага используется случайное число.
  2. Добавление в предикат шаблона проверки на наличие флага.
  3. Выполнение блока expressions (модификация текущего контекста).
  4. Вызов процедуры выбора и выполнения шаблона apply().
  5. Возвращение значения, полученного в результате выполнения шаблона.

Например, шаблон

block b1: {
    statements   
    applyNext()   
}

эквивалентен следующему шаблону:

var _randomflag = ~~(Math.random() * 1e9)
block b1, !this.ctx[_randomflag]: {
    statements   
    local(this.ctx[_randomflag] = true) apply()
}

Где statements — произвольные JS-выражения, допустимые в теле шаблона.

См. также:

applyCtx

Конструкция applyCtx предназначена для модификации фрагмента входного БЭМ-дерева this.ctx и последующего вызова процедуры применения шаблонов apply() к контексту с модифицированным БЭМ-деревом.

Синтаксис:

applyCtx(newctx)

Где в качестве newctx может выступать:

  • Объект (хэш), который будет использован в качестве входного фрагмента БЭМ-дерева. Может содержать ссылки на исходный this.ctx.
  • Операция присваивания переменной.

В ходе вычисления выражения applyCtx выполняются следующие шаги:

  1. Создание в контексте флага, позволяющего избежать бесконечной рекурсии при вызове шаблонов. В качестве флага используется случайное число.
  2. Добавление в предикат шаблона проверки на наличие флага.
  3. Выставление пустой моды в качестве текущей.
  4. Вызов процедуры выбора и выполнения шаблона apply().
  5. Возвращение значения, полученного в результате выполнения шаблона.

Выражение applyCtx(newctx) представляет собой сокращенную запись для выражения applyNext(this.ctx = {newctx}, '').

См. также:

Стандартные моды

В ядре BEMHTML определен набор стандартных мод, которые задают порядок обхода входного БЭМ-дерева (BEMJSON) и генерации выходного HTML, используемый BEMHTML по умолчанию.

По функциональности моды разделяются на два класса:

  • «Пустая» мода определяет алгоритм обхода узлов входного BEMHTML и вызова остальных мод;
  • Все остальные моды определяют порядок генерации выходного HTML. В каждой из таких мод формируется тот или иной фрагмент выходного HTML-дерева.

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

Данная логика работы накладывает следующие ограничения на шаблоны:

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

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

«Пустая» мода ("")

Тип значения тела шаблона: не используется

Пустая (не определенная) мода соответствует моменту, когда значение поля контекста this._mode равно пустой стоке (""). Это значение выставляется:

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

Действие, выполняемое в рамках пустой моды, зависит от типа контекстного (текущего) элемента входного BEMJSON-дерева.

Тип элемента Действие
БЭМ-сущность(блок или элемент) Выставление значений в служебных полях контекста (block elem mods elemMods ctx position) и вызов шаблонов по моде default.
строка/число Вывод значения, приведенного к строке, в буфер HTML-результата.
Boolean, undefined, null Вывод пустой строки в буфер HTML-результата.
массив Итерация по массиву с рекурсивным вызовом шаблонов по пустой моде.

Определение шаблона по пустой моде (подпредикат this._mode === "") имеет смысл только в том случае, если необходимо переопределить принцип обхода входного дерева.

Вызов шаблонов по пустой моде (конструкция apply('') в теле шаблона) необходим, если требуется отклониться от однозначного соответствия «входная БЭМ-сущность — выходной HTML-элемент» и сгенерировать более одного элемента на одну входную сущность. В частности, такой вызов осуществляется автоматически при использовании конструкции applyCtx.

См. также:

default

Тип значения тела шаблона: не используется

В рамках моды default полностью формируется выходной HTML-элемент, соответствующий входной БЭМ-сущности.

В ходе выполнения моды default происходит:

  • вызов всех остальных стандартных мод, отвечающих за формирование отдельных аспектов HTML-элемента;
  • объединение результатов выполнения всех вызываемых мод в результирующую HTML-строку;
  • рекурсивный вызов шаблонов на результат выполнения моды content.

На рисунке ниже схематически отражено, в каких модах генерируются различные фрагменты выходного HTML-элемента.

mode-default

Схема отражает вариант обработки элемента, имеющего пару открывающий—закрывающий тег и вложенное содержимое. Обработка коротких (самозакрытых) элементов аналогична и отличается только отсутствием закрывающего тега и рекурсии. Следует ли обрабатывать данный элемент как короткий, определяет вспомогательная функция контекста this._.isShortTag на основании имени элемента (тега).

Определение шаблона по моде default (подпредикат default) необходимо в тех случаях, когда нужно переопределить процедуру генерации выходного HTML-элемента, например, добавить DOCTYPE к тегу HTML:

block b-page {    
  default: {    
    this._buf.push('<!DOCTYPE html>');    
    applyNext();   
  }    
  tag: 'html'    
}

tag

  • Тип значения тела шаблона: String
  • Значение по умолчанию: 'div'

Мода tag задает имя выходного HTML-элемента (тег). По умолчанию имя элемента равно div. Фрагменты HTML, за генерацию которых отвечает мода tag, выделены на рисунке:

mode-tag


NB Если в качестве значения tag указать пустую строку, для данной сущности будет пропущен этап генерации HTML-элемента (тега и всех атрибутов), но содержимое элемента (content) будет обработано обычным образом.


Определение шаблона по моде tag (подпредикат tag) необходимо, если:

  • для данной сущности следует сгенерировать HTML-элемент с именем, отличным от div;
  • отказаться от генерации HTML-элемента для данной сущности, но обработать вложенные сущности.
Входные данные Шаблон HTML-результат
{
  block: 'b1', 
  content: 'text'
}
block b1, tag: 'span'
<span class="b1">text</span>
{
  block: 'b1',
  content: {
    block: 'b2'
  }
}
b1, tag: ''
<div class="b2"></div>

js

  • Тип значения тела шаблона: Boolean|Object
  • Значение по умолчанию: false

Мода js указывает, есть ли у обрабатываемого блока клиентский JavaScript. В случае наличия JavaScript в моде js могут быть переданы параметры клиентского JavaScript (записываются в атрибут HTML-элемента, имя которого определяется модой jsAttr.

Мода js допускает два типа значения тела шаблона:

  • Boolean — Флаг, указывающий, имеет ли данный блок клиентский JavaScript.
  • Object — Хэш, содержащий параметры JavaScript (подразумевается, что данный блок имеет клиентский JavaScript).

Фрагменты HTML, за генерацию которых отвечает мода js, выделены на рисунке:

mode-js

Определение шаблона по моде js (подпредикат js) имеет смысл только в том случае, если у блока имеется клиентский JavaScript.

Входные данные Шаблон HTML-результат
{block: 'b1'}
block b1, js: true
<div class="b1 i-bem" onclick="return { 'b1': {} }"></div>
{block: 'b1'}
block b1, js: {param: 'value'}
<div class="b1 i-bem" onclick="return { 'b1': { 'param': 'value' } }"></div>

См. также:

bem

  • Тип значения тела шаблона: Boolean
  • Значение по умолчанию: true

Мода bem указывает, нужно ли при формировании HTML-атрибута class включать автоматически сгенерированные имена классов, описывающие данную БЭМ-сущность. По умолчанию генерация БЭМ-классов выполняется. Фрагмент HTML, за генерацию которого отвечает мода bem, выделен на рисунке:

mode-bem

Определение шаблона по моде bem (подпредикат bem) имеет смысл только в том случае, если для данной сущности не нужно генерировать HTML-классы, относящиеся к БЭМ-предметной области. Это может быть необходимо для соблюдения синтаксических требований HTML. Например, теги html, meta, link, script, style не могут иметь атрибута class.

Входные данные Шаблон HTML-результат
{
  block: 'b-page'
}
block b-page {
  tag: 'html'
  bem: false
}
<html></html>

cls

  • Тип значения тела шаблона: String
  • Значение по умолчанию: ''

Мода cls позволяет определить произвольную строку, добавляемую в значение атрибута class помимо автоматически генерируемых значений. Фрагмент HTML, за генерацию которого отвечает мода cls, выделен на рисунке:

mode-cls

Определение шаблона по моде cls (подпредикат cls) имеет смысл в том случае, если для данного элемента необходимы специфические HTML-классы, не относящиеся к предметной области БЭМ.

Входные данные Шаблон HTML-результат
{
  block: 'b1'
}
block b1, cls: 'custom'
<div class="b1 custom"></div>

mix

  • Тип значения тела шаблона: Array|Object
  • Значение по умолчанию: []

Мода mix задает список БЭМ-сущностей, которые необходимо примешать к данной сущности. Сущность, в рамках которой выполняется примешивание, называется базовой, а добавляемая сущность — примешиваемой. Имеет смысл примешивание блоков и элементов.

Технически примешивание сводится к следующим операциям:

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

Все прочие составляющие HTML-элемента (тег, атрибуты и под.) генерируются на основании шаблонов для базовой сущности.

Значением тела шаблона для данной моды может быть:

  • Массив, в котором содержится список объектов (хэшей), каждый из которых описывает БЭМ-сущности, которые необходимо подмешать.
  • Объект, описывающий примешиваемую БЭМ-сущность. Интерпретируется как массив, состоящий из одного элемента.

Фрагмент HTML, за генерацию которого отвечает мода mix, выделен на рисунке:

mode-mix

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


NB Примешивание БЭМ-сущностей выполняется рекурсивно. Иными словами, если для примешиваемой сущности определен шаблон, в котором к ней примешиваются еще какие-либо сущности, все такие сущности добавляются рекурсивно и классы для них появятся в атрибуте class базовой сущности (см. пример ниже).


Входные данные Шаблон HTML-результат
{
  block: 'b1'
  js: { p: 1 }
}
block b1, mix: ({
  block: 'b2', 
  js: { p: 2 }
})
<div class="b1 b2 i-bem" onclick="return { 'b1': { 'p': 1}, 'b2': { 'p': 2} }"></div>
{
  block: 'b1'
}
block b1, mix: [ { block: 'b2' } ]
block b2, mix: [ { block: 'b3' } ]
block b3, mix: [ { block: 'b4' } ]
block b4, mix: [ { block: 'b1' } ]
<div class="b1 b2 b3 b4"></div>

jsAttr

  • Тип значения тела шаблона: String
  • Значение по умолчанию: 'onclick'

Мода jsAttr определяет имя HTML-атрибута, в значении которого будут переданы параметры клиентского JavaScript для данного блока. По умолчанию используется атрибут onclick. Фрагмент HTML, за генерацию которого отвечает мода jsAttr, выделен на рисунке:

mode-jsattr

Определение шаблона по моде jsAttr (подпредикат jsAttr), необходимо в том случае, если требуется передавать параметры JavaScript в нестандартном атрибуте. Например, для тач-сайтов в этих целях используется атрибут ondblclick.

Входные данные Шаблон HTML-результат
{
  block: 'b1',
  js: true
}
block b1, jsAttr: 'ondblclick'
<div class="b1 i-bem" ondblclick="return {'b1': {} }"></div>

attrs

  • Тип значения тела шаблона: Object
  • Значение по умолчанию: {}

Мода attrs позволяет задать имена и значения произвольных HTML-атрибутов для данного элемента. По умолчанию дополнительные атрибуты не генерируются. Фрагмент HTML, за генерацию которого отвечает мода attrs, выделен на рисунке:

mode-attrs

Значением тела шаблона для данной моды должен быть объект (хэш), содержащий имена и значения атрибутов в качестве пар ключ—значение. В качестве ключа должен выступать валидный идентификатор HTML-атрибута, а в качестве значения — строка или число. При выводе HTML специальные символы в значениях атрибутов экранируются вспомогательной функцией this._.attrEscape().


NB Если в качестве значения атрибута указать undefined, этот атрибут не будет выведен в HTML-элементе.


Определение шаблона по моде attrs (подпредикат attrs) необходимо во всех случаях, когда требуется:

  • добавить произвольные HTML-атрибуты на уровне шаблонизатора;
  • исключить указанные атрибуты из вывода, даже если они были определены во входном BEMJSON.
Входные данные Шаблон HTML-результат
{
  block: 'logo',
}
block logo {
  tag: 'img'
  attrs: ({alt: 'logo', href: 'http://...'})  
}
<img alt="logo" href="http://..." />
{
  block: 'input',
  disabled: true
}
block input {
  tag: 'input'
  attrs: ({ disabled: this.ctx.disabled ? 'disabled' : undefined })
}
<input class="input" disabled="disabled"/>
{ block: 'input'} Тот же шаблон
<input class="input"/>

content

  • Тип значения тела шаблона: BEMJSON
  • Значение по умолчанию: this.ctx.content

В рамках моды content вычисляется содержимое HTML-элемента, в качестве которого может выступать произвольный BEMJSON (как строка или число, так и дерево БЭМ-сущностей). В качестве значения по умолчанию используется значение поля content контекстной БЭМ-сущности (this.ctx.content).

Фрагмент HTML, за генерацию которого отвечает мода content, выделен на рисунке:

mode-content

Определение шаблона по моде content (подпредикат content) необходимо, если:

  • Необходимо на уровне шаблонизатора добавить содержимое для сущности, у которой отсутствует content во входном BEMJSON.
  • Необходимо подменить содержимое сущности на уровне шаблонизатора.
Входные данные Шаблон HTML-результат
{
  block: 'b1'
}
block b1, content: ({
  block: 'b2'
})
<div class="b1"><div class="b2"></div></div>

См. также:

Поля контекста

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

В момент выполнения шаблона контекст доступен в виде объекта, обозначаемого ключевым словом this. Обращение к контексту возможно как в предикате, так и в теле шаблона.

Автор шаблонов имеет возможность определить любые дополнительные поля в контексте.

Все поля контекста можно разделить на две категории:

  • Контекстно-зависимые, значение которых изменяется в зависимости от обрабатываемого узла и фазы процесса обработки.
  • Контекстно-независимые, значение которых постоянно.

См. также:

Контекстно-зависимые поля

Поле Тип значения Описание
this.block String Имя блока (контекстной БЭМ-сущности).
this.elem String Имя элемента (контекстной БЭМ-сущности).
this.mods Object Модификаторы блока (контекстной БЭМ-сущности), имя_модификатора: значение_модификатора.
this.elemMods Object Модификаторы элемента (контекстной БЭМ-сущности), имя_модификатора: значение_модификатора.
this.ctx BEMJSON Фрагмент входного BEMJSON-дерева, содержащий обрабатываемый узел и его потомков в неизмененном виде. Используется для получения доступа к произвольным полям данных входного BEMJSON.
this.position Number Номер позиции текущей сущности среди ее сиблингов во входном BEMJSON-дереве (начиная с 1).
this._mode String Текущая мода. Если необходимо определить собственные (нестандартные) моды, в соответствующем шаблоне следует присваивать этому полю имя моды в момент входа в нее.
this._buf Array Буфер HTML-результата. Обычно используется только для записи готовых HTML-фрагментов с использованием метода this._buf.push().
this.isFirst() Boolean Проверяет, является ли данная БЭМ-сущность первой среди сиблингов во входном БЭМ-дереве..
this.isLast() Boolean Проверяет, является ли данная БЭМ-сущность последней среди сиблингов во входном БЭМ-дереве. Подробнее см. Алгоритм вычисления позиции БЭМ-сущности.
this.generateId() Number Возвращает уникальный идентификатор для текущего контекста. Используется, когда нужно сгенерировать HTML-элементы, связанные с помощью атрибута id.

NB Ключевые слова для проверки БЭМ-сущностей в предикате являются сокращенной записью для проверки значений полей block, elem и т.д. в текущем контексте. Например, подпредикат block b1 эквивалентен подпредикату this.block === 'b1'.

Аналогично, ключевые слова для проверки моды в предикате являются сокращенной записью для проверки значения служебного поля _mode в текущем контексте. Например, подпредикатtag эквивалентен подпредикату this._mode === 'tag'.


Достраивание БЭМ-сущностей по контексту

В BEMJSON принято записывать БЭМ-сущности в свернутом виде. Например, если в блок menu вложен элемент item, в объекте, описывающем пункт меню, не указывается имя содержащего его блока меню:

{    
  block: 'menu',     
  content: {     
    elem: 'item'     
  }
}

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

this.block: 'menu'    
this.ctx.block: 'menu'

В момент входа во вложенный элемент item, в поле this.block достраивается значениеmenu. В то же время в поле this.ctx.block находится значение undefined, так как во входном BEMJSON это поле в элементе item не определено:

this.block: 'menu'    
this.elem: 'item'    
this.ctx.block: undefined    
this.ctx.elem: 'item'

Достраивание выполняется также для элементов, примешанных внутри блоков. Например, в приведенном БЭМ-дереве:

{ block: 'b1', mix: { elem: 'e1' } }

В примешанном элементе будет достроено имя блока:

{ block: 'b1', mix: { block: 'b1', elem: 'e1' } }

Достраивание БЭМ-сущностей необходимо для корректного срабатывания предикатов на элементы блоков вида block menu, elem item, так как в таких предикатах проверяются значения полей контекста this.block и this.elem.


NB Чтобы избежать срабатывания предикатов вида block menu внутри вложенных в блок элементов, на этапе компиляции шаблонов к таким предикатам в необходимых случаях автоматически добавляется подпредикат !this.elem. Автоматическое добавление может не сработать, если предикат шаблона содержит подпредикат с произвольным условием, записанный не в канонической форме XJST.


Алгоритм вычисления позиции БЭМ-сущности

Позиция в БЭМ-дереве (поле контекста this.position) представляет собой натуральное число, соответствующее порядковому номеру текущей (контекстной) БЭМ-сущности среди ее сиблингов в БЭМ-дереве (одноранговых сущностей).

При вычислении позиции:

  • Нумеруются только те узлы обрабатываемого BEMJSON, которые соответствуют БЭМ-сущностям, прочим узлам не соответствует никакой номер позиции.
  • Позиции нумеруются начиная с 1.
  • Нумерация производится в порядке обхода дерева (уплощенный список иерархического представления BEMJSON).

Пример нумерации позиций во входном БЭМ-дереве:

{    
  block: 'page',         // this.position === 1    
  content: [
    { block: 'head' },   // this.position === 1    
    { block: 'menu',     // this.position === 2    
      content: [ 
        { elem: 'item' }, // this.position === 1    
        { elem: 'item' }, // this.position === 2    
        { elem: 'item' }  // this.position === 3    
      ]    
    },    
    'text'              // this.position === undefined    
  ]   
}

NB БЭМ-дерево может быть достроено в процессе выполнения шаблонов с помощью шаблонов по моде content и шаблонов по пустой моде. Такое динамическое изменение БЭМ-дерева учитывается при вычислении позиции.


Функция определения последней БЭМ-сущности среди сиблингов this.isLast() не сработает в том случае, если в массиве, содержащем одноранговые БЭМ-сущности, последний элемент не является БЭМ-сущностью. Например:

{    
  block: 'b1',   
  content: [    
    { block: 'b2' },   
    { block: 'b3' }, // this.isLast() === false   
    'text'    
  ]    
}

Такое поведение объясняется тем, что в целях оптимизации BEMHTML не выполняет предварительного полного обхода БЭМ-дерева. Поэтому в момент обработки блока b3 уже известна длина массива (b3 не является последним элементом), но еще не известно, что последний элемент не является БЭМ-сущностью и не получит номера позиции.

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

Контекстно-независимые поля

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

Поле Тип значения Описание
this._.isArray(Object) Boolean Проверяет, является ли данный объект массивом.
this._.isSimple(Object) Boolean Проверяет, является ли данный объект примитивным JavaScript типом.
this._.isShortTag(String) Boolean Проверяет, принадлежит ли указанное имя тега к списку коротких тегов (не требующих закрывающего элемента и рекурсивной обработки). Полный список которких тегов:area, base, br, col, command, embed, hr, img, input, keygen, link, meta, param, source, wbr.
this._.extend(Object, Object) Object Возвращает хэш, объединяющий содержимое двух хэшей, переданных в качестве аргументов. Если хэши содержат совпадающие ключи, в результат записывается значение из хэша, переданного в качестве второго аргумента.
this._.xmlEscape(String) String Возвращает переданную строку с заэкранированными управляющими символами XML [&<>].
this._.attrEscape(String) String Экранирует значение управляющих символов для значений XML- и HTML-атрибутов ("[&<>]).

Примеры и рецепты

Приведение входных данных к формату, ориентированному на представление

Задача

Сформировать входное БЭМ-дерево для страницы френдленты (список постов с указанием информации об авторе), удобное для обработки в терминах шаблонов BEMHTML. Такое дерево должно быть ориентировано на представление, т.е. набор и порядок БЭМ-сущностей должен соответствовать набору и порядку DOM-узлов выходного HTML.

Решение

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

{
    posts: [ { text: 'post text', author: 'login' }, … ],    
    users: { 'login': { userpic: 'URL', name: 'Full Name' }, … },    
}

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

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

{    
    block: 'posts',    
    content: [     
        {    
            block: 'post',    
            content: [    
                { block: 'userpic', content: 'URL' },    
                { block: 'user', content: 'Full Name' },    
                { elem: 'text', content: 'post text' }    
            ]    
        },    
        …    
    ]    
}

Выбор шаблона по условию

Задача

Блок b-link встречается в двух разновидностях:

  • { block: 'b-link', content: 'ссылка без URL' }
  • { block: 'b-link', url: '//ya.ru', content: 'ссылка с URL' }

Необходимо по-разному оформить выходной HTML-элемент в зависимости от наличия/отсутствия поля url в данных блока.

Решение

Следует сделать проверку на наличие поля url подпредикатом шаблона: выражение this.ctx.url будет истинным, только если поле url определено.

block b-link {
  tag: 'span'
  this.ctx.url {
    tag: 'a'
    attrs: { href: this.ctx.url }
  }
}

Неправильно использовать для решения этой задачи условные конструкции JavaScript в теле шаблона:

block b-link, tag: this.ctx.url ? 'a' : 'span'

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

См. также:

Наследование

Задача

На разных уровнях переопределения определены два различных шаблона на одну и ту же БЭМ-сущность (block b1). Каждый из шаблонов определяет своё содержимое по моде content.

Необходимо на втором уровне переопределения унаследовать содержимое, определённое на первом уровне, и добавить дополнительное. Требуется аналог <xsl:apply-imports/>.

Решение

В BEMHTML есть аналог <xsl:apply-imports/>. Реализация основывается на возможности заново запустить в шаблоне процедуру применения шаблонов к текущему контексту (apply()). Таким образом можно вызвать тот шаблон, который был определен для данного контекста (БЭМ-сущности, моды и т.п.) ранее или на другом уровне переопределения.

При вычислении выражения apply() возвращается результат, полученный в ходе применения ранее определенного шаблона. Для избежания бесконечного цикла необходимо добавить подпредикат проверки наличия в контексте какого-то флага (например, _myGuard), который будет выставлен при выполнении apply().

// шаблон на первом уровне переопределения   
block b1, content: 'text1'   

// шаблон на втором уровне переопределения   
block b1, content, !this._myGuard: [
    apply(this._myGuard = true), // получаем предыдущее значение content
    'text2'
]

В результате применения шаблонов к блоку b1 будет получен HTML:

<div class="b1">text1text2</div>

В bem-bl версии 0.3 добавлена конструкция applyNext, которая автоматически генерирует уникальное имя флага против зацикливания.

block b1, content: 'text1'

block b1, content: [
    applyNext(), // получаем предыдущее значение content
    'text2'
]

См. также:

Оборачивание блока в другой блок

Задача

Необходимо вложить блок (b-inner) в другой блок (b-wrapper) при выполнении шаблона. Таким образом, одному входному блоку будет соответствовать два вложенных друг в друга блока.

Решение

При обработке блока b-inner в шаблоне по моде default (генерация целого элемента) следует модифицировать фрагмент входного дерева this.ctx, куда и добавляется блок b-wrapper и рекурсивно запустить вызов шаблонов по пустой моде apply(this._mode = "").

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

block b-inner, default, !this.ctx._wrap: apply(
   this._mode = "",
   this.ctx._wrap = true
   this.ctx = {
       block: 'b-wrapper',
       content: this.ctx
   }
)

В bem-bl начиная с версии 0.3 добавлена конструкция applyCtx(), которая автоматически добавляет флаг от зацикливания, присваивает this.ctx и применяет шаблоны по пустой моде:

block b-inner, default: applyCtx({ block: 'b-wrapper', content: this.ctx })

NB Конструкцию applyCtx() можно применять для замены БЭМ-сущности в исходном дереве, если не использовать исходное содержимое блока (this.ctx) в аргументе applyCtx().


См. также:

Добавление БЭМ-сущностей для задач вёрстки

Задача

Необходимо сверстать блок с закруглёнными уголками, работающий во всех браузерах (без использования CSS3).

Входной BEMJSON может быть таким:

{ block: 'box', content: 'text' }

Реализация уголков требует добавления к блоку четырех дополнительных элементов. Поскольку данные элементы отражают детали HTML-верстки, ими не следует загромождать входное БЭМ-дерево. Добавить эти элементы следует на уровне BEMHTML-шаблона. Финальное БЭМ-дерево должно выглядеть так:

{    
    block: 'box',    
    content: {    
        elem: 'left-top',    
        content: {    
            elem: 'right-top',    
            content: {    
                elem: 'right-bottom',    
                content: {    
                    elem: 'left-bottom',    
                    content: 'text'    
                }    
            }    
        }    
    }    
}
Решение

Для модификации входного БЭМ-дерева на уровне BEMHTML потребуется написать шаблон по моде content для блока box. Подмена фрагмента входного БЭМ-дерева (добавление необходимых элементов) выполняется с помощью конструкции applyCtx(), а подстановка исходного содержимого — с помощью конструкции applyNext().

BEMHTML-шаблон, выполняющий это преобразование:

block box, content: applyCtx({    
    elem: 'left-top',    
    content: {    
        elem: 'right-top',    
        content: {    
            elem: 'right-bottom',   
            content: {    
                elem: 'left-bottom',   
                content: applyNext()   
            }    
        }    
    }    
})

В bem-bl начиная с версии 0.3 добавлен короткий синтаксис для применения шаблонов по моде:

block b-source, default: apply("", this.ctx.block = 'b-target')

См. также:

Использование позиции БЭМ-сущности

Задача

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

Решение

Используем механизм вычисления позиции БЭМ-сущности среди сиблингов (поле контекста this.position). Входные данные могут выглядеть так:

{    
  block: 'menu',    
  content: [    
    { elem: 'item', content: 'aaa' },    
    { elem: 'item', content: 'bbb' },    
    { elem: 'item', content: 'ccc' },    
  ]    
}

Для выполнения нумерации следует написать шаблон по моде content на пункт меню, в котором содержание элемента будет составлено из номера позиции, разделителя (точки с пробелом) и исходного текста элемента (полученного с помощью конструкции applyNext()):

block menu {   
  tag: 'ul'    
  elem item {    
    tag: 'li'    
    content: [    
      this.position, '. ',    
      applyNext()    
    ]    
  }    
}

См. также:

Проверка подпредикатов в определенном порядке

Задача

Необходимо проверять подпредикаты шаблона в строго определенном порядке, например, сначала проверить наличие в контексте объекта this.world, а затем проверить значение поля в этом объекте this.world.answer.

Решение

Воспользуемся тем, что подпредикат шаблона BEMHTML может быть произвольным JavaScript-выражением и запишем его в следующей форме:

(this.world && this.world.answer === 42)

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

Связывание HTML-элементов по id

Задача

Необходимо для входного блока input сгенерировать пару HTML-элементов <label> и <input>, так чтобы значение атрибута input@id было сгенерировано автоматически, уникально и совпадало со значением атрибута label@for.

Входные данные могут выглядеть так:

{
  block: 'input',
  label: 'My Input',
  content: 'my value'
}
Решение

Для генерации уникального идентификатора, подходящего в качестве значения атрибута id, воспользуемся вспомогательной функцией контекста this.generateId(). Чтобы сгенерировать два HTML-элемента на основании одного входного блока, потребуется два шаблона:

  • шаблон по моде tag, указывающий пустую строку, чтобы отменить генерацию HTML-элемента для данного блока, но обработать содержимое;
  • шаблон по моде content, в котором будут сформированы два необходимых элемента и их атрибуты.
block input {
  tag: ''
  content: [
    {
      tag: 'label',
      attrs: { 'for': this.generateId() },
      content: this.ctx.label
    },
    {
      tag: 'input',
      attrs: {
        id: this.generateId(),
        value: this.ctx.content
      }
    }
  ]
}

Источники

Сборник видео: http://clubs.ya.ru/bem/posts.xml?tag=64664080

Рассказ на пЯТЬнице про BEMHTML
http://clubs.ya.ru/bem/replies.xml?item_no=898

Рассказ про XJST:
http://clubs.ya.ru/bem/replies.xml?item_no=899

"BEMHTML. Not yet another шаблонизатор".
Чем шаблонизатор BEMHTML отличается от сотен других шаблонизаторов и почему мы используем именно его.
http://clubs.ya.ru/bem/replies.xml?item_no=1153
http://clubs.ya.ru/bem/replies.xml?item_no=1172

Шаблонизатор, работающий с несколькими уровнями
http://clubs.ya.ru/bem/replies.xml?item_no=1391

Предварительная документация
http://clubs.ya.ru/bem/replies.xml?item_no=992
http://bem.github.com/bem-bl/pages/bemhtml-syntax/bemhtml-syntax.ru.html

bem-bl: http://bem.github.com/bem-bl/index.ru.html