BH — это BEMJSON-процессор. Его главная цель — превратить BEMJSON в HTML.
- BH быстрый. Очень быстрый.
- BH не требует компиляции (в отличие от BEMHTML).
- BH удобен в отладке, т.к. он не компилируется в другой код (в отличие от BEMHTML).
- BH написан на чистом JavaScript, используется и расширяется через JavaScript.
- BH прост для понимания, ведь это всего лишь обертка над обычными преобразованиями исходного BEMJSON в конечный BEMJSON.
- BH компактен на клиенте.
Скорость обработки BEMJSON (1000 итераций).
bemhtml - 3184ms (314 times per second)
bh - 1214ms (824 times per second)
BEM.HTML - 1427ms (701 times per second)
Вес результирующего файла (сжатого):
BH - 40kb.
BEMHTML - 76kb.
Время сборки.
BH - 50-60ms.
BEMHTML (dev) - 2000-3000ms.
BEMHTML (prod) - 6000-7000ms.
BH-процессор и ENB-технологии для его использования можно найти в npm-пакете bh
.
npm install bh
BH-файлы в проекте имеют суффикс bh.js
. Например, page.bh.js
. Файл формируется в формате CommonJS для NodeJS:
module.exports = function(bh) {
// ...
};
Функции для работы с BEMJSON ( матчеры ) объявляются через метод match
. В теле функций описывается логика преобразования BEMJSON.
Синтаксис:
{BH} bh.match({String} expression, function({Ctx} ctx) {
//.. actions
});
Также допустимо использовать несколько матчеров в одном вызове метода match
.
Синтаксис:
{BH} bh.match({Object} matchers);
Где matchers
представляет собой объект вида:
{
{String} expression1 : function({Ctx} ctx) {
//.. actions1
},
...,
{String} expressionN : function({Ctx} ctx) {
//.. actionsN
},
}
Ниже в этом документе можно найти перечень методов класса Ctx. Дальше пойдем по примерам.
Например, зададим блоку button
тег button
, а блоку input
тег input
:
module.exports = function(bh) {
bh.match('button', function(ctx) {
ctx.tag('button');
});
bh.match('input', function(ctx) {
ctx.tag('input');
});
};
Теперь нам нужна псевдо-кнопка. То есть, если у кнопки модификатор pseudo
равен yes
, то нужен тег a
и атрибут role="button"
:
module.exports = function(bh) {
bh.match('button_pseudo_yes', function(ctx) {
ctx.tag('a');
ctx.attr('role', 'button');
});
};
В данном примере мы матчимся не просто на блок button
, а на блок button
с модификатором pseudo
, имеющим значение yes
.
Рассмотрим синтаксис строки матчинга для функций преобразования:
'block[_blockModName[_blockModVal]][__elemName][_elemModName[_elemModVal]]'
По-русски:
'блок[_имяМодификатораБлока[_значениеМодификатораБлока]][__имяЭлемента][_имяМодификатораЭлемента[_значениеМодификатораЭлемента]]'
(В квадратных скобках необязательные параметры)
Например, мы хотим установить модификатор 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()
};
});
Обернем содержимое 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()
];
});
Это возможно в BH. Но, пожалуйста, не делайте этого. Пожалейте тех, кто после вас столкнется с горой ошибок из-за того, что поменялся матчер, который вы неявно использовали. Я рекомендую класть общий функционал в объект bh.lib
и расшаривать общие методы обработки данных вместо того, чтобы "обманывать систему".
Окей, вы все это прочитали, но остались непреклонны?
bh.match('corners', function(ctx) {
ctx.content([
ctx.content(),
{ block: 'corners', elem: 'tl' },
{ block: 'corners', elem: 'tr' },
{ block: 'corners', elem: 'bl' },
{ block: 'corners', elem: 'br' }
], true);
});
bh.match('button', function(ctx) {
ctx.applyBase({ block: 'corners' });
ctx.mix({ block: 'corners' });
// Crossing fingers.
});
Но лучше использовать другой подход:
bh.lib.corners = bh.lib.corners || {};
bh.lib.corners.add = function(ctx) {
ctx.mix({ block: 'corners' });
ctx.content([
ctx.content(),
{ block: 'corners', elem: 'tl' },
{ block: 'corners', elem: 'tr' },
{ block: 'corners', elem: 'bl' },
{ block: 'corners', elem: 'br' }
], true);
});
bh.match('button', function(ctx) {
bh.lib.corners.add(ctx);
});
Инстанции класса Ctx
передаются во все матчеры. Рассмотрим методы класса:
Возвращает/устанавливает тег в зависимости от аргументов. force — задать значение тега даже если оно было задано ранее.
bh.match('input', function(ctx) {
ctx.tag('input');
});
Возвращает/устанавливает модификатор в зависимости от аргументов. 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);
});
Возвращает/устанавливает атрибуты в зависимости от аргументов. 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, то для элемента не будут генерироваться BEM-классы.
bh.match('meta', function(ctx) {
ctx.bem(false);
});
Возвращает/устанавливает значение js в зависимости от аргументов. force — задать значение js даже если оно было задано ранее.
Значение js используется для инициализации блоков в браузере через BEM.DOM.init()
.
bh.match('input', function(ctx) {
ctx.js(true);
});
Возвращает/устанавливает содержимое в зависимости от аргументов. force — задать содержимое даже если оно было задано ранее.
bh.match('input', function(ctx) {
ctx.content({ elem: 'control' });
});
Возвращает текущий фрагмент BEMJSON-дерева. Может использоваться в связке с return
для враппинга и подобных целей.
bh.match('input', function(ctx) {
return {
elem: 'wrapper',
content: ctx.json()
};
});
ctx.position() возвращает позицию текущего BEMJSON-элемента в рамках родительского. ctx.isFirst() возвращает true, если текущий BEMJSON-элемент первый в рамках родительского BEMJSON-элемента. ctx.isLast() возвращает true, если текущий BEMJSON-элемент последний в рамках родительского BEMJSON-элемента.
Пример:
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-дерева. Например:
bh.match('input', function(ctx) {
ctx.content({
elem: 'control'
}, true);
ctx.tParam('value', ctx.param('value'));
});
bh.match('input__control', function(ctx) {
ctx.attr('value', ctx.tParam('value'));
});