Помощник для создания других блоков

{ 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 представление.
В HTML блоки на bem-js отличаются дополнительным CSS классом i-bem и специальным форматом записи параметров блока в onclick:

<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 объектами, имеющими свои методы. Эти методы необходимо использовать, если нужно повлиять на внешний вид или поведение блока.
Технология bem-js использует предметную область концепции BEM. Все сущности являются блоками или их элементами, управление их состояниями реализуется при помощи модификаторов.
Представление блока в js не обязано иметь взаимное соответствие с одной DOM-нодой. Можно разместить несколько блоков на одной DOM-ноде (это называется mix), а также реализовать один блок на нескольких DOM-нодах.
Далее блок, использующий технологию bem-js, будет называться блок, реализованный на bem-js, или bem-js-блок. Примером такого блока в библиотеке может служить b-link.

<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 есть специальные методы для установки и снятия модификаторов.

В коде компонента можно записать, как блок или элемент должен отреагировать на изменение модификатора. Эта запись тоже декларативна.
Например, блок b-dropdowna при установке модификатора disabled прячет показанный попап:

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-объект искомого блока, что позволяет затем напрямую вызывать его методы.
Не используйте jQuery-селекторы для поиска блоков и элементов.

В этом примере вызывается метод 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-блоки могут вызывать методы других блоков (если нужно) и реализовывать свой функционал.
js-код блока принято размещать в папке блока в файле с именем, соответствующим имени блока, и расширением .js. Если js-реализация относится не к блоку, а лишь к одной из его модификаций, можно разместить код в js-файле, соответствующем данному модификатору.

<a name="dom.decl"></a>

Декларация блока

Создание js-компонента блока сводится к его декларации с помощью специальных хелперов. Существуют два хелпера для декларации блоков: один для блоков, которые имеют DOM-представление, второй — для блоков, не имеющих DOM представления (например i-request, i-update-session).

В первом случае блоки декларируются с помощью BEM.DOM.decl, во втором — с помощью BEM.decl.

Хелпер декларации блока принимает 3 параметра:

  1. Матчащий параметр
    Первым параметром может быть либо строка с именем блока, либо хеш. Хеш кроме имени блока содержит дополнительную информацию о том, к какому типу блоков применять компонент.
  2. Методы и свойства экземляра блока
    Методы и свойства, предметной областью которых является конкретный инстанс блока на странице. Это как функции обработки модификаторов, так и кастомные методы блока.
  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-функций:

  • {jQuery} [elem] — элемент блока (если установка модификатора была для элемента)
  • {String} modName — имя модификатора
  • {String} modVal — устанавливаемое значение модификатора
  • {String} curModVal — текущее значение модификатора

Порядок вызовов callback-функций при установке модификатора modVal в значение modName:

  • вызывается callback-функция на установку любого модификатора в любое значение (если она существует)
  • вызывается callback-функция на установку модификатора modVal в любое значение (если она существует)
  • вызывается 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 параметрами.
Такая запись позволяет задавать параметры для нескольких блоков в том случае, если они представлены в HTML одной и той же DOM-нодой.

Вот как выглядит 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-ноде соответствуют несколько блоков, то появление модификатора у одного из них свидетельствует об инициализации только этого блока и не влияет на инициализацию другого (других).

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 событий:

  • liveInitOnEvent — хелпер для инициализации блока по событию на блоке или его внутреннем элементе
  • liveBindTo — подписка на события на блоке или его внутреннем элементе

Оба этих хелпера инициализируют блок при возникновении первого такого события. Различие же заключается в том, что 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();
        }
    }
});

Поиск блока или блоков может быть выполнен одним из следующих методов:

  • findBlockInside/findBlocksInside — поиск блока/блоков внутри DOM элементов текущего блока или его элементов
  • findBlockOn/findBlocksOn — поиск блока/блоков на DOM элементах текущего блока или его элементов
  • findBlockOutside/findBlocksOutside — поиск блока/блоков снаружи DOM элементов текущего блока или его элементов

Список методов поиска блоков и их сигнатуры можно посмотреть в референсе по 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) событий:

  • on(e, [data], fn, [ctx]) — подписка на событие e
  • onFirst(e, [data], fn, [ctx]) — подписка только на первое событие e
  • un([e], [fn], [ctx]) — отписка от конкретного события e или всех событий
  • trigger(e, [data]) — нотификация о событии e

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 событий позволяет избежать лишнего поиска блоков в DOM дереве. Кроме того, при такой привязке к событию реакция на событие блока из контейнера будет происходить даже в том случае, если на момент привязки блока в контейнере не было, а он появился позже в результате динамического изменения документа.

Кроме возможности привязки к live событию блока, здесь также продемонстрированы поиск блока относительно текущего и live-инициалиация.

Элементы
__dom

Хелперы для работы с DOM-представлением

{ elem: 'dom' }
__dom_init

__dom_init_auto
{ elemMods: { 'init': 'auto' } }

__html

Реализация HTML представления блоков

{ elem: 'html' }
__internal

Модуль для внутренних хелперов

{ elem: 'internal' }
Примеры

Блок, реагирующий на выбор пункта меню

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

В этом примере блок b-menu-vert следит на кликами по своим пунктам. И если выбран новый пункт меню, на BEM объекте блока случается событие current. Другие блоки могут реагировать на это событие, это будет реакцией на выбор пользователем пункта меню.

Для поиска меню внутри блока использован метод findBlockInside(). Первый параметр метода – это уточняющий селектор, с помощью которого можно указать, о каком конкретно меню идёт речь.

Метод on() – метод BEM объекта найденного блока b-menu-vert для навешивания функции, слушающей событие.

Когда новый пункт меню выбран, можно считать данные, хранящиеся в onclick атрибуте пункта и совершить действия на основе этих данных.

исходный кодуровень преопределенияскомпилированный код

Обработчик события click

BEM-js позволяет инициализировать компонент только тогда, когда пользователь начал с ним работать.

В примерре клик на квадрат включает/выключает модификатор color_green у блока b-square.

Для того, чтобы работал BEM-js, у блока должен быть модификатор is-bem:"yes" и проставлен атрибут onclick. Это выполняется автоматически при помощи шаблона по-моде js:true.

Java-script инициализация блока происходит при помощи live-события, подробнее о работе которого можно прочесть в документации на блок i-bem.

исходный кодуровень преопределенияскомпилированный код

Изменение модификатора блока по-клику

исходный кодуровень преопределенияскомпилированный код

LiveInit по нескольким событиям

Наведение на кнопку включает «профилактику», а клик по другой кнопке выключает её. На любое из этих действий блок реагирует, заворачиваясь в красную рамочку.

Это поведение описано в 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"" с передачей необходимых параметров.

исходный кодуровень преопределенияскомпилированный код