EN RU
Форум

Методология

Технологии

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

Библиотеки

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

Что доступно в теле шаблона?

В процессе обхода входных данных bem-xjst строит контекст, который будет содержать:

Функции шаблонов

В случае, когда тело шаблона является функцией, она вызывается с двумя аргументами:

Пример

block('link')({
    attrs: function(node, ctx) {
        return {
            // тоже самое что и this.ctx.url
            href: ctx.url,

            // тоже самое что и this.position
            'data-position': node.position
        };
    }
});

Такие же аргументы доступны в функции подпредикате match().

match(function(node, ctx) {
    // тоже самое что и this.mods.disabled
    return !node.mods.disabled &&
        // тоже самое что и this.ctx.target
        ctx.target;
})

Кроме того, функции шаблонов поддерживают ES6 arrow functions, поэтому вы можете писать везде в таком стиле:

match((node, ctx) => ctx.target)
addAttrs: (node, ctx) => ({ href: ctx.url })

Нормализованные сведения о текущей БЭМ-сущности

Шаблонизатор нормализует сведения о текущей БЭМ-сущности. В текущем BEMJSON-узле могут быть неполные сведения о БЭМ-сущности:

{
    block: 'page',
    content: {
        elem: 'top'
        // У узла нет поля `block`.
        // Но шаблонизатор поймёт, в контексте какого блока находится элемент `top`.
    }
}

Поля с нормализованными данными:

Обратите внимание, что объекты this.mods и this.elemMods всегда присутствуют, поэтому проверки на их наличие в теле шаблона избыточные:

block('page').match((node, ctx) => {
    // Избыточно:
    return node.mods && node.mods.type === 'index' && ctx.weather;

    // Достаточно:
    return node.mods.type === 'index' && ctx.weather;
})({ def: () => ({ … }) });

Текущий узел BEMJSON

В поле this.ctx доступен текущий узел BEMJSON.

{
    block: 'company',
    name: 'yandex'
}
block('company')({
    attrs: (node, ctx) => ({
        id: ctx.name,
        name: ctx.name
    })
});

Результат шаблонизации:

<div class="company" id="yandex" name="yandex"></div>

Хелперы

Методы для экранирования

xmlEscape

/**
 * @param {String} str
 * @returns {String}
 */
this.xmlEscape(str)

Возвращает переданную строку str с заэкранированными символами XML: &, <, >. Ожидается, что str это строка. Но если str это undefined, Null или NaN, то функция возвратит пустую строку. Если str любого другого типа, то перед экранированием этот тип будет приведен к строке.

Пример использования:

{ block: 'button' }

Шаблон:

block('button')({
    def: (node) => node.xmlEscape('<b>&</b>')
});

Результат шаблонизации:

&lt;b&gt;&amp;&lt;/b&gt;

attrEscape

/**
 * @param {String} str
 * @returns {String}
 */
this.attrEscape(str)

Возвращает переданную строку str с заэкранированными символами XML- и HTML-атрибутов: " и &. Ожидается, что str это строка. Но если str это undefined, Null или NaN, то функция возвратит пустую строку. Если str любого другого типа, то перед экранированием этот тип будет приведен к строке.

jsAttrEscape

/**
 * @param {String} str
 * @returns {String}
 */
this.jsAttrEscape(str)

Возвращает переданную строку str с заэкранированными символами: ' и &. Ожидается, что str это строка. Но если str это undefined, Null или NaN, то функция возвратит пустую строку. Если str любого другого типа, то перед экранированием этот тип будет приведен к строке.

По умолчанию входные данные из поля js и данные из режима js экранируются этой функцией.

Хелперы определения позиции

this.position

Позиция в БЭМ-дереве (поле контекста this.position) представляет собой натуральное число, соответствующее порядковому номеру текущей (контекстной) БЭМ-сущности среди её соседей в дереве (одноранговых сущностей).

При вычислении позиции:

Пример нумерации позиций во входном БЭМ-дереве:

{
    block: 'page',                // this.position === 1
    content: [
        { block: 'head' },        // this.position === 1
        'text',                   // this.position === undefined
        {
            block: 'menu',        // this.position === 2
            content: [
                { elem: 'item' }, // this.position === 1
                'text',           // this.position === undefined
                { elem: 'item' }, // this.position === 2
                { elem: 'item' }  // this.position === 3
            ]
        }
    ]
}

БЭМ-дерево может быть достроено в процессе выполнения шаблонов с помощью шаблонов в режиме def или content. Такое динамическое изменение БЭМ-дерева учитывается при вычислении позиции.

