=== Введение **Данный документ** представляет собой справочное руководство по шаблонизатору 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) logo %%|| || %%(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)
%% || |# **См. также**: * ((#nasledovanie Наследование)). * ((#dobavleniebjem-sushhnostejjdljazadachvjorstki Добавление БЭМ-сущностей для задач верстки)). === Поля контекста В процессе работы шаблонизатор BEMHTML строит структуру данных, содержащую сведения об обрабатываемом узле BEMJSON и о состоянии процесса обработки. Помимо этого в контексте доступен ряд вспомогательных функций BEMHTML. В момент выполнения шаблона контекст доступен в виде объекта, обозначаемого ключевым словом %%this%%. Обращение к контексту возможно как в предикате, так и в теле шаблона. Автор шаблонов имеет возможность определить любые дополнительные поля в контексте. Все поля контекста можно разделить на две категории: * **Контекстно-зависимые**, значение которых изменяется в зависимости от обрабатываемого узла и фазы процесса обработки. * **Контекстно-независимые**, значение которых постоянно. **См. также**: * ((#kontekst Контекст)) ==== Контекстно-зависимые поля #| || **Поле**| **Тип значения** | **Описание** || || %%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%% | Проверяет, является ли данная БЭМ-сущность последней среди сиблингов во входном БЭМ-дереве. Подробнее см. ((#algoritmvychislenijapoziciibjem-sushhnosti Алгоритм вычисления позиции БЭМ-сущности)). || || %%this.generateId()%% | %%Number%% | Возвращает уникальный идентификатор для текущего контекста. Используется, когда нужно сгенерировать HTML-элементы, связанные с помощью атрибута %%id%%. || |# ---- !!**NB**!! Ключевые слова для проверки БЭМ-сущностей в предикате являются сокращенной записью для проверки значений полей %%block%%, %%elem%% и т.д. в текущем контексте. Например, подпредикат %%block b1%% эквивалентен подпредикату %%this.block === 'b1'%%. Аналогично, ключевые слова для проверки моды в предикате являются сокращенной записью для проверки значения служебного поля %%_mode%% в текущем контексте. Например, подпредикат %%tag%% эквивалентен подпредикату %%this._mode === 'tag'%%. ---- ===== Достраивание БЭМ-сущностей по контексту В BEMJSON принято записывать БЭМ-сущности в свернутом виде. Например, если в блок %%menu%% вложен элемент %%item%%, в объекте, описывающем пункт меню, не указывается имя содержащего его блока меню: %%(js) { block: 'menu', content: { elem: 'item' } } %% Информация о том, что элемент %%item%% принадлежит блоку %%menu%%, достраивается по контексту (на основании вложенности) в процессе работы шаблонизатора. В момент, когда контекстной сущностью является блок %%menu%%, в полях контекста будут выставлены следующие значения: %%(js) this.block: 'menu' this.ctx.block: 'menu' %% В момент входа во вложенный элемент %%item%%, в поле %%this.block%% достраивается значение %%menu%%. В то же время в поле %%this.ctx.block%% находится значение %%undefined%%, так как во входном BEMJSON это поле в элементе %%item%% не определено: %%(js) this.block: 'menu' this.elem: 'item' this.ctx.block: undefined this.ctx.elem: 'item' %% Достраивание выполняется также для элементов, примешанных внутри блоков. Например, в приведенном БЭМ-дереве: %%(js) { block: 'b1', mix: { elem: 'e1' } } %% В примешанном элементе будет достроено имя блока: %%(js) { block: 'b1', mix: { block: 'b1', elem: 'e1' } } %% Достраивание БЭМ-сущностей необходимо для корректного срабатывания предикатов на элементы блоков вида %%(js)block menu, elem item%%, так как в таких предикатах проверяются значения полей контекста %%this.block%% и %%this.elem%%. ---- !!**NB**!! Чтобы избежать срабатывания предикатов вида %%block menu%% внутри вложенных в блок элементов, на этапе компиляции шаблонов к таким предикатам в необходимых случаях автоматически добавляется подпредикат %%!this.elem%%. Автоматическое добавление может не сработать, если предикат шаблона содержит подпредикат с произвольным условием, записанный не в ((#xjst-canonical канонической форме XJST)). ---- ===== Алгоритм вычисления позиции БЭМ-сущности Позиция в БЭМ-дереве (поле контекста %%this.position%%) представляет собой натуральное число, соответствующее порядковому номеру текущей (контекстной) БЭМ-сущности среди ее сиблингов в БЭМ-дереве (одноранговых сущностей). При вычислении позиции: * Нумеруются только те узлы обрабатываемого BEMJSON, которые соответствуют БЭМ-сущностям, прочим узлам не соответствует никакой номер позиции. * Позиции нумеруются начиная с 1. * Нумерация производится в порядке обхода дерева (уплощенный список иерархического представления BEMJSON). Пример нумерации позиций во входном БЭМ-дереве: %%(js) { 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()%% **не сработает** в том случае, если в массиве, содержащем одноранговые БЭМ-сущности, последний элемент не является БЭМ-сущностью. Например: %%(js) { 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-ориентированный формат). В случае френдленты формат исходных данных может быть таким: %%(js) { posts: [ { text: 'post text', author: 'login' }, … ], users: { 'login': { userpic: 'URL', name: 'Full Name' }, … }, } %% Данные представлены как два списка объектов разных типов. В списке постов используется только идентификатор пользователя, а полная информация о пользователе находится в соответствующем хеше в списке пользователей. Формат данных, ориентированный на представление, предполагает денормализацию данных, т.е. развертывание списка постов таким образом, чтобы в каждом посте содержалась полная информация об авторе, даже если в списке присутствует несколько постов одного автора. В BEMJSON подобный формат может выглядеть так: %%(js) { block: 'posts', content: [ { block: 'post', content: [ { block: 'userpic', content: 'URL' }, { block: 'user', content: 'Full Name' }, { elem: 'text', content: 'post text' } ] }, … ] } %% ==== Выбор шаблона по условию ===== Задача Блок %%b-link%% встречается в двух разновидностях: * %%(js){ block: 'b-link', content: 'ссылка без URL' }%% * %%(js){ block: 'b-link', url: '//ya.ru', content: 'ссылка с URL' }%% Необходимо по-разному оформить выходной HTML-элемент в зависимости от наличия/отсутствия поля %%url%% в данных блока. ===== Решение Следует сделать проверку на наличие поля %%url%% подпредикатом шаблона: выражение %%this.ctx.url%% будет истинным, только если поле %%url%% определено. %%(js) block b-link { tag: 'span' this.ctx.url { tag: 'a' attrs: { href: this.ctx.url } } } %% **Неправильно** использовать для решения этой задачи условные конструкции JavaScript в теле шаблона: %%(js) block b-link, tag: this.ctx.url ? 'a' : 'span' %% При компиляции это выражение не будет оптимизировано, что отрицательно скажется на скорости работы шаблона. **См. также**: * ((#shablon1 Синтаксис шаблонов)) ==== Наследование ===== Задача На разных ((http://bem.github.com/bem-method/pages/beginning/beginning.ru.html#Urovnipereopredeleniy уровнях переопределения)) определены два различных шаблона на одну и ту же БЭМ-сущность (%%block b1%%). Каждый из шаблонов определяет своё содержимое по моде %%content%%. Необходимо на втором уровне переопределения **унаследовать** содержимое, определённое на первом уровне, и добавить дополнительное. Требуется аналог %%%%. ===== Решение В BEMHTML есть аналог %%%%. Реализация основывается на возможности заново запустить в шаблоне процедуру применения шаблонов к текущему контексту (%%apply()%%). Таким образом можно вызвать тот шаблон, который был определен для данного контекста (БЭМ-сущности, моды и т.п.) ранее или на другом уровне переопределения. При вычислении выражения %%apply()%% возвращается результат, полученный в ходе применения ранее определенного шаблона. Для избежания бесконечного цикла необходимо добавить подпредикат проверки наличия в контексте какого-то флага (например, %%_myGuard%%), который будет выставлен при выполнении %%apply()%%. %%(js) // шаблон на первом уровне переопределения block b1, content: 'text1' // шаблон на втором уровне переопределения block b1, content, !this._myGuard: [ apply(this._myGuard = true), // получаем предыдущее значение content 'text2' ] %% В результате применения шаблонов к блоку %%b1%% будет получен HTML: %%(html)
text1text2
%% В bem-bl версии 0.3 добавлена конструкция %%applyNext%%, которая автоматически генерирует уникальное имя флага против зацикливания. %%(js) block b1, content: 'text1' block b1, content: [ applyNext(), // получаем предыдущее значение content 'text2' ] %% **См. также**: * ((#applynext Конструкция applyNext)) ==== Оборачивание блока в другой блок ===== Задача Необходимо вложить блок (%%b-inner%%) в другой блок (%%b-wrapper%%) при выполнении шаблона. Таким образом, одному входному блоку будет соответствовать два вложенных друг в друга блока. ===== Решение При обработке блока %%b-inner%% в шаблоне по моде %%default%% (генерация целого элемента) следует модифицировать фрагмент входного дерева %%this.ctx%%, куда и добавляется блок %%b-wrapper%% и рекурсивно запустить вызов шаблонов по пустой моде %%apply(this._mode = "")%%. Для избежания бесконечного цикла необходимо при вызове выражения %%apply()%% проверять наличие в контексте специального флага (%%_wrap%%), который будет выставлен при выполнении %%apply()%%. %%(js) 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%% и применяет шаблоны по пустой моде: %%(js) block b-inner, default: applyCtx({ block: 'b-wrapper', content: this.ctx }) %% ---- !!**NB**!! Конструкцию %%applyCtx()%% можно применять для **замены** БЭМ-сущности в исходном дереве, если не использовать исходное содержимое блока (%%this.ctx%%) в аргументе %%applyCtx()%%. ---- **См. также**: * ((#applyctx Конструкция applyCtx)) ==== Добавление БЭМ-сущностей для задач вёрстки ===== Задача Необходимо сверстать блок с закруглёнными уголками, работающий во всех браузерах (без использования CSS3). Входной BEMJSON может быть таким: %%(js) { block: 'box', content: 'text' } %% Реализация уголков требует добавления к блоку четырех дополнительных элементов. Поскольку данные элементы отражают детали HTML-верстки, ими не следует загромождать входное БЭМ-дерево. Добавить эти элементы следует на уровне BEMHTML-шаблона. Финальное БЭМ-дерево должно выглядеть так: %%(js) { 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-шаблон, выполняющий это преобразование: %%(js) 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 добавлен короткий синтаксис для применения шаблонов по моде: %%(js) block b-source, default: apply("", this.ctx.block = 'b-target') %% **См. также**: * ((#apply Конструкция apply)) * ((#applynext Конструкция applyNext)) * ((#applyctx Конструкция applyCtx)) ==== Использование позиции БЭМ-сущности ===== Задача Необходимо пронумеровать пункты меню, начиная с 1. В текст каждого элемента меню нужно добавить его порядковый номер с точкой. ===== Решение Используем механизм вычисления позиции БЭМ-сущности среди сиблингов (поле контекста %%this.position%%). Входные данные могут выглядеть так: %%(js) { block: 'menu', content: [ { elem: 'item', content: 'aaa' }, { elem: 'item', content: 'bbb' }, { elem: 'item', content: 'ccc' }, ] } %% Для выполнения нумерации следует написать шаблон по моде %%content%% на пункт меню, в котором содержание элемента будет составлено из номера позиции, разделителя (точки с пробелом) и исходного текста элемента (полученного с помощью конструкции %%applyNext()%%): %%(js) block menu { tag: 'ul' elem item { tag: 'li' content: [ this.position, '. ', applyNext() ] } } %% **См. также**: * ((#content Мода content)) * ((#applynext Конструкция applyNext)) ==== Проверка подпредикатов в определенном порядке ===== Задача Необходимо проверять подпредикаты шаблона в строго определенном порядке, например, сначала проверить наличие в контексте объекта %%this.world%%, а затем проверить значение поля в этом объекте %%this.world.answer%%. ===== Решение Воспользуемся тем, что подпредикат шаблона BEMHTML может быть произвольным JavaScript-выражением и запишем его в следующей форме: %%(js) (this.world && this.world.answer === 42) %% Недостаток этого решения в том, что при компиляции это выражение не будет оптимизировано, что отрицательно скажется на скорости работы шаблона. В большинстве случаев можно и нужно избегать необходимости в строгом порядке проверки подпредикатов. ==== Связывание HTML-элементов по id ===== Задача Необходимо для входного блока %%input%% сгенерировать пару HTML-элементов %%