BH — это BEMJSON-процессор, который превращает BEMJSON в HTML. Одним словом, это шаблонизатор.
- Быстрый.
- Не требует компиляции.
- Удобен в отладке, т.к. не компилируется в другой код.
- Написан на чистом JavaScript, используется и расширяется через JavaScript.
- Прост для понимания, т.к. это обертка над обычными преобразованиями исходного BEMJSON в конечный BEMJSON / HTML.
- Компактен на клиенте (12,4 Кб после сжатия, 3,7 Кб после gzip).
BH-процессор можно найти в npm-пакете bh, а ENB-технологии для его использования — в npm-пакете enb-bh.
npm install bhBH-файлы в проекте имеют суффикс bh.js (например, page.bh.js). Файл формируется в формате CommonJS для NodeJS:
module.exports = function(bh) {
// ...
};Для преобразования исходного дерева BEMJSON в конечный HTML используется метод apply.
Для получения промежуточного результата в виде развернутого BEMJSON-дерева нужно использовать метод processBemJson.
Для получения конечного HTML без преобразования BEMJSON-дерева используется метод toHtml.
Простой пример использования:
var bh = new (require('bh').BH);
bh.match('button', function(ctx) {
ctx.tag('button');
})
bh.processBemJson({ block: 'button' }); // { block: 'button', mods: {}, tag: 'button' }
bh.apply({ block: 'button' }); // '<button class="button"></button>'
bh.toHtml({ block: 'button' }); // '<div class="button"></div>'Функции для работы с BEMJSON — шаблоны — объявляются через метод match. В теле функций описывается логика преобразования BEMJSON.
В функцию-шаблон передаются два аргумента: ctx — экземпляр класса Ctx и json — ссылка на текущий узел BEMJSON-дерева.
Замечание: Категорически не рекомендуется вносить изменения напрямую в объект json. Вместо этого следует использовать методы объекта ctx. Объект json рекомендуется использовать только для «чтения» (см. также метод ctx.json()).
Синтаксис:
{BH} bh.match({String} expression, function({Ctx} ctx, {Object} json) {
//.. actions
});Также допустимо объявлять несколько шаблонов в одном вызове метода match.
Синтаксис:
{BH} bh.match({Array} expressions, function({Ctx} ctx));Где expressions — массив вида:
[
{String} expression1,
...,
{String} expressionN
]Или в виде объекта:
{BH} bh.match({Object} templates);Где templates представляет собой объект вида:
{
{String} expression1 : function({Ctx} ctx) {
//.. actions1
},
...,
{String} expressionN : function({Ctx} ctx) {
//.. actionsN
},
}Ниже в этом документе можно найти перечень методов класса Ctx. Дальше пойдем по примерам.
Например, зададим блоку button тег button, а блоку input тег input:
bh.match('button', function(ctx) {
ctx.tag('button');
});
bh.match('input', function(ctx) {
ctx.tag('input');
});Теперь нам нужна псевдо-кнопка. То есть, если у кнопки модификатор pseudo равен yes, то нужен тег a и атрибут role="button":
bh.match('button_pseudo_yes', function(ctx) {
ctx
.tag('a')
.attr('role', 'button');
});В данном примере мы матчимся не просто на блок button, а на блок button с модификатором pseudo, имеющим значение yes.
Рассмотрим синтаксис строки матчинга для функций преобразования (в квадратных скобках указаны необязательные параметры):
'block[_blockModName[_blockModVal]][__elemName][_elemModName[_elemModVal]]'По-русски:
'блок[_имяМодификатораБлока[_значениеМодификатораБлока]][__имяЭлемента][_имяМодификатораЭлемента[_значениеМодификатораЭлемента]]'Метод setOptions позволяет задавать параметры шаблонизации.
Позволяет задать имя атрибута для хранения поля js. Значение по умолчанию — onclick.
bh.setOptions({ jsAttrName: 'data-bem' });
bh.apply({ block: 'button', js: true });<div class="button i-bem" data-bem='return {"button":{}}'></div>Формат хранения данных в атрибуте. По умолчанию js
bh.setOptions({ jsAttrScheme: 'json' });
bh.apply({ block: 'button', js: { foo: bar } });<div class="button i-bem" onclick='{"button":{"foo":"bar"}}'></div>Имя дополнительного класса для узлов, имеющих js. По умолчанию i-bem.
Если передать значение false, дополнительный класс не будет добавляться.
bh.setOptions({ jsCls: false });
bh.apply({ block: 'button', js: true });<div class="button" onclick='{"button":{}}'></div>Регулирует установку дополнительного класса, указанного в параметре jsCls, для элемента с js-реализацией. По умолчанию true. Если задать значение false, дополнительный класс добавляться не будет.
bh.setOptions({ jsElem: false });
bh.apply({ block: 'button', elem: 'box', js: true });<div class="button__box" onclick='return {"button__box":{}}'></div>Включает эскейпинг содержимого. По умолчанию выключен.
bh.setOptions({ escapeContent: true });
bh.apply({ content: '<script>' });<div><script></div>Удаляет имя блока и/или элемента из имен модификаторов в классе. По умолчанию false.
bh.setOptions({ clsNobaseMods: true });
bh.apply({
block: 'button',
mods: { disabled: true, theme: 'new' },
mix: [
{ block: 'clearfix' },
{ elem: 'box', elemMods: { pick: 'left' } }
],
content: {
elem: 'control',
elemMods: { disabled: true }
}
});<div class="button _disabled _theme_new clearfix button__box _pick_left">
<div class="button__control _disabled"></div>
</div>Задает разделитель между блоком и элементом. По умолчанию __.
bh.setOptions({ delimElem: '_' });
bh.apply({ block: 'button', elem: 'text' });<div class="button_text"></div>Задает разделитель между блоком или элементом и их модификатором. По умолчанию _.
bh.setOptions({ delimMod: '--' });
bh.apply({ block: 'button', mods: { disabled: true } });<div class="button button--disabled"></div>shortTags расширяет стандартный набор коротких тегов.
bh.setOptions({ shortTags: ['rect'] });Например, мы хотим установить модификатор state со значением closed для всех блоков popup:
bh.match('popup', function(ctx) {
ctx.mod('state', 'closed');
});Замиксуем form с search-form:
bh.match('search-form', function(ctx) {
ctx.mix({ block: 'form' });
});Установим класс для page:
bh.match('page', function(ctx) {
ctx.cls('ua_js_no ua_css_standard');
});Кроме модификации элемента, функция-преобразователь может вернуть новый BEMJSON. Здесь мы воспользуемся методами ctx.json() (возвращает текущий элемент BEMJSON «как есть») и ctx.content() (возвращает или устанавливает контент).
Например, обернем блок header блоком header-wrapper:
bh.match('header', function(ctx) {
return {
block: 'header-wrapper',
content: ctx.json()
};
});Замечание: Любое не-undefined значение вставляется в конечное BEMJSON-дерево вместо текущего узла. Соответственно, удалить текущий узел можно просто вернув значение null.
Обернем содержимое button элементом content:
bh.match('button', function(ctx) {
ctx.content({
elem: 'content',
content: ctx.content()
}, true);
});Метод ctx.content принимает первым аргументом BEMJSON, который надо выставить для содержимого, а вторым — флаг force (выставить содержимое, даже если оно уже существует).
Добавим элемент before в начало, а after — в конец содержимого блока header:
bh.match('header', function(ctx) {
ctx.content([
{ elem: 'before' },
ctx.content(),
{ elem: 'after' }
], true);
});Добавим блок before-button перед блоком button:
bh.match('button', function(ctx) {
return [
{ block: 'before-button' },
ctx.json()
];
});Метод enableInfiniteLoopDetection позволяет включать и выключать механизм определения зацикливаний.
Замечание: Рекомендуется включать этот механизм только для отладки, так как он замедляет работу шаблонизатора.
bh.enableInfiniteLoopDetection(true);
bh.match('button', function(ctx) {
ctx.content({ block: 'button' });
});Error: Infinite matcher loop detected at "button".Инстанции класса Ctx передаются во все шаблоны. Все методы класса в set-режиме возвращают инстанцию класса, то есть реализуют чейнинг.
Применяет шаблоны для переданного BEMJSON-дерева в текущем контексте. Возвращает результат преобразований.
bh.match('button', function(ctx) {
bh.toHtml(ctx.process({ elem: 'control' }));
});Возвращает/устанавливает тег в зависимости от аргументов. force — задать значение тега, даже если оно было задано ранее.
bh.match('input', function(ctx) {
ctx.tag('input');
});Замечание: Если передать в качестве значения false или пустую строку, текущий узел не будет выведен в конечный HTML, выведется только его содержимое, если оно есть.
Возвращает/устанавливает модификатор в зависимости от аргументов. force — задать модификатор, даже если он был задан ранее.
bh.match('input', function(ctx) {
ctx.mod('native', 'yes');
ctx.mod('disabled', true);
});
bh.match('input_islands_yes', function(ctx) {
ctx.mod('native', '', true);
ctx.mod('disabled', false, true);
});Возвращает/устанавливает модификаторы в зависимости от аргументов. force — задать модификаторы, даже если они были заданы ранее.
bh.match('paranja', function(ctx) {
ctx.mods({
theme: 'normal',
disabled: true
});
});Возвращает/устанавливает значение атрибута в зависимости от аргументов. force — задать значение атрибута, даже если оно было задано ранее.
bh.match('input_disabled_yes', function(ctx) {
ctx.attr('disabled', 'disabled');
});Замечание: Если необходимо удалить сам атрибут, а не просто обнулить значение атрибута, то вторым параметром надо передать null:
bh.match('link', function(ctx) {
ctx.attr('href', null);
});Замечание: Чтобы задать булевый атрибут, следует передать вторым параметром true:
bh.match('link_hidden_yes', function(ctx) {
ctx.attr('hidden', true);
});Возвращает/устанавливает атрибуты в зависимости от аргументов. force — задать атрибуты, даже если они были заданы ранее.
bh.match('input', function(ctx) {
ctx.attrs({
name: ctx.param('name'),
autocomplete: 'off'
});
});Возвращает/устанавливает значение mix в зависимости от аргументов.
При установке значения если force равен true, то переданный микс заменяет прежнее значение, в противном случае миксы складываются.
bh.match('button_pseudo_yes', function(ctx) {
ctx.mix({ block: 'link', mods: { pseudo: 'yes' } });
ctx.mix([
{ elem: 'text' },
{ block: 'ajax' }
]);
});Возвращает/устанавливает значение bem в зависимости от аргументов. force — задать значение bem, даже если оно было задано ранее.
Если bem имеет значение false, то для элемента не будут генерироваться БЭМ-классы.
bh.match('meta', function(ctx) {
ctx.bem(false);
});Возвращает/устанавливает значение js в зависимости от аргументов. force — задать значение js, даже если оно было задано ранее.
Значение js используется для инициализации блоков в браузере через BEM.DOM.init().
bh.match('input', function(ctx) {
ctx.js(true);
});Возвращает/устанавливает дополнительное значение CSS-класса в зависимости от аргументов. force — задать значение cls, даже если оно было задано ранее.
bh.match('field_type_email', function(ctx) {
ctx.cls('validate');
});<div class="field field_type_email validate"></div>Возвращает/устанавливает содержимое в зависимости от аргументов. force — задать содержимое, даже если оно было задано ранее.
bh.match('input', function(ctx) {
ctx.content({ elem: 'control' });
});Возвращает текущий фрагмент BEMJSON-дерева. Может использоваться в связке с return для враппинга и подобных целей. Для сокращения можно использовать второй аргумент функции-шаблона — json.
Замечание: После вызова ctx.applyBase() нарушается цепочка естественного применения шаблонов. Из-за этого json перестает указывать на актуальный узел в BEMJSON-дереве. В этом случае следует использовать ctx.json().
bh.match('input', function(ctx, json) {
return {
elem: 'wrapper',
attrs: { name: json.name },
content: ctx.json()
};
});ctx.position() возвращает позицию текущего BEMJSON-элемента в рамках родительского.
См. пример для ctx.position(), ctx.isFirst() и ctx.isLast().
ctx.isFirst() возвращает true, если текущий BEMJSON-элемент — первый в рамках родительского BEMJSON-элемента.
См. пример для ctx.position(), ctx.isFirst() и ctx.isLast().
ctx.isLast() возвращает true, если текущий BEMJSON-элемент — последний в рамках родительского BEMJSON-элемента.
Пример для ctx.position(), ctx.isFirst() и ctx.isLast():
bh.match('list__item', function(ctx) {
ctx.mod('pos', ctx.position());
if (ctx.isFirst()) {
ctx.mod('first', 'yes');
}
if (ctx.isLast()) {
ctx.mod('last', 'yes');
}
});Проверяет, что объект является примитивом.
bh.match('link', function(ctx) {
ctx.tag(ctx.isSimple(ctx.content()) ? 'span' : 'div');
});Аналог функции extend в jQuery.
Выполняет преобразования данного BEMJSON-элемента остальными шаблонами. Может понадобиться, например, чтобы добавить элемент в самый конец содержимого, если в базовых шаблонах в конец содержимого добавляются другие элементы.
Пример:
bh.match('header', function(ctx) {
ctx.content([
ctx.content(),
{ elem: 'under' }
], true);
});
bh.match('header_float_yes', function(ctx) {
ctx.applyBase();
ctx.content([
ctx.content(),
{ elem: 'clear' }
], true);
});Останавливает выполнение прочих шаблонов для данного BEMJSON-элемента.
Пример:
bh.match('button', function(ctx) {
ctx.tag('button', true);
});
bh.match('button', function(ctx) {
ctx.tag('span');
ctx.stop();
});Возвращает уникальный идентификатор. Может использоваться, например, чтобы задать соответствие между label и input.
Возвращает/устанавливает параметр текущего BEMJSON-элемента. force — задать значение параметра, даже если оно было задано ранее. Например:
bh.match('search', function(ctx) {
ctx.attr('action', ctx.param('action') || '/');
});Получает / передает параметр вглубь BEMJSON-дерева. force — задать значение параметра, даже если оно было задано ранее.
bh.match('input', function(ctx) {
ctx.content({ elem: 'control' });
ctx.tParam('value', ctx.param('value'));
});
bh.match('input__control', function(ctx) {
ctx.attr('value', ctx.tParam('value'));
});