Runtime
Как работает runtime шаблонизатора
Шаблонизатор bem-xjst обладает встроенным механизмом обхода дерева входных данных. Это дерево рекурсивно обходится в прямом порядке и для каждого узла ищется подходящий шаблон.
В теле шаблона текущий узел может быть подменён на другой или дополнен новыми. В этом случае шаблонизатор попытается примерить шаблоны на все новые узлы.
Если вы не добавили ни одного шаблона, то bem-xjst сгенерирует результат по умолчанию. Это поведение описано в разделе про режимы.
Как выбираются и применяются шаблоны?
Шаблоны находятся в упорядоченном списке. Шаблоны проверяются в обратном порядке, то есть последние шаблоны приоритетнее первых (с учетом особенностей wildcard).
Для каждого узла входного дерева шаблонизатор проверяет предикат каждого шаблона. Для этого выполняются все подпредикаты в контексте узла. Если все подпредикаты вернули true
, дальнейший поиск шаблона прекращается и выполняется тело текущего.
Если шаблон не найден, то будет использовано поведение по умолчанию.
Шаблоны на любые сущности
В предикатах вы можете писать *
вместо имени блока или элемента. Например, это может быть полезно для единообразной обработки всех блоков.
Входной BEMJSON:
[
{ block: 'header' },
{ block: 'link', mix: [{ block : 'title' }], counter: '1549865' },
{ block: 'snippet', counter: '1549865' }
]
Шаблон:
block('*')
.match((node, ctx) => ctx.counter)({
mix: (node, ctx) => ({
block: 'counter', js: { id: ctx.counter }
})
})
Результат шаблонизации:
<div class="header"></div>
<div class="link counter title i-bem" data-bem='{"counter":{"id":"1549865"}}'></div>
<div class="snippet counter i-bem" data-bem='{"counter":{"id":"1549865"}}'></div>
Шаблон с подпредикатом на *
будет приоритетнее, чем шаблон с конкретным именем в подпредикате. Это связано с оптимизацией производительности. На практике тяжело столкнуться с ситуациями, когда это имеет значение.
Подпредикат блока на *
будет истинным на пустой объект.
Инструкции управления
apply
/**
* @params {String} modeName название режима
* @params {Object} [changes] объект, которым будет расширен BEMContext контекст выполнения шаблонов
* @returns {*} Возвращает результат работы режима
*/
apply(modeName, changes)
Применяется для вызова стандартного или пользовательского режима текущего узла.
{ block: 'button' }
Шаблон:
block('button')({
test: (node, ctx) => node.tmp + ctx.foo,
def: () => apply('test', {
tmp: 'ping',
'ctx.foo': 'pong'
})
});
Результат шаблонизации:
pingpong
Используя пользовательский режим (например, size
) и его вызов через apply('size')
можно переопределять его на разных уровнях переопределения, либо в разных условиях (в комбинации с match
).
Предположим у вас есть три уровня переопределения: common, desktop и touch. На общем уровне common у вас есть шаблон:
block('my-block')({
size: 'm',
content: () => ({
block: 'button',
size: apply('size')
})
});
На touch уровне переопределения можно указать:
```js
block('my-block').mode('size')('l');
На desktop уровне переопределения можно указать:
block('my-block').mode('size')('s');
С помощью apply
нельзя вызывать пользовательские режимы для других блоков.
// BEMJSON
[
{ block: 'header' },
{ block: 'footer' }
]
Шаблон:
block('footer')({ custom: 'footer' });
block('header')({
custom: 'header',
// несмотря на то, что вторым аргументом apply явно указан блок footer
// будет вызван пользовательский режим блока `header`.
tag: () => apply('custom', { block: 'footer' })
});
Результат шаблонизации:
<header class="header"></header>
<div class="footer"></div>
Если будет вызван apply()
для режима для которого нет шаблонов, то
шаблонизатор попытается извлечь значение поля с соответствующим названием из
текущего узла BEMJSON-а.
// BEMJSON
{ block: 'animal', type: 'cat' }
Шаблон:
block('animal')({
content: () => apply('type')
});
Результат шаблонизации:
<div class="animal">cat</div>
applyNext
/**
* @param {Object} [changes] объект, которым будет расширен BEMContext шаблонов
* @returns {*}
*/
applyNext(changes)
Конструкция applyNext
возвращает результат работы следующего по приоритету шаблона в текущем режиме для текущего узла.
Пример
block('link')({ tag: 'a' });
block('link')({
tag: () => {
var res = applyNext(); // res === 'a'
return res;
}
});
Аргумент changes
позволяет изменить контекст выполнения шаблонов для всех
дочерних узлов.
block('page')({ content: (node) => node._type });
block('page')({
// здесь applyNext вызовет предыдущий шаблон с _type в контексте
content: () => applyNext({ _type: '404' })
});
Для BEMJSON-а { block: 'page' }
результат применения этих шаблонов будет:
<div class="page">404</div>
Также вы можете пробросить любые данные для шаблонов дочерних узлов. См. пример
applyCtx
/**
* @param {BEMJSON} bemjson входные данные
* @param {Object} [changes] объект, которым будет расширен BEMContenxt шаблонов
* @returns {String}
*/
applyCtx(bemjson, changes)
Конструкция applyCtx
предназначена для модификации текущего фрагмента БЭМ-дерева this.ctx
с вызовом процедуры применения шаблонов apply()
.
{ block: 'header', mix: [{ block: 'sticky' }] }
Шаблон:
block('header')({
def: (node, ctx) => applyCtx(node.extend(ctx, {
block: 'layout',
mix: [{ block: 'header' }].concat(ctx.mix || [])
}));
});
Результат шаблонизации:
<div class="layout header sticky"></div>