Декларация блоков и элементов
JS-реализация блока или элемента описывает поведение определенных частей веб-интерфейса. В конкретных интерфейсах каждый блок или элемент может быть представлен несколькими экземплярами. Экземпляр реализует функциональность своего класса и имеет собственное, независимое состояние.
В терминах парадигмы объектно-ориентированного программирования:
блок и элемент — классы;
экземпляры блока и элемента — экземпляры классов.
В соответствии с ООП, вся функциональность блока и элемента реализуется модульно в методах класса.
Методы блока и элемента подразделяются на:
методы экземпляра;
статические методы (методы класса).
Код блока и элемента в i-bem.js
принято называть декларацией, чтобы подчеркнуть принятый в БЭМ декларативный стиль программирования.
Поведение блока программируется в декларативном стиле в виде утверждений: набор условий
— реакция блока
.
Синтаксис декларации
Блоки и элементы с DOM-представлением
Объявление нового блока без родителя
Чтобы задекларировать новый JS-блок с DOM-представлением (привязанный к HTML-элементу), нужно воспользоваться методом declBlock
ym-модуля i-bem-dom
.
Метод declBlock
принимает аргументы:
Имя
{String}
или класс{Function}
блока.Методы экземпляра —
{Object}
.Статические методы (методы класса) —
{Object}
.
Объявленные методы будут применяться во всех экземплярах блока независимо от их состояний (модификаторов).
Пример
Декларация методов для блока button
.
modules.define('button', ['i-bem-dom'], function(provide, bemDom) {
provide(bemDom.declBlock(this.name,
{
/* методы экземпляра */
},
{
/* статические методы */
})
);
});
Поле контекста ym
this.name
, передаваемое первым аргументом методу bemDom.declBlock
, содержит ссылку на имя блока, указанное первым аргументом modules.define
.
Объявление нового элемента без родителя
Чтобы задекларировать новый JS-элемент с DOM-представлением (привязанный к HTML-элементу), нужно воспользоваться методом declElem
ym-модуля i-bem-dom
.
Метод declElem
принимает аргументы:
Имя
{String}
или класс{Function}
блока.Имя
{String}
или класс{Function}
элемента.Методы экземпляра —
{Object}
.Статические методы (методы класса) —
{Object}
.
Объявленные методы будут применяться во всех экземплярах элемента независимо от их состояний (модификаторов).
Пример
Декларация методов для элемента menu__item
.
modules.define('menu__item', ['i-bem-dom'], function(provide, bemDom) {
provide(bemDom.declElem('menu', 'item',
{
/* методы экземпляра */
},
{
/* статические методы */
})
);
});
Блоки и элементы без DOM-представления
Для декларации блоков и элементов без DOM-представления служат аналогичные методы declBlock
и declElem
ym-модуля i-bem
.
Методы принимают те же параметры, что и метод declBlock
и declElem
модуля i-bem-dom
:
modules.define('my-block', ['i-bem'], function(provide, bem) {
provide(bem.declBlock(this.name,
{
/* методы экземпляра */
},
{
/* статические методы */
})
);
});
Примечание Оформлять инфраструктурный код в виде блока без DOM-представления удобно, если в нем планируется использовать API БЭМ-блоков (состояния, выражаемые модификаторами, БЭМ-события и т. п.). Если использовать БЭМ-предметную область не планируется, инфраструктурный код можно оформлять в виде ym-модуля.
Пример
modules.define('router', function(provide) { provide({ route : function() { /* ... */ } }); });
Наследование
Одна и та же функциональность может быть востребована в нескольких блоках или элементах проекта. Например, разные блоки могут обращаться за данными к бэкенду, используя AJAX, или совершать однотипные операции с DOM-деревом и т.д. Чтобы избежать ненужных повторов в коде, общую функциональность можно инкапсулировать в виде модулей, а затем добавлять к блокам.
Наследование позволяет повторно использовать функциональность блока, расширяя её новой логикой.
В i-bem.js
доступно несколько механизмов наследования. Выбор конкретного механизма зависит от специфики создаваемого блока.
Простое наследование
В случае простого наследования создаваемый блок объявляется как наследник существующего. Для этого нужно:
Указать базовый блок в зависимостях модульной системы.
Передать ссылку на базовый блок во втором параметре декларации.
Например, блок b-block
наследуется от блока a-block
:
modules.define('a-block', ['i-bem-dom'], function(provide, bemDom) {
provide(bemDom.declBlock(this.name, {}));
});
modules.define('b-block', ['i-bem-dom', 'a-block'], function(provide, bemDom, ABlock) {
provide(bemDom.declBlock(this.name, ABlock, {}));
});
Аналогично для элемента:
modules.define('block__a-elem', ['i-bem-dom'], function(provide, bemDom) {
provide(bemDom.declElem('block', 'a-elem', {}));
});
modules.define('block__b-elem', ['i-bem-dom', 'block__a-elem'], function(provide, bemDom, BlockAElem) {
provide(bemDom.declElem('block', 'b-elem', BlockAElem, {}));
});
Такой механизм позволяет использовать методы базового блока или элемента внутри производного. Для вызова одноименных методов базового блока служит вспомогательный метод this.__base
.
Примечание В
i-bem
можно создавать цепочки наследования – блок наследуется от другого, который, в свою очередь, наследуется от третьего и т.д.
Доопределение блока или элемента
Чтобы добавить и/или изменить поведение уже существующего блока или элемента, можно доопределить базовый класс на уровне переопределения проекта.
Для этого в проекте создается декларация блока или элемента с тем же именем, что и у базового. В результате блоку будет доступна вся функциональностью базового. Реализация одноименных методов и модификаторов, при этом, будет взята из новой декларации.
modules.define('a-block', ['i-bem-dom'], function(provide, bemDom) {
provide(bemDom.declBlock(this.name, {})); // Объявляем базовый блок
});
modules.define('a-block', ['i-bem-dom'], function(provide, bemDom, ABlock) {
provide(bemDom.declBlock(ABlock, {})); // Доопределяем базовый блок
});
Аналогично для элементов с использованием метода declElem
.
Такая схема наследования часто используется при работе с библиотечными блоками.
Добавление модификатора
В соответствии с БЭМ-методологией состояния блока или элемента должны описываться модификаторами. Поэтому чтобы расширить функциональность блока или элемента часто нужно реализовать поддержку новых модификаторов.
Для декларации модификатора используется метод declMod
у класса блока или элемента. В обоих случаях метод принимает аргументы:
Хеш с ключами
modName
иmodVal
. Значением дляmodName
– имя модификатора,{String}
. ЗначениемmodVal
,{String|Boolean|Array}
— значение модификатора.Хеш методов, которые будут доступны для блока с соответствующим модификатором. При наличии одноименных методов и модификаторов, будет использована их реализация из хеша.
modules.define('a-block', ['i-bem-dom'], function(provide, bemDom) {
provide(bemDom.decl(this.name, {})); // Объявляем базовый блок
});
modules.define('a-block', function(provide, ABlock) {
provide(ABlock.declMod({ modName : 'm1', modVal : 'v1' }, {})); // Доопределяем базовый блок с модификтором _m1_v1
});
Аналогично для элементов.
Примечание Cтатические методы блока и элемента будут доступны всем их экземплярам вне зависимости от значений модификаторов. Модификаторы — это свойства экземпляра, а статические методы принадлежат классу и не учитывают состояния модификаторов.
Миксины
В i-bem.js
для добавления востребованной функциональности к блокам или элементам могут использоваться специальные сущности – миксины. Главная особенность миксинов состоит в том, что они не участвуют в цепочке наследования. Это позволяет примешивать реализованную в них функциональность к другим блокам или элементам без риска нарушить их связи с родительскими классами this.__base
.
Декларация блока-микса
Для декларации миксина используется метод declMixin
модуля i-bem-dom
(или i-bem
).
Метод принимает параметры:
Методы экземпляра —
{Object}
.Статические методы (методы класса) —
{Object}
.
modules.define('my-mixin', ['i-bem-dom'], function(provide, bemDom) {
provide(bemDom.declMixin(
{ /* методы экземпляра */ },
{ /* статические методы */ }
));
});
Примечание Миксины нельзя инстанцировать и использовать как самостоятельный блок.
Примешивание миксина
Чтобы примешать к блоку или элементу один или несколько миксинов, необходимо:
Указать необходимые миксины в зависимостях модульной системы.
Передать ссылку миксин (или миксины) во втором параметре декларации блока или элемента.
modules.define('my-block', ['i-bem-dom', 'my-mixin'], function(provide, bemDom, myMixin) {
provide(bemDom.declBlock(this.name, myMixin,
{ /* методы экземпляра */ },
{ /* статические методы */ }
));
});
В случае нескольких миксинов:
modules.define('my-block', ['i-bem-dom', 'a-mixin', 'b-mixin'], function(provide, bemDom, aMixin, bMixin) {
provide(bemDom.declBlock(this.name, [aMixin, bMixin], {}));
});
Аналогично для элемента с использованием метода declElem
.
Примечание В случае если нужно задекларировать наследование и миксины одновременно, во второй параметр следует передавать массив.Родительский класс обязательно должен быть первым элементом этого массива.
modules.define('b-block', ['i-bem-dom', 'a-block', 'a-mixin'], function(provide, bemDom, ABlock, aMixin) { provide(bemDom.declBlock(this.name, [ABlock, aMixin], {})); });
Декларация триггеров
Триггеры, выполняемые при установке модификаторов, описываются в декларации блока или элемента. Для этого в хеше методов экземпляра зарезервированы свойства:
beforeSetMod
— триггеры, вызываемые до установки модификаторов;onSetMod
— триггеры, вызываемые после установки модификаторов;
modules.define('my-block', function(provide, bemDom) {
provide(bemDom.declBlock(this.name,
{
/* методы экземпляра */
beforeSetMod: { /* триггеры до установки модификаторов */},
onSetMod: { /* триггеры после установки модификаторов */ },
},
{
/* статические методы */
}
));
});
Значение свойств beforeSetMod
и onSetMod
— хеш, связывающий изменения модификаторов с триггерами. Триггер получает аргументами:
modName
– имя модификатора;modVal
– выставляемое значение модификатора;prevModVal
– предыдущее значение модификатора. ДляbeforeSetMod
это текущее значение модификатора, которое будет заменено наmodVal
, если триггер не вернетfalse
.
bemDom.declBlock(this.name, {
onSetMod: {
'mod1': function(modName, modVal, prevModVal) { /* ... */ }, // установка mod1 в любое значение
'mod2': {
'val1': function(modName, modVal, prevModVal) { /* ... */ }, // триггер на установку mod2 в значение val1
'val2': function(modName, modVal, prevModVal) { /* ... */ }, // триггер на установку mod2 в значение val2
'': function(modName, modVal, prevModVal) { /* ... */ } // триггер на удаление модификатора mod2
},
'mod3': {
'true': function(modName, modVal, prevModVal) { /* ... */ }, // триггер на установку простого модификатора mod3
'': function(modName, modVal, prevModVal) { /* ... */ } // триггер на удаление простого модификатора mod3
},
'mod4': {
'!val1' : function() { /* ... */ } // декларация для изменения mod4 в любое значение, кроме val1
'~val2' : function() { /* ... */ } // декларация для изменения значения mod4 из val2 в любое другое значение
},
'*': function(modName, modVal, prevModVal) { /* ... */ } // триггер на установку любого модификатора в любое значение
}
})
Для триггера на установку любого модификатора блока в любое значение существует сокращенная форма записи:
beforeSetMod: function(modName, modVal, prevModVal) { /* ... */ }
onSetMod: function(modName, modVal, prevModVal) { /* ... */ }
Аналогично для элементов с использованием метода declElem
.
Примечание Если модификаторы были заданы в HTML-элементе блока или элемента до момента его инициализации, триггеры на установку данных модификаторов не выполняются. Экземпляр в этом случае получает начальное состояние, а не меняет его.