Функция определения последней БЭМ-сущности среди соседей isLast возвратит false, если в массиве, содержащем узлы, последний элемент не является БЭМ-сущностью.

block('list')({
    content: [
        { block: 'item1' },
        { block: 'item2' }, // this.isLast() === false
        'text'
    ]
});

Такое поведение объясняется тем, что в целях оптимизации BEMHTML не выполняет предварительного обхода БЭМ-дерева. Поэтому в момент обработки блока item2 уже известна длина массива (item2 не является последним элементом), но еще неизвестно, что последний элемент не является БЭМ-сущностью и не получит номера позиции.

На практике описанный случай не должен порождать ошибок, так как проверка на первую/последнюю БЭМ-сущность обычно применяется к автоматически сгенерированным спискам, в которые не имеет смысла включать данные других типов.

isFirst

/**
 * @returns {Boolean}
 */
this.isFirst()

Проверяет, является ли узел первым среди своих соседей во входном дереве.

isLast

/**
 * @returns {Boolean}
 */
this.isLast()

Проверяет, является ли узел последним среди своих соседей во входном дереве.

Генератор уникальных идентификаторов

this.generateId()

Генерирует id для текущего узла.

Пример использования:

// BEMJSON
{ block: 'input', label: 'Имя', value: 'Иван' }

Шаблон:

block('input')({
    content: (node, ctx) => {
        var id = node.generateId();

        return [
            {
                tag: 'label',
                attrs: { for: id },
                content: ctx.label
            },
            {
                tag: 'input',
                attrs: {
                    id: id,
                    value: ctx.value
                }
            }
        ];
    }
});

Результат шаблонизации:

<div class="input">
    <label for="uniq14563433829878">Имя</label>
    <input id="uniq14563433829878" value="Иван" />
</div>

Другие хелперы

this.reapply()

Это возможность отшаблонизировать произвольные BEMJSON-данные, находясь в теле шаблона и получить в результате строку.

BEMJSON:

{ block: 'a' }

Шаблон:

block('a')({
    js: (node) => ({
        template: node.reapply({ block: 'b', mods: { m: 'v' } })
    })
});

Результат шаблонизации:

<div class="a i-bem" data-bem='{
    "a":{"template":"<div class=\"b b_m_v\"></div>"}}'></div>

Кастомные пользовательские поля

Контекст, доступный в теле шаблона, может быть расширен пользователем.

При помощи функции oninit в коде шаблонов:

var bemxjst = require('bem-xjst');

var templates = bemxjst.bemhtml.compile(() => {

    // Внимание: oninit сработает только при первой компиляции шаблонов.
    oninit((exports, shared) => {
        shared.BEMContext.prototype.hi = function(username) {
            return 'Hello, ' + username;
        };
    });

    block('b')({
        content: (node) => node.hi('username')
    });
});

var bemjson = { block: 'b' };

// Применяем шаблоны
var html = templates.apply(bemjson);

html будет содержать строку:

<div class="b">Hello, username</div>

При помощи прототипа BEMContext:

var bemxjst = require('bem-xjst');
var templates = bemxjst.bemhtml.compile('');

// Расширяем прототип контекста
templates.BEMContext.prototype.hi = function(name) {
    return 'Hello, ' + username;
};

// Добавляем шаблоны
templates.compile(() => {
    block('b')({
        content: (node) => node.hi('templates')
    });
});

var bemjson = { block: 'b' };

// Применяем шаблоны
var html = templates.apply(bemjson);

В результате html будет содержать строку:

<div class="b">Hello, templates</div>

Таннелинг флагов или любых данных шаблонам дочерних узлов

Предположим, что необходимо прокинуть какие-либо данные всем шаблонам для дочерних узлов формы вопросов и ответов 'qa-form'.

[
  {
      block: 'qa-form',
      content: [
          { block: 'input' },
          …
      ]
  },
  { block: 'input' }
]

В этом случае можно дополнить контекст шаблонов при помощи режима extend:

// выставляем флаг _inQaForm, который будет доступен
// из всех узлов внутри данного
block('qa-form')({ extend: { _inQaForm: true } });

Теперь можно добавить проверку на наличие этого флага:

block('input')
  // подпредикат, который проверяет, что флаг взведен
  .match((node) => node._inQaForm)
  .mix()({ mods: { inside: 'qa' } });

Результат шаблонизации будет таким:

<div class="qa-form">
    <div class="input input_inside_qa"></div>
</div>
<div class="input"></div>

Методы для управления шаблонизацией

В теле шаблонов доступны методы apply, applyNext и applyCtx. Подробнее о них читайте в следующей секции про runtime.

Читать далее: runtime