Форум

Методология

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

Платформа

Сообщество

События

В i-bem.js поддерживается два вида событий:

  • DOM-события — jQuery-события на DOM-узле, связанном с блоком. Отражают взаимодействие пользователя с интерфейсом (клик, наведение мыши, ввод текста и т.п.). DOM-события обычно обрабатывает тот экземпляр блока, на DOM-узлах которого они возникают.
  • БЭМ-события — собственные события, генерируемые блоком. Позволяют организовать API для взаимодействия с блоком. БЭМ-события обычно обрабатывает экземпляр блока, отслеживающий состояние других блоков, на которых генерируются события.

DOM-события следует использовать только во внутренних процедурах блока. Для взаимодействия блока с внешней средой (другими блоками) предназначены БЭМ-события.

Делегирование событий

Обработка БЭМ-событий и DOM-событий может быть делегирована контейнеру (всему документу или конкретному DOM-узлу). В этом случае контейнер служит точкой обработки событий, возникающих на любом из его дочерних узлов, даже если в момент подписки на события некоторых из дочерних узлов ещё не существовало.

Например, блок меню может содержать вложенные блоки — пункты меню. Обработку кликов на пунктах меню имеет смысл делегировать самому блоку меню. Это, во-первых, позволяет сэкономить затраты ресурсов на подписку на события (дешевле подписаться на одно событие контейнера, чем на много событий элементов). Во-вторых, даёт возможность добавлять и удалять пункты меню, не подписываясь на события добавленных пунктов и не отписываясь от событий удалённых.

Возможно делегировать как БЭМ-события, так и DOM-события.

DOM-события

Работа с DOM-событиями в i-bem.js полностью реализована средствами фреймворка jQuery.

Подписка на DOM-событие

У объекта экземпляра блока зарезервирован набор методов для подписки на DOM-события:

  • bindTo([elem], event, handler) — на события основного DOM-узла блока и DOM-узлов его элементов.
  • bindToDoc(event, [data], handler) — на события DOM-узла document.
  • bindToWin(event, [data], handler) — на события DOM-узла window.

Пример: В момент инициализации экземпляра блока my-block выполняется подписка на событие click, при наступлении которого блок выставляет себе модификатор size в значение big.

BEMDOM.decl('my-block', {
    onSetMod : {
        'js' : {
            'inited': function() {
                this.bindTo('click', function(e) {
                    this.setMod('size', 'big');
                });
            }
        }
    }
});

Пример: При инициализации экземпляра блока my-form выполняется подписка на событие click элемента submit, при наступлении которого будет вызвана функция-обработчик _onSubmit.

BEMDOM.decl('my-form', {
    onSetMod : {
        'js' : {
            'inited': function() {
                this.bindTo('submit', 'click', this._onSubmit);
            }
        }
    },

    _onSubmit : function() { /* ... */ }
});

Важно: Функция-обработчик выполняется в контексте того экземпляра блока, в котором возникло событие.

Удаление подписки на DOM-событие

Удаление подписки на DOM-события выполняется автоматически при уничтожении экземпляра блока. Тем не менее у объекта экземпляра блока зарезервирован набор методов для удаления подписки вручную во время работы блока:

  • unbindFrom([elem], event, [handler]) — удаление подписки на события основного DOM-узла блока и DOM-узлов его элементов.
  • unbindFromDoc(event, [handler]) — удаление подписки на события DOM-узла document.
  • unbindFromWin(event, [handler]) — удаление подписки на события DOM-узла window.

Если при вызове одного из методов не указана функция-обработчик, будут удалены все обработчики, установленные блоком на DOM-узле для этого события.

_stopKeysListening : function() {
    this.unbindFromDoc('keydown');  // удаляем все обработчики события 'keydown'
                                    // установленные блоком DOM-узлу document
}

Объект DOM-события

Первым аргументом функция-обработчик получает jQuery-объект DOM-события — {jQuery.Event}.

Это позволяет использовать методы объекта stopPropаgation и preventDefault для управления распространением события и реакцией на него браузера.

BEMDOM.decl('my-block', {
    onSetMod : {
        'js' : {
            'inited': function() {
                this.bindTo('click', function(e) {
                    e.stopPropаgation(); // останавливаем всплывание события
                    this._onSubmit();
                });
            }
        }
    },

    _onSubmit : function() {
        /* ... */
    }
});

DOM-событие может быть сгенерировано вручную, например, с помощью jQuery-функции trigger. После объекта события, функция-обработчик DOM-события получит аргументами те параметры, с которыми trigger была вызвана при создании события.

