Форум

Методология

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

Платформа

Сообщество

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

JS-реализация блока описывает поведение определённого класса элементов веб-интерфейса. В конкретных интерфейсах каждый блок может быть представлен несколькими экземплярами. Экземпляр блока реализует функциональность своего класса и имеет собственное, независимое состояние.

В терминах парадигмы объектно-ориентированного программирования:

  • блок — класс;
  • экземпляр блока — экземпляр класса.

В соответствии с ООП, вся функциональность блока реализуется модульно в методах класса (=блока).

Методы блока подразделяются на:

  • методы экземпляра блока;
  • статические методы.

Код блока в i-bem.js принято называть декларацией, чтобы подчеркнуть принятый в БЭМ декларативный стиль программирования.

Поведение блока программируется в декларативном стиле в виде утверждений: набор условий — реакция блока.

Синтаксис декларации

Блоки с DOM-представлением

Чтобы задекларировать новый JS-блок с DOM-представлением (привязанный к HTML-элементу), нужно воспользоваться методом decl ym-модуля i-bem__dom.

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

  1. Описание блока {String} или {Object}.
  2. Методы экземпляра блока — {Object}.
  3. Статические методы — {Object}.

Объявленные методы будут применяться во всех экземплярах блока независимо от их состояний (модификаторов).

Пример: Декларация методов для блока button.

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

    provide(BEMDOM.decl(this.name,
        {
            /* методы экземпляра */
        },
        {
            /* статические методы */
        })
    );

});

Поле контекста ym this.name, передаваемое первым аргументом методу BEMDOM.decl, содержит ссылку на имя блока, указанное первым аргументом modules.define.

Блоки без DOM-представления

Для декларации блоков без DOM-представления служит метод decl ym-модуля i-bem.

Метод принимает те же параметры, что и метод decl модуля i-bem__dom:

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

    provide(BEM.decl(this.name,
        {
            /* методы экземпляра */
        },
        {
            /* статические методы */
        })
    );

});

Важно: Оформлять инфраструктурный код в виде блока без DOM-представления удобно, если в нём планируется использовать API блоков (состояния, выражаемые модификаторами, БЭМ-события и т.п.). Если использовать БЭМ-предметную область не планируется, инфраструктурный код можно оформлять в виде ym-модуля.

Пример:

modules.define('router', function(provide) {

    provide({
        route : function() { /* ... */ }
    });

});

Наследование блока

Одна и та же функциональность может быть востребована в нескольких блоках проекта. Например, разные блоки могут обращаться за данными к бэкенду, используя AJAX, или совершать однотипные операции с DOM-деревом и т.д. Чтобы избежать ненужных повторов в коде, общую функциональность можно инкапсулировать в виде модулей, а затем добавлять к блокам.

Наследование позволяет повторно использовать функциональность блока, расширяя её новой логикой. В i-bem.js доступно несколько механизмов наследования. Выбор конкретного механизма зависит от специфики создаваемого блока.

Простое наследование

В случае простого наследования создаваемый блок объявляется как наследник существующего. Для этого нужно:

  1. Указать базовый блок в зависимостях модульной системы.
  2. Передать ссылку на базовый блок в специальном поле baseBlock декларации.

Например, блок bblock наследуется от блока ablock:

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

    provide(BEMDOM.decl(this.name, {}));

});

modules.define('bblock', ['i-bem__dom', 'ablock'], function(provide, BEMDOM, A) {

    provide(BEMDOM.decl({ block : this.name, baseBlock : A }));

});

Такой механизм позволяет использовать методы базового блока внутри производного. Для вызова одноимённых методов базового блока служит вспомогательное свойство this.__base.

Важно: В i-bem можно создавать цепочки наследования — блок наследуется от другого, который, в свою очередь, наследуется от третьего и т.д.

Доопределение блока

Чтобы создать вариант уже существующего блока с изменённой или дополненной функциональностью, можно доопределить базовый блок на уровне переопределения проекта.

Для этого в проекте создаётся декларация нового блока с тем же именем, что и у базового. В результате блоку будет доступна вся функциональностью базового. Реализация одноимённых методов и модификаторов, при этом, будет взята из новой декларации.

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

    provide(BEMDOM.decl(this.name, {})); // Объявляем базовый блок

});

modules.define('ablock', function(provide, ABlock) {

    provide(ABlock.decl({})); // Доопределяем базовый блок

});

Такая схема наследования часто используется при работе с библиотечными блоками.

Добавление модификатора к блоку

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

Для добавления модификатора необходимо передать методу decl доопределяемого блока:

  • хэш с ключами modName и modVal. Значением для modName служит строка — имя модификатора. Значением modVal — строка со значением модификатора.
  • хэш методов, которые будут доступны для блока с соответствующим модификатором. При наличии одноимённых методов и модификаторов, будет использована их реализация из хэша.
modules.define('ablock', ['i-bem__dom'], function(provide, BEMDOM) {

    provide(BEMDOM.decl(this.name, {})); // Объявляем базовый блок

});

modules.define('ablock', function(provide, ABlock) {

    // Доопределяем базовый блок с модификтором _m1_v1
    provide(ABlock.decl({ modName : 'm1', modVal : 'v1' }, {}));

});

Важно: Cтатические методы блока будут доступны всем его экземплярам вне зависимости от значений модификаторов. Модификаторы — это свойства экземпляра блока, а статические методы принадлежат классу блока и не учитывают состояния модификаторов.

