Основные понятия шаблонизатора BEMHTML

BEMHTML — это шаблонизатор, использующий предметную область «Блок-Элемент-Модификатор». Шаблоны, написанные на BEMHTML, компилируются в plain JavaScript, который может быть выполнен в любом интерпретаторе JavaScript (как на сервере, так и в браузере).

Входными данными для таких шаблонов служит описание страницы в формате JSON, а на выходе получается HTML. Простой пример такого описания страницы находится в тестовом проекте библиотеки блоков. О том, что такое BEMHTML, расказывалось в докладе Сергея Бережного «bemhtml — bem js-шаблонизатор», можно посмотреть видео и почитать презентацию.

BEMHTML шаблоны построены по декларативному принципу (как XSLT), каждый шаблон состоит из двух частей: предикат и тело шаблона.
Предикат – это набор условий, при котороых выполняется шаблон.
Тело – инструкции к выполнению.

Предикаты

Предикат может состоять из одного или нескольких условий. Например:

block b-menu, elem item, elemMod state current, tag:

Запись через запятую означает, что шаблон применим только тогда, когда выполнятся все условия.

Условия могут быть следующих типов:
  • выражающие совпадение со входным BEM деревом
  • применяемая мода
  • произвольное условие

Условия совпадения со входным BEM деревом

При помощи этих условий можно выразить совпадение со входным BEM деревом. Например, можно описать, что шаблон применим к конкретному блоку.

Возможны следующие описания BEM сущностей, к которым применим шаблон:
  • предикат на блок — block b-menu
  • предикат на элемент блока — block b-menu, elem item
  • предикат на модификатор блока — block b-link, mod pseudo yes
  • предикат на модификатор элемента — block b-menu, elem item, elemMod state current

Именами и значениями BEM сущностей в таких условиях могут быть также JS выражения:

block 'b' + '-' + 'link'
block b-menu, elem ['i', 't', 'e', 'm'].join('')

Моды

Для каждого блока может понадобиться несколько шаблонов: для тела, для атрибутов, для контента. Поэтому есть необходимость вводить понятие мод. Наличие разных мод позволяет написать несколько шаблонов для одной и той же BEM сущности, каждый из которых продуцирует свою часть выходного HTML этой BEM сущности.

Реализованы следующие моды:
  • default
    • С обработки моды default начинается работа любого BEMHTML шаблона. Если мода default явно не фигурирует в BEMHTML шаблоне, то работа происходит так, как описано в ядре BEMHTML реализации элемента html блока i-bem. Там описано, в каких случаях и в каком порядке вызывать другие моды, относящиеся к контекстной BEM сущности.
  • bem
    • При установке в false позволяет не создавать у DOM-ноды данной BEM сущности характерных CSS классов в предметной области BEM.
  • js
    • При установке в true говорит о том, что у блока есть клиентский JavaScript, значит, блок будет смиксован с блоком i-bem (появится дополнительный CSS класс) и в его DOM-ноде появится атрибут onclick, хранящий параметры для клиентского js компонента.
block b-link {
    ...
	js: true
	...
}
  • tag
    • Переопределяет HTML тег BEM сущности, который по умолчанию задан как div.
block b-icon {
    tag: 'img'
    ...
}
  • attrs
    • Расширяет множество атрибутов DOM-ноды BEM сущности дополнительными значениями.
block b-icon {
    ...
    attrs: {

        return {};

    }
}
  • cls
    • Добавка к HTML атрибуту class.
  • mix
    • Определяет массив подмешиваемых BEM сущностей. Используется для размещения на одном DOM узле нескольких BEM сущностей. Например, двух блоков, одного блока или элемента другого блока.
      Также нужен для подмешивания к существующей контекстной сущности модификаторов внутри BEMHTML шаблонов.
  • content
    • Определяет содержание блока или элемента. Содержит BEMJSON, описывающий содержание или строку как частный случай простого BEMJSON.
block b-layout-table, elem gap {
    ...
    content: { elem:  'gap-i', tag:  'i',}
}

Произвольное условие

Произвольные условия учитывают совпадения с данными, не попадающими под предметную область BEM. Например:

block b-icon {
    ...
    attrs, this.ctx.url: {

        return { src: this.ctx.url };

    }
}

Здесь this.ctx — хеш с данными контекстной сущности из BEMJSON, в данном случае — блока b-icon.

Последнее из условий предиката отделено от тела шаблона двоеточием:

block b-icon, attrs, this.ctx.url: {
    Тело шаблона
}

Тело

После двоеточия BEMHTML шаблон может содержать:
  • boolean или строку или число
block b-icon {
    tag: 'img'
    ...
}
  • bemjson
block b-layout-table, elem gap {
    ...
    content: { elem:  'gap-i', tag:  'i' }
}
  • исполняемый JavaScript код, заключённый в фигурные скобки
block b-icon {
    ...
    attrs: {

        var ctx = this.ctx,
            a = { src: '//yandex.st/lego/_/La6qi18Z8LwgnZdsAr1qy1GwCwo.gif', alt: '' },
            props = ['src', 'alt', 'width', 'height'], p;

        while(p = props.shift()) ctx[p] && (a[p] = ctx[p]);

        return a;

    }
}

Контекст

Все шаблоны выполняются в каком-либо контексте. Контекст выражается ключевым словом this, доступным как в предикатах, так и в теле шаблона.

В контексте есть набор полей:
  • this.block {String}
    • Например, может быть использован в предикате для сравнения значения:
    this.block === 'b-link'

Это идентично записи условия на языке BEM сущностей

    block b-link
  • this.elem {String} — имя элемента
  • this.mods {Object} — модификаторы блока
  • this.elemMods {Object} — модификаторы элемента блока
  • this.ctx {Object} — фрагмент входного дерева для текущего контекста. Входной JSON предоставляется "as is", безо всяких нормализаций и модификаций. Используется для получения доступа к данным, например, this.ctx.url в шаблоне блока b-link.
  • this.isFirst {Function} — функция проверяет, является ли этот элемент первым
  • this.isLast {Function} — функция проверяет, является ли этот элемент последним
  • this.position {Number} — возвращает позицию контекста

Синтаксис

Если у нескольких шаблонов часть предиката совпадает, их можно объединить, используя фигурные скобки.
Например, так можно описать элемент item блока в b-menu:

block b-menu {
    elem item {
        Здесь всё про b-menu__item
    }
}

То же самое можно написать через запятую. Вот так:

block b-menu, elem item {
    Здесь всё про b-menu__item
}

То есть, скобки — это syntax sugar над запятыми. Вложенность условий может быть сколь угодно большой.

local

local — это блок кода языка BEMHTML, по синтаксису подобный блокам while и for из JavaScript.

Возможны такие варианты записи блока local:
  • краткая запись
    local(expressions) code
  • полная запись
    local(expressions) { code }

local используется для временного изменения контекста и переменных, также для последующих операций с ними. В круглых скобках могут быть описаны только выражения присваивания, в фигурных — JavaScript код, который выполняется с учётом заданных изменений. По выходу из блока local все переменные и объекты (в том числе и this.ctx) приобретают свои прежние значения. Более подробную информацию можно найти в документации xjst.