EN RU
Форум

Методология

Технологии

Инструментарий

Библиотеки

Учебные материалы

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>