=== Введение **Данный документ** представляет собой справочное руководство по шаблонизатору BEMHTML. В документе описаны: * основные особенности BEMHTML, отличающие его от других шаблонизаторов; * синтаксис описания входных данных BEMJSON и шаблонов BEMHTML; * порядок обработки входных данных и генерации HTML; * примеры решения типовых задач средствами BEMHTML. **Целевая аудитория документа** — веб-разработчики и HTML-верстальщики, использующие ((http://bem.github.com/bem-method/pages/beginning/beginning.ru.html БЭМ-методологию)). Предполагается, что читатель знаком с: * HTML; * JavaScript; * CSS; * БЭМ. **В документе не описаны** настройка среды разработки и процедуры компиляции шаблонов. === Особенности шаблонизатора BEMHTML ==== Привязка к БЭМ-предметной области Шаблонизатор BEMHTML входит в связку технологий, обеспечивающих создание веб-интерфейсов в рамках ((http://bem.github.com/bem-method/pages/beginning/beginning.ru.html БЭМ-методологии)). Входными данными шаблонизатора является описывающее страницу БЭМ-дерево в формате ((#sintaksisbemjson BEMJSON)). Язык шаблонов BEMHTML предлагает специальные конструкции для обработки блоков, элементов и модификаторов. ==== Декларативные шаблоны ===== Императивный подход Традиционные шаблонизаторы предлагают **императивный подход** к написанию шаблонов: HTML формируется в процессе последовательного чтения и выполнения шаблона. #| || **Входные данные** | **Шаблон** | **Результат** || || %%(js) { items: [ { text: '1' }, { text: '2' } ] } %%|%%(html)
%%|%%(html) %% |||# В таком шаблоне неизбежна избыточность, вызванная синтаксическими требованиями HTML: //вывести открывающий тег — сгенерировать содержимое — вывести закрывающий тег//. Еще выше избыточность в таблицах, списках и т.п. ===== Декларативный подход **Декларативный подход** позволяет формулировать шаблоны как набор простых утверждений вида: //тип входных данных (БЭМ-сущность) — HTML-представление (тег, атрибут, и т.п.)//. #| || **Входные данные** | **Шаблон** | **Результат** || || %%(js) { block: 'menu', content: [ { elem: 'item', content: '1' }, { elem: 'item', content: '2' } ] } %%|%%(js) block menu { tag: 'ul' elem item, tag: 'li' } %%|%%(html) %% |||# Декларативность шаблонов достигается за счет того, что в BEMHTML процедура генерации HTML-элемента стандартизована и выполняется шаблонизатором. Этот же подход к выполнению преобразований данных используется в XSLT и AWK. ==== Язык описания шаблонов — JavaScript BEMHTML представляет собой специализированный язык (DSL), **расширяющий** JavaScript. Точнее, BEMHTML является надмножеством языка шаблонов ((https://github.com/veged/xjst 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-страницы, которые являются зоной ответственности верстальщика, должны определяться только на уровне шаблонизатора. Пример такого решения приведен в разделе ((#dobavleniebjem-sushhnostejjdljazadachvjorstki Добавление БЭМ-сущностей для задач верстки)). **См. также**: * ((#sintaksisbemjson Синтаксис BEMJSON)) ==== Шаблон Единицей программы на BEMHTML является **шаблон**. Шаблон BEMHTML связывает входную БЭМ-сущность (заданную именем сущности, элемента, именем и значением модификатора) и соответствующий этой сущности HTML-элемент. Шаблон состоит из: * **предиката** — набора условий, при выполнении которых применяется шаблон. Типичный предикат описывает свойства входной БЭМ-сущности; * и **тела** — инструкций по генерации выходного HTML. **См. также**: * ((#sintaksis Синтаксис BEMHTML)). ==== Мода В процессе работы шаблонизатор последовательно обходит узлы входного БЭМ-дерева. Для каждого узла — БЭМ-сущности — выполняется цикл генерации выходного HTML-элемента. Для вложенных сущностей цикл генерации HTML-элементов выполняется рекурсивно. Таким образом, выходное HTML-дерево формируется поэлементно в процессе обхода входного БЭМ-дерева. Цикл генерации каждого элемента последовательно проходит ряд фаз, называемых **модами**. Каждая мода отвечает за определенный фрагмент генерируемого HTML-элемента — тег, атрибуты, класс и т.п. В каждой моде вызывается процедура выбора и выполнения подходящего шаблона. Моды позволяют разделить выходной элемент на фрагменты, каждый из которых может быть описан простым типом данных: тег и класс — строкой, атрибуты — словарем, необходимость в БЭМ-классах — логическим значением и т.п. Благодаря этому возможно написание декларативных шаблонов, в предикате которых указана мода, а в теле содержатся данные соответствующего этой моде простого типа. В этом случае полное представление HTML-элемента может быть задано несколькими шаблонами. Особый статус имеет **((#default мода default))**, которая отвечает за генерацию целого HTML-элемента. В рамках этой моды задан набор и порядок прохождения остальных мод, соответствующих фрагментам HTML-элемента, а также определена процедура сборки финального представления HTML-элемента из фрагментов, сгенерированных в остальных модах. Написание шаблона, который переопределяет поведение в данной моде, позволяет полностью контролировать генерацию элемента из BEMHTML, не пользуясь стандартными модами, позволяющими генерировать выходной элемент по частям. **См. также**: * ((#standartnyemody Стандартные моды)). ==== Контекст В процессе обхода входного BEMJSON-дерева шаблонизатор строит **контекст** — структуру данных, с которой работают шаблоны. Контекст соответствует текущему элементу (узлу) входного БЭМ-дерева и включает: * нормализованные сведения о текущей БЭМ-сущности; * фрагмент входных данных без модификаций (текущий элемент BEMJSON-дерева и его потомки); * строковый буфер, в который записывается HTML-результат; * служебные поля, содержание сведения о текущем состоянии (мода, позиция во входном БЭМ-дереве и т.п.); * вспомогательные функции. БЭМ-сущность, описываемая текущим контекстом, называется **контекстной сущностью**. **См. также**: * ((#poljakonteksta Поля контекста)). * ((#dostraivaniebjem-sushhnostejjpokontekstu Достраивание БЭМ-сущностей по контексту)). === Синтаксис 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' } ] } %%|| |# **См. также**: * ((#dostraivaniebjem-sushhnostejjpokontekstu Достраивание БЭМ-сущностей по контексту)) ===== Представление 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|Object%% | %% { block: 'b-form-input', mods: { autocomplete: 'yes' }, js: { dataprovider: { url: 'http://suggest.yandex.ru/...' } } } %%|| |# Обратите внимание, что имена и смысл полей BEMJSON, управляющих HTML-представлением, совпадают с именами и смыслом соответствующих ((#standartnyemody стандартных мод)) 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%%, не ограничен. **См. также**: * ((#content Мода content)) ===== Произвольные поля Помимо специальных полей, описывающих БЭМ-сущность и ее HTML-представление, в том же объекте могут присутствовать любые поля с произвольными данными, которые будут доступны для использования в BEMHTML-шаблонах. Примером произвольного поля может служить поле %%url%% в блоке ссылки: %% { block: 'b-link', url: '//yandex.ru' } %% Пример использования данных из поля %%url%% см. в разделе: ((#vyborshablonapousloviju Выбор шаблона по условию)). ==== Произвольный 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-файле. Более специфические шаблоны должны быть ниже в тексте, чем более общие. ---- **См. также**: * ((#proverkapodpredikatovvopredelennomporjadke Проверка подпредикатов в определенном порядке)) ==== Подпредикаты Предикат шаблона представляет собой набор условий, описывающих момент применения шаблона. Подпредикат шаблона соответствует элементарному условию. В 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%%. Для модификаторов блоков и элементов используются разные ключевые слова, чтобы дать возможность комбинировать в предикатах условия, одновременно включающие упоминания модификаторов блоков и элементов. ---- ===== Мода В качестве подпредиката может выступать название одной из ((#standartnyemody стандартных мод)). Это означает, что данный предикат будет истинным в тот момент обработки, когда выставлена соответствующая мода. Для проверки стандартных мод используются ключевые слова: * %%default%% * %%tag%% * %%bem%% * %%mix%% * %%cls%% * %%js%% * %%jsAttr%% * %%attrs%% * %%content%% Кроме того, любой подпредикат, состоящий только из идентификатора (%%[a-zA-Z0-9-]+%%), интерпретируется как название нестандартной моды. Например, подпредикат %%my-mode%% эквивалентен подпредикату %%this._mode === 'my-mode'%%. ===== Произвольное условие Произвольные условия учитывают совпадения с данными, не попадающими под предметную область БЭМ. В качестве произвольного условия может выступать любое JavaScript-выражение, которое будет приведено к логическому значению. {{a name="xjst-canonical"}} ---- !!**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%%. ---- **См. также**: * Подробнее о конструкции local ((http://github.com/veged/xjst/blob/master/README.md#local в документации XJST)). ===== apply Конструкция %%apply%% предназначена для явного вызова процедуры выбора и выполнения шаблона, предикат которого истинен в данном контексте. Конструкция позволяет вызывать шаблоны в модифицированном контексте. Синтаксис: %% apply(expressions) %% Где %%expressions%% — это список выражений, модифицирующих контекст. Список может быть пуст. Каждое выражение в списке %%expressions%% может представлять собой: * Операцию присваивания значений переменным. Аналогично блоку expressions в ((#local конструкции 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%% позволяет заново запустить процедуру применения шаблонов к текущему контексту непосредственно в теле шаблона. Результат вычисляется так, как если бы шаблона, в котором используется данная конструкция, не было. Конструкция возвращает значение, вычисленное в результате применения шаблонов к текущему контексту. Синтаксис: %%(js) applyNext(expressions) %% Где %%expressions%% — список выражений, модифицирующих контекст (операций присваивания значений переменным или строка, означающая присвоение моды). Список может быть пуст. Аналогично блоку %%expressions%% в ((#apply конструкции apply)). При вызове %%applyNext%% выполняются следующие шаги: 1. Создание в контексте флага, позволяющего избежать бесконечной рекурсии при вызове шаблонов. В качестве флага используется случайное число. 2. Добавление в предикат шаблона проверки на наличие флага. 3. Выполнение блока %%expressions%% (модификация текущего контекста). 4. Вызов процедуры выбора и выполнения шаблона %%apply()%%. 5. Возвращение значения, полученного в результате выполнения шаблона. Например, шаблон %%(js) block b1: { statements applyNext() } %% эквивалентен следующему шаблону: %%(js) var _randomflag = ~~(Math.random() * 1e9) block b1, !this.ctx._randomflag: { statements local(this.ctx._randomflag = true) apply() } %% Где %%statements%% — произвольные JS-выражения, допустимые в теле шаблона. **См. также**: * ((#nasledovanie Наследование)). * ((#dobavleniebjem-sushhnostejjdljazadachvjorstki Добавление БЭМ-сущностей для задач верстки)). ===== applyCtx !!(зел)Новое в bem-bl 0.3!! Конструкция %%applyCtx%% предназначена для модификации фрагмента входного БЭМ-дерева %%this.ctx%% и последующего вызова процедуры применения шаблонов %%apply()%% к контексту с модифицированным БЭМ-деревом. Синтаксис: %%(js) applyCtx(newctx) %% Где в качестве %%newctx%% может выступать: * Объект (хэш), который будет использован в качестве входного фрагмента БЭМ-дерева. Может содержать ссылки на исходный %%this.ctx%%. * Операция присваивания переменной. В ходе вычисления выражения %%applyCtx%% выполняются следующие шаги: 1. Создание в контексте флага, позволяющего избежать бесконечной рекурсии при вызове шаблонов. В качестве флага используется случайное число. 2. Добавление в предикат шаблона проверки на наличие флага. 3. Выставление ((#pustajamodaquotquot пустой моды)) в качестве текущей. 4. Вызов процедуры выбора и выполнения шаблона %%apply()%%. 5. Возвращение значения, полученного в результате выполнения шаблона. Выражение %%applyCtx(newctx)%% представляет собой сокращенную запись для выражения %%applyNext(this.ctx = {newctx}, '')%%. **См. также**: * ((#oborachivanieblokavdrugojjblok Оборачивание блока в другой блок)) * ((#dobavleniebjem-sushhnostejjdljazadachvjorstki Добавление БЭМ-сущностей для задач верстки)). === Стандартные моды В ядре 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 конструкции applyCtx)). **См. также**: * ((#oborachivanieblokavdrugojjblok Оборачивание блока в другой блок)) ==== default !!(зел)Тип значения тела шаблона: %%не используется%%!! В рамках моды %%default%% полностью формируется выходной HTML-элемент, соответствующий входной БЭМ-сущности. В ходе выполнения моды default происходит: * вызов всех остальных стандартных мод, отвечающих за формирование отдельных аспектов HTML-элемента; * объединение результатов выполнения всех вызываемых мод в результирующую HTML-строку; * рекурсивный вызов шаблонов на результат выполнения моды %%content%%. На рисунке ниже схематически отражено, в каких модах генерируются различные фрагменты выходного HTML-элемента. file:mode-default.png Схема отражает вариант обработки элемента, имеющего пару открывающий—закрывающий тег и вложенное содержимое. Обработка коротких (самозакрытых) элементов аналогична и отличается только отсутствием закрывающего тега и рекурсии. Следует ли обрабатывать данный элемент как короткий, определяет вспомогательная функция контекста %%this._.isShortTag%% на основании имени элемента (тега). Определение шаблона по моде %%default%% (подпредикат %%default%%) необходимо в тех случаях, когда нужно переопределить процедуру генерации выходного HTML-элемента, например, добавить DOCTYPE к тегу HTML: %%(js) block b-page { default: { this._buf.push(''); applyNext(); } tag: 'html' } %% ==== tag !!(зел)Тип значения тела шаблона: %%String%%!! !!(зел)Значение по умолчанию: %%'div'%%!! Мода %%tag%% задает имя выходного HTML-элемента (тег). По умолчанию имя элемента равно %%div%%. Фрагменты HTML, за генерацию которых отвечает мода %%tag%%, выделены на рисунке: file:mode-tag.png ---- !!**NB**!! Если в качестве значения %%tag%% указать пустую строку, для данной сущности будет пропущен этап генерации HTML-элемента (тега и всех атрибутов), но содержимое элемента (%%content%%) будет обработано обычным образом. ---- Определение шаблона по моде %%tag%% (подпредикат %%tag%%) необходимо, если: * для данной сущности следует сгенерировать HTML-элемент с именем, отличным от %%div%%; * отказаться от генерации HTML-элемента для данной сущности, но обработать вложенные сущности. #| || **Входные данные** | **Шаблон** | **HTML-результат** || || %%(js) { block: 'b1', content: 'text' } %% | %%(js) block b1, tag: 'span' %% | %%(html) text %%|| || %%(js) { block: 'b1', content: { block: 'b2' } } %% | %%(js) b1, tag: '' %% | %%(html) %%|| |# ==== js !!(зел)Тип значения тела шаблона: %%Boolean|Object%%!! !!(зел)Значение по умолчанию: %%false%%!! Мода %%js%% указывает, есть ли у обрабатываемого блока клиентский JavaScript. В случае наличия JavaScript в моде %%js%% могут быть переданы параметры клиентского JavaScript (записываются в атрибут HTML-элемента, имя которого определяется ((#jsattr модой %%jsAttr%%))). Мода %%js%% допускает два типа значения тела шаблона: * %%Boolean%% — Флаг, указывающий, имеет ли данный блок клиентский JavaScript. * %%Object%% — Хэш, содержащий параметры JavaScript (подразумевается, что данный блок имеет клиентский JavaScript). Фрагменты HTML, за генерацию которых отвечает мода %%js%%, выделены на рисунке: file:mode-js.png Определение шаблона по моде %%js%% (подпредикат %%js%%) имеет смысл только в том случае, если у блока имеется клиентский JavaScript. #| || **Входные данные** | **Шаблон** | **HTML-результат** || || %%(js) {block: 'b1'} %%| %%(js) block b1, js: true %% | %%(html) %%|| || %%(js) {block: 'b1'} %%| %%(js) block b1, js: {param: 'value'} %% | %%(html) %%|| |# **См. также**: * ((http://bem.github.com/bem-bl/sets/common-desktop/i-bem/i-bem.ru.html JS-реализация блока i-bem)) ==== bem !!(зел)Тип значения тела шаблона: %%Boolean%%!! !!(зел)Значение по умолчанию: %%true%%!! Мода %%bem%% указывает, нужно ли при формировании HTML-атрибута %%class%% включать автоматически сгенерированные имена классов, описывающие данную БЭМ-сущность. По умолчанию генерация БЭМ-классов выполняется. Фрагмент HTML, за генерацию которого отвечает мода %%bem%%, выделен на рисунке: file:mode-bem.png Определение шаблона по моде %%bem%% (подпредикат %%bem%%) имеет смысл только в том случае, если для данной сущности **не нужно** генерировать HTML-классы, относящиеся к БЭМ-предметной области. Это может быть необходимо для соблюдения синтаксических требований HTML. Например, теги %%html%%, %%meta%%, %%link%%, %%script%%, %%style%% не могут иметь атрибута %%class%%. #| || **Входные данные** | **Шаблон** | **HTML-результат** || || %%(js) { block: 'b-page' } %% | %%(js) block b-page { tag: 'html' bem: false } %% | %%(html) %%|| |# ==== cls !!(зел)Тип значения тела шаблона: %%String%%!! !!(зел)Значение по умолчанию: %%''%%!! Мода %%cls%% позволяет определить произвольную строку, добавляемую в значение атрибута %%class%% помимо автоматически генерируемых значений. Фрагмент HTML, за генерацию которого отвечает мода %%cls%%, выделен на рисунке: file:mode-cls.png Определение шаблона по моде %%cls%% (подпредикат %%cls%%) имеет смысл в том случае, если для данного элемента необходимы специфические HTML-классы, не относящиеся к предметной области БЭМ. #| || **Входные данные** | **Шаблон** | **HTML-результат** || || %%(js) { block: 'b1' } %% | %%(js) block b1, cls: 'custom' %% | %%(html) %% || |# ==== mix !!(зел)Тип значения тела шаблона: %%Array|Object%%!! !!(зел)Значение по умолчанию: %%[]%%!! Мода %%mix%% задает список БЭМ-сущностей, которые необходимо **примешать** к данной сущности. Сущность, в рамках которой выполняется примешивание, называется **базовой**, а добавляемая сущность — **примешиваемой**. Имеет смысл примешивание блоков и элементов. Технически примешивание сводится к следующим операциям: * БЭМ-классы примешиваемой сущности добавляются в значение атрибута %%class%% текущего элемента наряду с классами базовой сущности. * Если примешиваемая сущность имеет JavaScript-параметры, они добавляются в значение атрибута, заданного модой %%jsAttr%%. JavaScript-параметры передаются в виде хэша, ключом является имя примешиваемой сущности. Все прочие составляющие HTML-элемента (тег, атрибуты и под.) генерируются на основании шаблонов для базовой сущности. Значением тела шаблона для данной моды может быть: * **Массив**, в котором содержится список объектов (хэшей), каждый из которых описывает БЭМ-сущности, которые необходимо подмешать. * **Объект**, описывающий примешиваемую БЭМ-сущность. Интерпретируется как массив, состоящий из одного элемента. Фрагмент HTML, за генерацию которого отвечает мода %%mix%%, выделен на рисунке: file:mode-mix.png Определение шаблона по моде %%mix%% (подпредикат %%mix%%) требуется, когда необходимо выполнить примешивание блока или элемента на уровне шаблонизатора. ---- !!**NB**!! Примешивание БЭМ-сущностей выполняется рекурсивно. Иными словами, если для примешиваемой сущности определен шаблон, в котором к ней примешиваются еще какие-либо сущности, все такие сущности добавляются рекурсивно и классы для них появятся в атрибуте %%class%% базовой сущности (см. пример ниже). ---- #| || **Входные данные** | **Шаблон** | **HTML-результат** || || %%(js) { block: 'b1' js: { p: 1 } } %% | %%(js) block b1, mix: ({ block: 'b2', js: { p: 2 } }) %% | %%(html) %%|| || %%(js) { block: 'b1' } %% | %%(js) block b1, mix: [ { block: 'b2' } ] block b2, mix: [ { block: 'b3' } ] block b3, mix: [ { block: 'b4' } ] block b4, mix: [ { block: 'b1' } ] %%| %%(html) %%|| |# ==== jsAttr !!(зел)Тип значения тела шаблона: %%String%%!! !!(зел)Значение по умолчанию: %%'onclick'%%!! Мода %%jsAttr%% определяет имя HTML-атрибута, в значении которого будут переданы параметры клиентского JavaScript для данного блока. По умолчанию используется атрибут %%onclick%%. Фрагмент HTML, за генерацию которого отвечает мода %%jsAttr%%, выделен на рисунке: file:mode-jsattr.png Определение шаблона по моде %%jsAttr%% (подпредикат %%jsAttr%%), необходимо в том случае, если требуется передавать параметры JavaScript в нестандартном атрибуте. Например, для тач-сайтов в этих целях используется атрибут %%ondblclick%%. #| || **Входные данные** | **Шаблон** | **HTML-результат** || || %%(js) { block: 'b1', js: true } %% | %%(js) block b1, jsAttr: 'ondblclick' %% | %%(html) %%|| |# ==== attrs !!(зел)Тип значения тела шаблона: %%Object%%!! !!(зел)Значение по умолчанию: %%{}%%!! Мода %%attrs%% позволяет задать имена и значения произвольных HTML-атрибутов для данного элемента. По умолчанию дополнительные атрибуты не генерируются. Фрагмент HTML, за генерацию которого отвечает мода %%attrs%%, выделен на рисунке: file:mode-attrs.png Значением тела шаблона для данной моды должен быть объект (хэш), содержащий имена и значения атрибутов в качестве пар ключ—значение. В качестве ключа должен выступать валидный идентификатор HTML-атрибута, а в качестве значения — строка или число. При выводе HTML специальные символы в значениях атрибутов экранируются вспомогательной функцией %%this._.attrEscape()%%. ---- !!**NB**!! Если в качестве значения атрибута указать %%undefined%%, этот атрибут не будет выведен в HTML-элементе. ---- Определение шаблона по моде %%attrs%% (подпредикат %%attrs%%) необходимо во всех случаях, когда требуется: * добавить произвольные HTML-атрибуты на уровне шаблонизатора; * исключить указанные атрибуты из вывода, даже если они были определены во входном BEMJSON. #| || **Входные данные** | **Шаблон** | **HTML-результат** || || %%(js) { block: 'logo', } %% | %%(js) block logo { tag: 'img' attrs: ({alt: 'logo', href: 'http://...'}) } %% | %%(html) %%|| || %%(js) { block: 'input', disabled: true } %% | %%(js) block input { tag: 'input' attrs: ({ disabled: this.ctx.disabled ? 'disabled' : undefined }) } %% | %%(html) %%|| || %%(js) { block: 'input' } %% | Тот же шаблон | %%(html) %%|| |# ==== content !!(зел)Тип значения тела шаблона: %%BEMJSON%%!! !!(зел)Значение по умолчанию: %%this.ctx.content%%!! В рамках моды %%content%% вычисляется содержимое HTML-элемента, в качестве которого может выступать произвольный BEMJSON (как строка или число, так и дерево БЭМ-сущностей). В качестве значения по умолчанию используется значение поля %%content%% контекстной БЭМ-сущности (%%this.ctx.content%%). Фрагмент HTML, за генерацию которого отвечает мода %%content%%, выделен на рисунке: file:mode-content.png Определение шаблона по моде %%content%% (подпредикат %%content%%) необходимо, если: * Необходимо на уровне шаблонизатора добавить содержимое для сущности, у которой отсутствует %%content%% во входном BEMJSON. * Необходимо подменить содержимое сущности на уровне шаблонизатора. #| || **Входные данные** | **Шаблон** | **HTML-результат** || || %%(js) { block: 'b1' } %% | %%(js) block b1, content: ({ block: 'b2' }) %% | %%(html)