Важно: Параметры окружения и поведение функции-обработчика события идентичны функции-обработчику jQuery.

Делегирование DOM-событий

Делегировать обработку DOM-событий рекомендуется с помощью метода liveBindTo([elem], event, handler). В статических методах декларации блока зарезервированно свойство live для подписки на делегированные DOM-события.

Пример: Все экземпляры блока menu подписываются на делегированное DOM-событие click своих элементов item. Метод _onItemClick экземпляра блока menu будет выполняться при клике на любой элемент item в меню. Не важно, существовал ли этот элемент в момент инициализации экземпляра.

BEMDOM.decl('menu', {
    _onItemClick : function(e) { /* ... */ }
}, {
    live : function() {
        this.liveBindTo('item', 'click', function(e) {
            this._onItemClick(e);
        });
    }
});

Если в декларации блока задано свойство live, инициализация экземпляров блока будет отложена до момента, когда экземпляр блока потребуется в работе (ленивая инициализация). Таким моментом может стать DOM-событие на экземпляре блока, на которое выполнена делегированная подписка, или обращение к экземпляру блока из другого блока.

Важно: Функция-обработчик выполняется в контексте ближайшего блока данного типа на пути всплытия DOM-события (снизу вверх по DOM-дереву).

Чтобы воспользоваться делегированными событиями в блоке, не откладывая инициализацию, из функции, заданной в свойстве live, следует вернуть false:

modules.define('my-block', ['i-bem__dom'], function(provide, BEMDOM) {

    provide(BEMDOM.decl(this.name,
        {
            _onClick: function() { /* ... */ }  // будет выполняться каждый раз,
                                                // когда возникает событие 'click'
        },
        {
            live: function() {
                this.liveBindTo('click', function() { this._onClick() });

                // экземпляры блоков будут инициализированы автоматически
                return false;
            }
        }
    ));

});

БЭМ-события

В отличие от DOM-событий, БЭМ-события генерируются не на DOM-элементах, а на экземплярах блоков. Элементы блоков не могут генерировать БЭМ-события.

Генерация БЭМ-события

Для генерации БЭМ-события используется метод экземпляра блока emit(event, [data]).

При взаимодействии пользователя с элементом управления блока возникают DOM-события. В ходе их обработки блоком можно создавать БЭМ-события. Это позволяет реализовать уровень абстракции над DOM-событиями. БЭМ-события создаются как реакция на DOM-события, но при соблюдении некоторых условий, например, при наличии или конкретном значении модификатора.

Например, при клике по кнопке submit (DOM-событие click) БЭМ-событие click генерируется только в том случае, если у блока в этот момент не установлен модификатор disabled:

BEMDOM.decl('submit', {
    onSetMod: {
        'js': {
            'inited': function() {
                // подписка на DOM-событие "click"
                this.bindTo('click', this._onClick);
            }
        }
    },

    _onClick: function() {
        if(!this.hasMod('disabled')) {
            this.emit('click'); // создание БЭМ-события "click"
        }
    }
});

Вторым аргументом emit можно передать произвольные данные, которые будут доступны как второй аргумент функции-обработчика.

Подписка на БЭМ-события

Для подписки на БЭМ-события экземпляров блоков используется метод экземпляра блока on(event, [data], handler, [handlerCtx]).

Пример: В момент инициализации HTML-формы (экземпляра блока my-form) выполняется поиск вложенной в форму кнопки submit и подписка на её БЭМ-событие click. В результате при нажатии на кнопку (экземпляр блока submit) будет выполнен метод _onSubmit формы (экземпляр блока my-form).

BEMDOM.decl('my-form', {
    onSetMod: {
        'js': {
            'inited': function() {
                this.findBlockInside('submit').on(
                    'click', // имя БЭМ-события
                    this._onSubmit, // метод экземпляра блока my-form
                    this); // контекст для выполнения _onSubmit — блок my-form
            }
        }
    },

    _onSubmit: function() { /* ... */ }
});

Важно: Если не передавать аргумент [handlerCtx], контекстом для выполнения функции-обработчика будет тот блок, в котором возникло БЭМ-событие (в примере выше это блок submit).

Удаление подписки на БЭМ-события

Удаление подписки на БЭМ-события выполняется автоматически при уничтожении экземпляра блока. Для удаления подписки вручную, используйте метод экземпляра блока un(event, [handler], [handlerCtx]).

События при изменении модификаторов

Для подписки на БЭМ-события при изменении модификатора блока или элемента используется метод экземпляра блока on(event, [data], handler, [handlerCtx]).