Блоки-миксы

В i-bem.js для добавления востребованной функциональности к блокам используется специальный тип блоков — блоки-миксы. Главная особенность блоков-миксов состоит в том, что они не участвуют в цепочке наследования. Это позволяет примешивать реализованную в них функциональность к другим блокам без риска нарушить их связи с родительскими блоками (this.__base).

Примешивание блока-микса

Чтобы примешать к блоку один или несколько блоков-миксов, необходимо в декларации блока присвоить значение опциональному полю baseMix. Значением служит массив строк — имён примешиваемых блоков-миксов:

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

    provide(BEMDOM.decl({ block : this.name, baseMix : ['foo', 'bar']},
        {
            /* методы экземпляра */
        },
        {
            /* статические методы */
        }
    ));

});

Декларация блока-микса

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

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

    provide(BEMDOM.declMix('mymix', // только строка с именем
        {
            /* методы экземпляра */
        },
        {
            /* статические методы */
        }
    ));

});

Важно: Блок-микс нельзя инстанциировать и использовать как самостоятельный блок.

Декларация триггеров

Триггеры, выполняемые при установке модификаторов, описываются в декларации блока. Для этого в хэше методов экземпляра блока зарезервированы свойства:

  • beforeSetMod — триггеры, вызываемые до установки модификаторов блока;
  • beforeElemSetMod — триггеры, вызываемые до установки модификаторов элементов;
  • onSetMod — триггеры, вызываемые после установки модификаторов блока;
  • onElemSetMod — триггеры, вызываемые после установки модификаторов элементов блока.
modules.define('block-name', function(provide, BEMDOM) {

    provide(BEMDOM.decl(this.name,
        {
            /* методы экземпляра */

            beforeSetMod: { /* триггеры до установки модификаторов блока */ },

            beforeElemSetMod: { /* триггеры до установки модификаторов элементов*/},

            onSetMod: { /* триггеры после установки модификаторов блока */ },

            onElemSetMod: { /* триггеры после установки модификаторов элементов */ }
        },
        {
            /* статические методы */
        }
    ));

});

Значение свойств beforeSetMod и onSetMod — хэш, связывающий изменения модификаторов с триггерами. Триггер получает аргументы:

  • modName — имя модификатора;
  • modVal — выставляемое значение модификатора;
  • prevModVal — предыдущее значение модификатора. Для beforeSetMod это текущее значение модификатора, которое будет заменено на modVal, если триггер не вернёт false.
BEMDOM.decl(this.name, {
    onSetMod: {
        // установка mod1 в любое значение
        'mod1': function(modName, modVal, prevModVal) { /* ... */ },

        'mod2': {
            // триггер на установку mod2 в значение val1
            'val1': function(modName, modVal, prevModVal) { /* ... */ },

            // триггер на установку mod2 в значение val2
            'val2': function(modName, modVal, prevModVal) { /* ... */ },

            // триггер на удаление модификатора mod2
            '': function(modName, modVal, prevModVal) { /* ... */ }
        },

        'mod3': {
            // триггер на установку простого модификатора mod3
            'true': function(modName, modVal, prevModVal) { /* ... */ },

            // триггер на удаление простого модификатора mod3
            '': function(modName, modVal, prevModVal) { /* ... */ }
        },

        // триггер на установку любого модификатора в любое значение
        '*': function(modName, modVal, prevModVal) { /* ... */ }
    }
})

Для триггера на установку любого модификатора блока в любое значение существует сокращённая форма записи:

beforeSetMod: function(modName, modVal, prevModVal) { /* ... */ }

onSetMod: function(modName, modVal, prevModVal) { /* ... */ }

Триггеры на установку модификаторов элемента описываются в свойствах beforeElemSetMod и onElemSetMod. Хэш в значениях свойств имеет дополнительный уровень вложенности — имя элемента. В качестве аргументов триггеру передаются:

  • elem — имя элемента;
  • modName — имя модификатора;
  • modVal — выставляемое значение модификатора;
  • prevModVal — предыдущее значение модификатора. Для beforeSetMod это текущее значение модификатора, которое будет заменено на modVal, если триггер не вернёт false.
BEMDOM.decl(this.name, {
    onElemSetMod: {
        'elem1': {
            // триггер на установку mod1 элемента elem 1 в любое значение
            'mod1': function(elem, modName, modVal, prevModVal) { /* ... */ },

            'mod2': {
                // триггер на установку mod2 элемента elem1 в значение val1
                'val1': function(elem, modName, modVal, prevModVal) { /* ... */ },

                // триггер на установку mod2 элемента elem1 в значение val2
                'val2': function(elem, modName, modVal, prevModVal) { /* ... */ }
            }
        },

        // триггер на установку любого модификатора элемента elem2 в любое значение
        'elem2': function(elem, modName, modVal, prevModVal) { /* ... */ }
    }
})

Сокращённая запись для триггера на установку любого модификатора элемента elem1 в любое значение:

beforeElemSetMod: {
    'elem1': function(elem, modName, modVal, prevModVal) { /* ... */ }
}

onElemSetMod: {
    'elem1': function(elem, modName, modVal, prevModVal) { /* ... */ }
}
Если вы заметили ошибку или хотите чем-то дополнить статью, вы всегда можете или написать нам об этом на Гитхабе, или поправить статью с помощью prose.io.