Skip to content
Permalink
bem-info-data
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time

JavaScript по БЭМ. Основные понятия

Статья подготовлена по материалам выступления Владимира Варанкина БЭМ и JavaScript: Зачем мы написали JS-фреймворк? на Я.Субботнике в Москве 8 сентября 2012.

В стеке БЭМ-технологий есть блок i-bem библиотеки bem-core, который также называют блоком-хелпером. Его JavaScript-реализация использует предметную область БЭМ и позволяет работать по всем БЭМ-принципам не только с внешним видом компонент, но и с их поведением.

Зачем нужен еще один фреймворк?

Разработчики старой школы ещё помнят времена, когда не было даже jQuery и все приходилось делать самостоятельно. В каждом проекте был свой common.js-файл, который включал в себя набор вспомогательных функций. Его копировали из проекта в проект, а потом выносили в свою маленькую JavaScript библиотеку.

Так, эволюционно появлялись JavaScript-фреймворки.

Похожие этапы преодолел и БЭМ в создании своего фреймворка. Сначала понятие блоков (интерфейсных модулей), их элементов и модификаторов существовало только для CSS. Затем разработчики захотели работать с такой же структурой в JavaScript, и использовать ее ключевое понятие — уровни переопределения — чтобы дополнять и расширять поведение блоков от проекта к проекту.

Так появилась JavaScript реализация блока-хелпера i-bem.js. Он и стал фреймворком (или блоком-ядром) для того, чтобы писать JavaScript в терминах БЭМ.

Ассоциация с HTML-кодом

Как и любой JavaScript-компонент, код, написанный под i-bem.js должен быть проассоциирован с HTML-фрагментом, который он намерен превратить в функционирующую часть интерфейса. В БЭМ для этого достаточно добавить блоку CSS-класс i-bem и указать в атрибуте data-bem параметры блока.

<div lass="myblock i-bem" data-bem="{ myblock: { }}">
    <span class="myblock__item"></span>
</div>

Декларация поведения

В JavaScript-файле блока (myblock.js) описывается его поведение.

С точки зрения объектной модели все одинаковые блоки образуют класс. При этом каждое появление блока на странице рождает экземпляр этого класса.

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

Для описания поведения используется метод decl, принимающий следующие параметры:

  1. Блок, о котором пойдёт речь.
  2. Собственные свойства экземпляра блока.
  3. Статические свойства класса, к которому принадлежит блок.
modules.define('myblock', ['i-bem__dom'], function(provide, BEMDOM) {

  provide(BEMDOM.decl(this.name, {

        /\* собственные свойства экземпляра \*/

    }, {

        /\* статические свойства \*/

    }));

});

В JavaScript ссылку на экзепляр всегда можно получить по ключевому слову this и использовать его зарезервированные поля __self и __base.

  • this.__self
    Ссылается на статические методы класса, к которому принадлежит экземпляр.
  • this.__base
    Делает super call, то есть вызывает базовую реализацию метода.

Последнее позволяет использовать уровни переопределения. При расширении функциональности уже существующего блока, разработчик всегда имеет доступ к поведению, определённому предыдущим уровнем. То есть методы можно не только полностью перезаписывать, но и «обрамлять» дополнительным поведением.

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

    provide(BEMDOM.decl('myblock', {

        method: function() {

            this.__base();
            this.doMore();

        }

    }));
});

Кроме наследования по уровням переопределения есть ещё возможность явно отнаследовать один блок от другого. Поэтому возможность наследования от уровня к уровню лучше воспринимать как соединение (merge) реализаций.

Селекторы блоков

Для поиска других блоков можно воспользоваться одним из find* методов. Выбор метода зависит от того, где находится желаемый блок относительно текущего:

// поиск внутри контекста
this.findBlockInside([elem], block)

// поиск снаружи контекста
this.findBlockOutside([elem], block)

// поиск на DOM-узле текущего блока
this.findBlockOn([elem], block)

Все эти методы возвращают JavaScript-объект, экземпляр найденного блока.

Похожим образом можно найти и коллекции блоков:

// поиск внутри контекста
this.findBlocksInside([elem], block)

// поиск снаружи контекста
this.findBlocksOutside([elem], block)

// поиск на BEM-узле текущего блока
this.findBlocksOn([elem], block)

Элементы

Аналогично, есть методы для доступа к элементам блока: elem и findElem. Метод elem кэширует свой результат при первом обращении. Это его основное отличие от метода findElem. То есть можно не сохранять вызов в переменную, чтобы сэкономить на поиске — всё уже сделано в реализации этого метода.

//кэширующий селектор
this.elem(name,
    [modName], [modVal])

//некэширующий
this.findElem([ctx], name,
    [modName], [modVal])

Модификаторы

Модификаторы в JavaScript служат для выражения состояния блока или элемента.

Методы работы с модификаторами одинаковы и для блоков, и для элементов. Но первый (опциональный) параметр показывает, о чём идёт речь.

// значение модификатора блока
this.getMod(modName)

// значение модификатора элемента
this.getMod(elem, modName)

// проверка модификатора
this.hasMod([elem], modName, modVal)

// установка модификатора
this.setMod([elem], modName, modVal)

// удаление модификатора
this.delMod([elem], modName)

// переключение значений модификатора
this.toggleMod([elem], modName,
    modVal1, modVal2, [condition])

Сами модификаторы описываются как состояния блока. То есть, экземпляр блока знает о том, как ему нужно реагировать на установку модификатора.

Для такого описания используется поле onSetMod из собственных свойств блока.

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

    provide(BEMDOM.decl('myblock', {
        onSetMod : {
            'mod1' : {

                // установка модификатора mod1 в val1
                'val1' : function(mod, val, oldVal) {
            },
            // установка модификатора`mod2` в любое значение
            'mod2' : function(mod, val, oldVal) {
            }
        }
    }));
});

Похожая декларация есть и для модификаторов элементов:

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

provide(BEMDOM.decl('myblock', {
    // …

    onElemSetMod : {

        // структура, аналогичная блоку
        'elem' : {
            'mod1' : {

                // дополнительный параметр `elem`
                'val1' : function(elem, mod, val, oldVal) {
                }

            }
        }
    }

}));

});

События

События играют ключевую роль в JavaScript. Поэтому и при реализации блока i-bem их не обошли вниманием. Специальные методы позволяют работать с событиями как на DOM-узлах, соответствующих блокам, так и на BEM-объектах (JavaScript-объектах, представляющих экземпляры блоков).

// DOM-события
this
    .bindTo([elem], event, fn)
    .unbindFrom([elem], event)

// BEM-события
this
    .on(event, [data], fn, [ctx])
    .un(event, [data], fn, [ctx])
    .trigger(event, [data])

DOM-события не нуждаются в пояснении — это то, что происходит в результате действий пользователя: клик мышкой, работа с клавиатурой, прокрутка и т.д.

BEM-события — это кастомные события, необходимые для возможности организовать API блоков.

Инициализация

Работа блока начинается с его инициализации. В этот момент у блока появляется модификатор js_inited.

Аналогично другим модификаторам, на его установку можно реагировать исполнением задекларированного кода. То есть, существует возможность написать «конструктор».

onSetMod : {

    'js' : {

        'inited' : function(){

            // «конструктор» блока

        }
    }
}

Кроме описанного, блок i-bem предоставляет для других блоков возможности отложенной (ленивой) инициализации, позволяет писать блоки без DOM-представления, использует идею делегирования событий и многое другое.

Об этом можно прочесть на странице блока i-bem.