Метод принимает аргументы:

  • объект свойств модификатора, на который производится подписка;
  • функцию-обработчик, выполняющуюся при установке соответствующего модификатора.

Объект, описывающий модификатор, может содержать следующие зарезервированные свойства:

  • modName {String} — имя модификатора. Обязательное свойство.
  • modVal {String} — значение модификатора. Обязательное свойство. Со значением * производится подписка на установку модификатора в любое значение. Со значением '' — на удаление модификатора. Подробнее смотрите в разделе триггеры на установку модификаторов.
  • elem {String} — имя элемента (для модификаторов элементов).

Пример: В момент инициализации блок form подписывается на событие при изменении модификатора у вложенного блока submit.

Можно подписаться на:

  • установку модификатора disabled в любое значение;

    BEMDOM.decl('form', {
        onSetMod: {
            'js': {
                'inited': function() {
                    var submit = findBlockInside('submit');
    
                    submit.on({ modName : 'disabled', modVal : '*' }, function() {});
                }
            }
        },
    });
    
  • установку модификатора 'disabled' в значение 'true';

    submit.on({ modName : 'disabled', modVal : 'true' }, function() {});
    
  • удаление модификатора 'disabled';

    submit.on({ modName : 'disabled', modVal : '' }, function() {});
    
  • удаление модификатора m1 у элемента 'control';

    submit.on({ elem : 'control', modName : 'm1', modVal : '' }, function() {});
    

Делегирование БЭМ-событий

Делегирование БЭМ-событий означает, что блок подписывается на определённое БЭМ-событие всех экземпляров блока с заданным именем в пределах заданного контекста.

Подписка на делегированные БЭМ-события выполняется с помощью статического метода класса блока MyBlock.on([ctx], event, [data], handler, [handlerCtx]).

  • {jQuery} [ctx] — DOM-узел, в пределах которого отслеживаются БЭМ-события (контейнер). Если не указан, в качестве контейнера используется весь документ.
  • {String} event — имя БЭМ-события.
  • {Object} [data] — произвольные данные, передаваемые функции-обработчику.
  • {Function} handler — функция-обработчик события.
  • {Object} [handlerCtx] — контекст функции-обработчика события. Если отсутствует, функция-обработчик будет выполняться в контексте экземпляра блока, в котором произошло событие.

Пример: При инициализации экземпляров блока menu выполняется подписка на БЭМ-событие click всех ссылок (экземпляров блока link) в пределах DOM-узла блока (this.domElem). В качестве контекста функции-обработчика передаётся текущий экземпляр блока.

modules.define('menu', ['i-bem__dom', 'link'], function(provide, BEMDOM, Link) {

provide(BEMDOM.decl(this.name,
    onSetMod : {
        'js' : {
            'inited' : function() {
                Link.on( // подписка на БЭМ-событие

                    // контейнер — DOM-узел экземпляра блока menu
                    this.domElem,

                    'click', // БЭМ-событие

                    this._onLinkClick, // обработчик

                    this); // контекст обработчика — экземпляр блока menu
            },

            '' : function() {
                Link.un( // удаление подписки на БЭМ-событие
                    this.domElem,
                    'click',
                    this._onLinkClick,
                    this);
            }
        }
    },

    _onLinkClick : function(e) {
        var clickedLink = e.target; // экземпляр блока 'link',
                                    // на котором произошло БЭМ-событие 'click'
    }
});

});

Делегировать можно любые БЭМ-события, в том числе и события при изменении модификаторов.

Важно: Удаление подписки на делегированные БЭМ-события никогда не происходит автоматически. Всегда следует явно удалять подписку при помощи статического метода блока un([ctx], event, [handler], [handlerCtx]).

Объект БЭМ-события

При вызове функция-обработчик получает аргументом объект, описывающий БЭМ-событие. Класс объекта БЭМ-события events.Event определен в ym-модуле events библиотеки bem-core.

Объект содержит поля:

  • target — экземпляр блока, в котором произошло БЭМ-событие.
  • data — произвольные дополнительные данные, переданные как аргумент data при подписке на БЭМ-событие.
  • result — последнее значение, возвращенное обработчиком данного события. Аналогично jQuery.Event.result.
  • type — тип события. Аналогично jQuery.Event.type.

Подробнее о свойствах и методах объекта БЭМ-события читайте в документации блока events.

Если вы заметили ошибку или хотите чем-то дополнить статью, вы всегда можете или написать нам об этом на Гитхабе, или поправить статью с помощью prose.io.