{ block: 'i-bem' } | Блок i-bem — это блок-хелпер, позволяющий создавать другие блоки. Блок реализован в технологиях BEMHTML и JS. Обе эти реализации являются ядром библиотеки блоков в соответствующих технологиях. js-реализация блока i-bemРеализация блока i-bem в JS обеспечивает хелперы для представления блока в виде JS объекта с определёнными методами и свойствами. Это нужно, чтобы писать клиентский JS в терминах BEM. То есть JS оперирует более высоким уровнем абстрации, чем DOM представление. Для того, чтобы js-представление блока использовало ядро i-bem, оно должно быть написано с соблюдением специальных правил. Что описано на этой странице?
<a name="dom.blocks"></a> Блоки с DOM-представлениемБлокам, реализованным на bem-js, могут соответствовать ноды в HTML. В этом случае говорится о том, что блоки имеют DOM представление. <div class="b-my-block i-bem" onclick="return { 'b-my-block' : { name : 'b-my-block'}}"> ... </div> Ниже подробно рассказано о формате параметров в onclick. <a name="abstract.blocks"></a> Блоки без DOM-представленияТехнология bem-js позволяет также создавать блоки, не имеющие DOM представления. Такие блоки тем не менее существуют в JS в виде объектов, манипулировать ими можно так же, как и блоками с DOM представлением. О том, как создавать такие блоки, написано под заголовком Декларация блока. Блоки, реализованные на bem-js, после инициализации представлены в js объектами, имеющими свои методы. Эти методы необходимо использовать, если нужно повлиять на внешний вид или поведение блока. <a name="decl"></a> Декларативный принципВажной особенностью технологии bem-js является декларативный принцип. Подобно работе декларативных языков программирования, js-код содержит не последовательный алгоритм работы блока, а набор действий и условий, при которых эти действия необходимо выполнять. Декларация блокаДекларативность проявляется в объявлении того, к каким блокам или их модификациям применим код компонента: BEM.DOM.decl('b-link', { ... BEM.DOM.decl({ name : 'b-domik', modName : 'type', modVal : 'popup' }, { ... <a name="mods.reaction"></a> Реакция на изменение модификаторовСогласно концепции, состояния блока или его элементов определяются модификаторами. Поэтому, чтобы динамически изменять состояния блоков и элементов, в bem-js есть специальные методы для установки и снятия модификаторов. В коде компонента можно записать, как блок или элемент должен отреагировать на изменение модификатора. Эта запись тоже декларативна. BEM.DOM.decl('b-dropdowna', { onSetMod : { 'disabled' : function(modName, modVal) { this._getSwitcher().setMod(modName, modVal); modVal == 'yes' && this.getPopup().hide(); } }, ... Подробно о декларации обработки модификаторов рассказано в пункте про создание собственного блока. <a name="block.getting"></a> Доступ к другим блокамМожет возникнуть необходимость управлять другим блоком. Для любых манипуляций с блоком необходимо получить доступ к js-объекту этого блока и вызывать его методы. <a name="blocks.finding"></a> Доступ к bem-js-блоку из другого bem-js-блока.В случае реализации собственного кастомного блока на технологии bem-js, блоку соответствует js-объект. Он наследует общие для всех блоков методы, позволяющие работать с DOM документа в терминах BEM. Среди этих методов есть методы поиска других блоков относительно текущего (findBlock*-методы). Они возвращают js-объект искомого блока, что позволяет затем напрямую вызывать его методы. В этом примере вызывается метод val() у блока b-form-checkbox: BEM.DOM.decl('b-checkbox-example', { onSetMod: { 'js': function() { var checkbox = this.findBlockInside({ blockName : 'b-form-checkbox', modName : 'type', modVal : 'my-checkbox'}); this.domElem.append('Значение checkbox: ' + checkbox.val()); } } } ); Доступ к bem-js-блоку не из bem-js-блокаВ случае работы не из bem-js-блока, методы findBlock* недоступны. js-объект блока можно получить, используя метод .bem() jQuery коллекции: $(уникальный селектор).bem('b-link'); Этот способ не рекомендован. Лучшим вариантом работы с блоками, реализованными на i-bem, является создание собственного компонента на i-bem. Подробнее о создании собственного bem-js-компонента написано ниже. <a name="mods"></a> Работа с модификаторами блокаМодификатор задаёт блоку определённое состояние. Каждому блоку можно присвоить один или несколько модификаторов (у блока также может не быть модификаторов вообще). У модификатора есть имя и значение. Любой перевод блока в другое состояние должен производиться при помощи установки модификатора. Например, для того, чтобы сделать чекбокс выделенным в блоке b-form-checkbox, ему нужно установить модификатор checked в значение yes. Модификаторы нельзя устанавливать, напрямую меняя CSS класс на соответствующей DOM-ноде. Для корректной работы js все манипуляции с модификаторами должны производиться при помощи метода-хелпера setMod(). Также существуют методы hasMod(), getMod/getMods(), toggleMod() и delMod(). Сигнатуры этих методов доступны в референсе по BEM. <a name="customization"></a> Изменение поведения существующих блоковИспользуя bem-js, можно переопределять и доопределять методы блока и функций реакции на изменения модификаторов. Это делается аналогично кастомизации блоков на CSS или BEMHTML. Переопределение поведенияНапример, на сервисе существует необходимость модифицировать все блоки b-dropdowna так, чтобы они не закрывались по второму клику на псевдо-ссылку. В этом случае на уровне переопределения сервиса нужно сделать файл blocks/b-dropdowna/b-dropdowna.js, кастомизирующий поведение блока из библиотеки: BEM.DOM.decl('b-dropdowna', { onSetMod : { 'js' : function() { this._getSwitcher().on('click', this._on, this); } }, _on : function() { this.getPopup().show(this.elem('switcher')); } }); Расширение поведенияВ предыдущем примере код кастомизации полностью переопределяет поведение блока. Технология bem-js позволяет также реализовывать «доопределение» блока. Для этого в методах кастомизирующего кода можно вызывать this.__base.apply(), передавая в качестве аргументов this и arguments. Вызов такого метода аналогичен использованию <xsl:apply-imports/>. Например, можно доопределить реакцию на клик всех блоков b-link на проекте, так, чтобы после первого клика на псевдо-ссылку она приобретала красный цвет. Содержание файла blocks/b-link/_pseudo/b-link_pseudo_yes.js BEM.DOM.decl({'name': 'b-link', 'modName': 'pseudo', 'modVal': 'yes'}, { _onClick : function() { this .__base.apply(this, arguments) // выполнить метод _onClick основного b-link .setMod('status', 'clicked'); } }); Содержание файла blocks/b-link/_status/b-link_status_clicked.css .b-link_status_clicked { color: red; } Кастомизация с использованием модификаторовПредыдущие примеры кастомизации изменяют поведение всех определенных блоков на странице. Но очень часто возникает задача кастомизации конкретного блока без влияния на поведение всех таких блоков. Согласно концепции BEM, если блок чем-то отличается от других похожих, это выражается модификатором. Так что нужно реализовывать поведение для блока с таким модификатором. Возвращаясь к примеру про псевдоссылку, приобретающую красный цвет после первого клика, сделаем модификацию reaction_odd. Псевдоссылка с таким модификатором приобретает красный цвет после каждого нечётного клика, а после каждого чётного возвращается к исходному цвету . Содержание файла blocks/b-link/_reaction/b-link_reaction_odd.js: BEM.DOM.decl({name : 'b-link', modName : 'reaction', 'modVal' : 'odd'}, { _onClick : function() { this .__base.apply(this, arguments) // выполнить метод _onClick основного b-link .toggleMod('status', 'clicked'); } }); <a name="creation"></a> Создание js-компонента для собственного блока или собственной модификацииРекомендованным способом работы с bem-js-блоками является создание собственных bem-js-блоков (чаще всего — контейнеров), реагирующих на события других блоков страницы. Собственные bem-js-блоки могут вызывать методы других блоков (если нужно) и реализовывать свой функционал. <a name="dom.decl"></a> Декларация блокаСоздание js-компонента блока сводится к его декларации с помощью специальных хелперов. Существуют два хелпера для декларации блоков: один для блоков, которые имеют DOM-представление, второй — для блоков, не имеющих DOM представления (например i-request, i-update-session). В первом случае блоки декларируются с помощью BEM.DOM.decl, во втором — с помощью BEM.decl. Хелпер декларации блока принимает 3 параметра:
Например: BEM.DOM.decl( 'b-link', // имя блока { // методы и свойства экземпляра блока }, { // статические методы и свойства блока }); и BEM.decl('i-request', { { // методы и свойства экземпляра блока }, { // статические методы и свойства блока }); Вместо имени блока может быть указано более сложное описание, например, информация о предке: BEM.decl({ name : 'b-dataprovider', baseBlock : 'i-request' }, { get : function() { this.__base(); // вызов одноименного метода из i-request doSomething(); } }); Тут указано, что блок b-dataprovider наследуется от блока i-request и переопределяет его метод get. В первом параметре (хеше) декларации может быть указано не только то, к какому блоку применить компонент, но и уточнён модификатор и/или его значение: BEM.decl({ name : 'b-popup', modName : 'type', modVal : 'inplace' }, { show : function() { doSomething(); } }); Все методы, описанные в такой декларации, будут вызываться для таких блоков b-popup, которые в данный момент имеют модификатор type, установленный в inplace. <a name="mods.callbacks"></a> Реакция на изменение модификаторовСогласно концепции BEM состояния блоков и его элементов определяются модификаторами. Блок может сам назначать себе или своему элементу модификатор, или получать это назначение из другого блока. Для этого в декларации в части описании методов и свойств экземпляра блока зарезервировано два специальных свойства: onSetMod и onElemSetMod, где описываются callback-функции, вызываемые при установке модификаторов для блока или его элементов. Описание callback-функций для onSetMod представляет собой хеш вида: { 'модификатор1' : { 'значение1-модификатора1' : function() { ... }, // функция, которая будет вызвана при установке модификатора 'модификатор1' в значение 'значение1' ... 'значениеN-модификатора1' : function() { ... }, // функция, которая будет вызвана при установке модификатора 'модификатор1' в значение 'значениеN' '*' : function() { ... } // функция, которая будет вызвана при установке модификатора 'модификатор1' в любое из значений }, 'модификатор2' : function() {}, // функция, которая будет вызвана при установке модификатора 'модификатор2' в любое из значений ... 'модификаторN' : { 'значение1-модификатораN' : function() { ... }, ... 'значениеN-модификатораN' : function() { ... } } } Описание callback-функций для onElemSetMod аналогично, за исключением того, что на верхнем уровне указывается имя элемента: { 'элемент1' : { 'модификатор1' : { 'значение1-модификатора1' : function() { ... }, // функция, которая будет вызвана при установке модификатора 'модификатор1' в значение 'значение1' для элемента 'элемент1' ... 'значениеN-модификатора1' : function() { ... }, // функция, которая будет вызвана при установке модификатора 'модификатор1' в значение 'значениеN' для элемента 'элемент1' '*' : function() { ... } // функция, которая будет вызвана при установке модификатора 'модификатор1' в любое из значений для элемента 'элемент1' } }, ... 'элементN' : function() { }, // функция, которая будет вызвана при установке любого модификатора в любое из значений для элемента 'элементN' } Параметры callback-функций:
Порядок вызовов callback-функций при установке модификатора modVal в значение modName:
Если хоть один из вызовов этих функций вернет false, то установки модификатора не произойдет. Например: BEM.DOM.decl('b-menu', { onElemSetMod : { 'trigger' : { 'state' : function(elem, modName, modVal) { // тут описаны действия, которые нужно совершить при установке элементу 'trigger' модификатора с именем 'state' в любое значение this .toggleMod( this.findElem(elem.closest(this.buildSelector('layout-cell')), 'item-content').eq(0), 'visibility', 'visible', modVal == 'opened') .trigger('trigger', { domElem : elem, state : modVal }); } } }, onTriggerClick : function(e) { // при клике на триггер e.preventDefault(); this.toggleMod(e.data.domElem, 'state', 'opened'); // устанавливаем или снимаем значение 'opened' у модификатор 'state' для элемента 'trigger' } }, { live : function() { this .liveBindTo('trigger', 'click', function(e) { // слушаем live-клик на элементах 'trigger' this.onTriggerClick(e); }); } }); В данном примере при вызове toggleMod внутри onTriggerClick будет вызвана соответствущая ей callback-функция из onElemSetMod. Callback функции, реагирующие на изменение модификатора, выполняются до установки модификатора. Если существует необходимость выполнить часть кода после установки модификатора, нужно воспользоваться методом .afterCurrentEvent(). Пример ниже демонстрирует, что квадратик становится больше только после установки модификатора: BEM.DOM.decl('b-square2', { onSetMod : { 'js' : function() { var square = this; this.bindTo('click', function(){ square.setMod('size', 'big'); }); }, 'size' : function() { this.domElem.append('размер1: ', this.domElem.width() + '<br/>'); // напишет 200 this.afterCurrentEvent(function(){ this.domElem.append('размер2: ', this.domElem.width()); // напишет 400 }); } } }); Начало работы с блоком (модификатор js)Блок начинает свою работу с действий, описанных в callback-функции на установку его модификатора js в значение inited: BEM.DOM.decl('b-form-input', { onSetMod : { 'js' : { 'inited' : function() { this .bindTo(this.elem('input'), { 'focus' : this.onFocus, 'blur' : this.onBlur }) } } } }); Этот модификатор присваивается блоку в момент инициализации. Поскольку код обработчика модификатора выполняется до установки модификатора, эта функция-обработчик и является первой выполняющейся функцией блока. Модификаторы могут без ограничения присваиваться как блокам, имеющим DOM представление, так и блокам без него. Так что, у блоков без DOM представления первый исполняемый метод также задаётся как callback модификатора js_inited. В коде блоков можно встретить callback функцию не на значение inited модификатора js, а на установку модификатора js в любое значение: BEM.DOM.decl('b-form-input', { onSetMod : { 'js' : function() { // конструктор b-form-input ... } } }); Это краткая декларация, возможная из-за того, что до инициализации блок не имеет модификатора js, а в момент инциализации приобретает значение inited. Другие значения модификатора сейчас не предусмотрены. <a name="methods"></a> Методы блокаКроме реакции на модификаторы, в блоке могут быть определены его собственные методы. Определённые в блоке методы могут быть вызваны им самим или другими блоками. Например, так выглядит метод .toggle() блока b-form-checkbox: BEM.DOM.decl('b-form-checkbox', { ... toggle : function() { this.toggleMod('checked', 'yes', ''); } ... }); Переопределение и доопределение методов блокаЛюбой метод блока (в том числе и методы обработки модификаторов) может быть переопределён. Об этом написано выше в пункте Изменение поведения существующих блоков. <a name="static.methods"></a> Статические методы блокаТретий параметр, передаваемый в функцию декларации блока, – это хеш статических методов блока. Примером блока, использующего статические методы, может служить /blocks/b-flash/b-flash.wiki. Для каждого блока может быть определен статический метод live, позвляющий реализовать инициализацию по требованию (liveinit). <a name="init"></a> ИнициализацияДля того, чтобы у блока появился js-объект, описанный в декларации, происходит процесс инициализации блока. Инициализация блоков производится функцией BEM.DOM.init() на фрагменте DOM дерева. Если элемент i-bem__dom задекларирован с модификатором init_auto (подключается файл i-bem__dom_init_auto.js), то инициализация блоков происходит на всём документе по событию domReady. Также функцию BEM.DOM.init можно вызвать самостоятельно. Например, это делается для инициализации блоков после динамического изменения страницы, если появились новые блоки с js-представлением. Инициализация блоков с DOM-представлениемДля инициализации блоков, представленных в DOM, на фрагменте дерева ищутся все блоки, помеченные классом i-bem, у них считываются параметры из атрибута onclick, и создаётся js-объект такого блока. <a name="onclick.params"></a> Формат параметров блока в onclickПараметры для блока записываются в виде возвращаемого атрибутом onclick хеша. Этот хеш должен содержать элементы с названиями, соответствующими названиям блоков, к которым они относятся. Значением каждого элемента должен быть вложенный хеш c параметрами. Вот как выглядит DOM-нода произвольного блока, реализованного на bem-js: <div class="b-my-block i-bem" onclick="return { 'b-my-block' : {} }"> .. </div> В случае, если блоку необходим параметр, он указывается на том же уровне, что и элемент name. Формат параметра может быть любым: строка, число, массив, хеш, функция. Количество параметров также не ограничено. <div class="b-my-block i-bem" onclick="return { 'b-my-block' : { 'points' : [ [1.67, 2.5], [-30, 2.07], [290, -0.39] ], 'title' : 'Какое-то название', } }"> .. </div> Для нескольких блоков на одной DOM-ноде HTML представление будет аналогично следующему: <div class="b-my-block b-my-second-block i-bem" onclick="return { 'b-my-block' : { 'title' : 'Какое-то название', // Этот блок имеет опцинальный параметр title }, 'b-my-second-block' : { // У этого блока нет никаких опциональных параметров } }"> .. </div> DOM-представление инициализированного блокаПосле инициализации DOM представление блока изменяется: у блока появляется дополнительный модификатор js_inited. DOM-представление блока после инициализации: <div class="b-my-block b-my-block_js_inited i-bem" onclick="return { 'b-my-block' : { 'name' : 'b-my-block' } }"> .. </div> DOM представление двух блоков после инициализации: <div class="b-my-block b-my-second-block b-my-block_js_inited b-my-second-block_js_inited i-bem" onclick="return {
'b-my-block' : {
'name' : 'b-my-block',
},
'b-my-second-block' : {
'name' : 'b-my-second-block'
}
}"> DOM представление двух блоков, но инициализован только один из них: <div class="b-my-block b-my-second-block b-my-second-block_js_inited i-bem" onclick="return {
'b-my-block' : {
'name' : 'b-my-block',
},
'b-my-second-block' : {
'name' : 'b-my-second-block'
}
}"> Инициализация блоков без DOM-представленияВ том случае, если у блока нет DOM представления, в процессе инициализации просто возникает js-объект, соответствующий этому блоку. Дальнейшее зависит от кода блока. <a name="liveinit"></a> Инициализация по требованию (liveInit)Многим блокам (например, b-link, b-dropdown, b-smart-help) нет необходимости делать сразу же полную инициализацию. Инициализация может происходить только на ключевые события для этого блока, например, клик по элементу этого блока. Рассмотрим на примере блока b-link: BEM.DOM.decl('b-link', { _onClick : function(e) { e.preventDefault(); this.trigger('click'); } }, { live : function() { this.liveBindTo('click', function(e) { this._onClick(e); }); } }); В статических свойствах блока предусмотрено специальное свойство live (Function|Boolean), отвечающее за инициализацию по требованию и за подписку на live события на DOM элементах внутри такого блока. Если live определено как Function, то эта функция будет выполнена один раз — при попытке инициализации первого такого блока. Существует несколько хелперов для live событий:
Оба этих хелпера инициализируют блок при возникновении первого такого события. Различие же заключается в том, что callback функция в liveInitOnEvent вызывается только один раз после инициализации блока, а в liveBindTo она будет вызываться при каждом событии. Контекстом такой callback функции является тот блок, в котором произошло событие. В вышеприведенном примере блок b-link будет инициализирован при первом клике на себе и будет реагировать на каждый последующий клик. Если же live определено как Boolean и установлено в true, то такой блок будет инициализирован только при попытке доступа к нему, например, из методов поиска findBlockInside/findBlockOutside. <a name="finding"></a> Методы доступа к блокам и элементамРаботая с блоками, реализованными на bem-js, необходимо использовать встроенные методы для поиска блоков и их элементов. Эти методы доступны в каждом блоке и умеют возвращать другой блок или jQuery коллекцию (в случае поиска элементов). Методы поиска блоковПоиск блоков осуществляется относительно текущего блока при помощи методов findBlock*. Реализуем блок b-my-block, который находит первый из блоков b-form-checkbox внутри себя и вызывает у него метод toggle() для переключения чекбокса. BEM.DOM.decl('b-my-block', { onSetMod : { 'js' : function() { var checkbox = this.findBlockInside('b-form-checkbox'); checkbox.toggle(); } } }); Поиск блока или блоков может быть выполнен одним из следующих методов:
Список методов поиска блоков и их сигнатуры можно посмотреть в референсе по BEM.DOM. Примерами блоков, использующих методы поиска других блоков, могут быть: b-smart-help, b-screenshot и b-dropdowna. Методы доступа к элементамДля поиска элементов внутри блока используется метод elem. Результат этого метода кэшируется. Например: BEM.DOM.decl('b-form-input', { doSomething : function() { this.elem('hint'); // тут будут найдены элементы b-form-input__hint } }); Можно искать элементы внутри блока с учетом модификатора: BEM.DOM.decl('b-menu', { doSomething : function() { this.elem('item', 'state', 'current'); // тут будут найдены элементы b-menu__item_state_current } }); Некэширующий метод поиска элементов называется findElem(). Полный список методов для поиска элементов и их сигнатуры можно найти в референсе по BEM.DOM. <a name="events"></a> Работа с событиямиСобытия на блокахБлоки предоставляют интерфейс для подписки/отписки/нотификации своих собственных (не DOM) событий:
live-события на блокахВ bem-js есть события, реализованные по паттерну делегированных событий, они называются live события. Следующий пример демонстрирует работу с live-событием click для блоков b-link, содержащихся в определённой DOM-ноде. В данном случае контейнер и блок совпадают: BEM.DOM.decl('b-link-example', { onSetMod: { 'js': function() { var link = this.findBlockInside('b-link'); BEM.blocks['b-link'] .liveCtxBind(link.domElem, 'click', function(){ link.domElem.text('Кликнутая ссылка'); }, this); } } }, { live: function() { this.liveInitOnBlockInsideInit('b-link'); } } ); Метод .liveCtxBind() реализует возможность реакции на bem-события блоков, вложенных в какой-либо DOM элемент. Это не DOM-события Кроме возможности привязки к live событию блока, здесь также продемонстрированы поиск блока относительно текущего и live-инициалиация. |
__dom | Хелперы для работы с DOM-представлением |
{ elem: 'dom' } |
__dom_init |
__dom_init_auto { elemMods: { 'init': 'auto' } } |
__html | Реализация HTML представления блоков |
{ elem: 'html' } |
__internal | Модуль для внутренних хелперов |
{ elem: 'internal' } |
При клике на DOM-элементе блока b-link возникает событие click на BEM-объекте блока (не путать с DOM-событием click).
За продуцирование этого события отвечает JS код блока b-link.
В своём блоке можно реагировать на события вложенных блоков. Для поиска вложенного блока используется метод findBlockInside(). Он возвращает BEM объект найденного блока. Один из методов такого объекта – on() позволяет задавать реакцию на событие.
BEM-js позволяет инициализировать компонент только тогда, когда пользователь начал с ним работать.
В примерре клик на квадрат включает/выключает модификатор color_green у блока b-square.
Для того, чтобы работал BEM-js, у блока должен быть модификатор is-bem:"yes" и проставлен атрибут onclick. Это выполняется автоматически при помощи шаблона по-моде js:true.
Java-script инициализация блока происходит при помощи live-события, подробнее о работе которого можно прочесть в документации на блок i-bem.
Наведение на кнопку включает «профилактику», а клик по другой кнопке выключает её. На любое из этих действий блок реагирует, заворачиваясь в красную рамочку.
Это поведение описано в js-коде блока b-tv. Инициализация блока происходит по двум событиям: наверении курсора на один элемент или клике на другой.
Возможность задать несколько сценариев инициализации позволяет реализовывать сложные блоки, работу с которыми пользователь может начинать по-разному.
В примере используется стандартный блок b-link. Модификатор pseudo: "yes" позволяет воспользоваться BEM-js этого блока. Этот js отслеживает событие onclick на ссылке и использует preventDefault, благодаря которому несмотря на наличие href в ссылке, пользователь после клика не уходит со страницы.
Для того, чтобы реализовать свою реакцию на ссылку, не нужно повторять этот код. Достаточно доопределить поведение блока. Это делается при помощи BEM-js для кастомного модификатора action: "alert".
В JS-декларации блока указано, что файл описывает только поведение блоков b-link с модификатором action в значении alert. Опредлён метод _onClick (такой же как в файле b-link_pseudo_yes.js из общей библиотеки блоков, который является для него родительских.
Вызов родительского метода осуществляется через конструкцию ""this.base.apply"" с передачей необходимых параметров.
Блок «видит» вложенные в его блоки и может, например, реагировать на их события. Это позволяет реиспользовать существующие компоненты и собирать свои блоки из имеющихся.