JavaScript по БЭМ
В БЭМ-методологии JavaScript — это одна из технологий реализации блока. Поэтому к JavaScript наравне с CSS применяются основные принципы методологии БЭМ:
Особенности реализации JavaScript по БЭМ-методологии рассматриваются в следующих разделах:
Единая предметная область
Для работы с JavaScript наравне с другими технологиями используются термины блоков, элементов и модификаторов.
В БЭМ к JavaScript применяется компонентный подход, который в отличие от Web Components не требует совместимости с браузерами. Независимым компонентом является блок. Аналогом Shadow Dom выступают элементы блока, которые реализуют принцип инкапсуляции. Модификаторы блоков и элементов отвечают за состояния. Установка/снятие модификатора изменяет поведение блока.
Таким образом в БЭМ используется абстракция над DOM-деревом — БЭМ-дерево.
Рассмотрим пример, в котором необходимо показать всплывающее окно (блок popup
).
Один из наиболее распространенных способов — добавить соответствующий класс и жестко прописать имя блока в коде.
document.querySelector('.button').addEventListener('click', function() {
document.querySelector('.popup').classList.toggle('popup_visible');
}, false);
В БЭМ-проекте нет необходимости хардкодить имя блока. Поиск компонента выполняется по имени блока, так как имя всегда уникально. Сам компонент может выражаться классом, тегом, атрибутом и т.д. Отображение всплывающего окна также не зависит от класса: блок переводится в состояние visible
с помощью добавления модификатора.
block('button').click(function() {
block('popup').toggleMod('visible');
});
Важно Для примеров, написанных по БЭМ-методологии, используется псевдокод. Реальные примеры реализации представлены в документации к i-bem.js.
Привязка JavaScript-блоков к HTML
Первичным «каркасом» страницы является HTML-дерево документа. Блокам с JavaScript-реализацией могут соответствовать узлы в HTML с именем блока в атрибуте class
. В этом случае говорится о том, что блоки имеют DOM-представление.
Такой способ привязки JavaScript-компонентов к DOM-дереву имеет следующие преимущества:
естественная деградация интерфейса на клиентах с отключенным JavaScript;
прогрессивный рендеринг — возможность начинать отрисовку элементов интерфейса до окончания загрузки всех данных страницы.
В простейшем случае блок соответствует DOM-узлу один к одному. Однако DOM-узел и блок — это не всегда одно и то же. Можно разместить несколько блоков на одном DOM-узле (это называется микс), а также реализовать один блок на нескольких DOM-узлах.
Разделение кода на части
В БЭМ-методологии поведение каждого блока описывается независимо. Независимость блоков в JavaScript позволяет повторно использовать блоки и реализуется за счет использования принципов:
Логика работы каждого блока, его опциональных элементов и модификаторов описывается в отдельных файлах. JavaScript-файлы хранятся в соответствии с правилами организации файловой структуры БЭМ-проекта.
Рассмотрим на примере логотипа (блок logo
), реализованного в технологии CSS.
Блок logo
в файловой структуре проекта:
logo/ # Директория блока logo logo.css # Реализация блока logo в технологии CSS
Добавим блоку logo
новую функциональность: нажатие на логотип будет вызывать какое-то действие.
JavaScript-реализация блока logo
:
document.querySelector('.logo').addEventListener('click', doSomething, false);
Файл logo.js
в файловой структуре блока logo
:
logo/ # Директория блока logo logo.css # Реализация блока logo в технологии CSS logo.js # Реализация блока logo в технологии JavaScript
Реализация поведения блока, как и его внешнего вида, независима и отделена от реализации других блоков не только логически, но и физически — вынесена в отдельный файл.
Принцип инкапсуляции
В БЭМ внутренняя реализация блока скрыта. Блок предоставляет API для взаимодействия с другими блоками. Таким образом достигается независимость блока, возможность его повторного использования.
Элементы всегда являются внутренней реализацией блока, поэтому обращение к ним возможно только через API самого блока.
Принцип декларативности
Логика работы блока описывается декларативно: как набор действий и условий, при которых эти действия необходимо выполнять. Это позволяет разделять функциональность блока на отдельные части и использовать уровни переопределения.
Принцип наследования
Декларативное описание поведения блоков позволяет использовать методы базового блока внутри производного, наследовать их. Новый блок может получать все свойства и методы базового.
Также можно создавать цепочки наследования — блок наследуется от другого, который, в свою очередь, наследуется от третьего и т.д.
Примеры реализации доступны в документации к i-bem.js.
Работа с уровнями переопределения
Декларативное описание работы блока обеспечивает возможность использовать уровни переопределения для JavaScript наравне с CSS:
реализовывать новое поведение блока на другом уровне переопределения, сохраняя предыдущее, наследовать и дополнять его (делать super call);
полностью перекрывать поведение блока (переопределять);
добавлять блоки с новым поведением в проект.
С помощью уровней переопределения можно создать универсальную JavaScript-библиотеку блоков, изменять ее на проектном уровне и включать в проект только необходимое поведение блоков.
Подробнее о подключении библиотеки в проект.
Рассмотрим пример формы отправки сообщения.
block('button').onSetMod({
focused: {
true: this.onFocus,
false: this.onBlur
}
});
Запись в БЭМ-терминах позволяет:
Полностью перекрывать поведение блока на другом уровне переопределения.
block('button').onSetMod({ focused: { true: this.someCustomOnFocused // Полное изменение поведение блока } });
Добавлять или частично изменять поведение блока на другом уровне переопределения.
block('button').onSetMod({ focused: { true: function() { this.__base.apply(this, arguments); // Вызываем предыдущую реализацию this.someCustomOnFocused(); } } });
Для работы с JavaScript в БЭМ-терминах и использования уровней переопределения в БЭМ создан специализированный фреймворк i-bem.js.
Работа с блоками
Взаимодействие блоков
БЭМ-методология предполагает работу с независимыми блоками. Однако на практике полная независимость блоков недостижима.
Блоки могут взаимодействовать друг с другом с помощью:
Подписки на события других экземпляров блоков.
Подписки на изменения модификаторов.
Непосредственного вызова методов других экземпляров блоков или статических методов класса другого блока.
Любых паттернов взаимодействия. Например, канала событий: все коммуникации происходят благодаря сообщениям, которые компоненты публикуют и слушают с помощью посредника.
Примеры реализации доступны в документации к i-bem.js.
БЭМ-методология рекомендует выстраивать взаимодействие между блоками в иерархическом порядке в соответствии с их расположением в DOM-дереве. Вложенный блок не должен ничего знать о родительском блоке, так как это нарушает принцип независимости компонентов.
Взаимодействие блока с элементами
Элемент — это внутренняя реализация блока. Для работы блока с его элементами принято реализовывать дополнительные хелперы блока. Обращение напрямую к элементу другого блока невозможно. Взаимодействие с элементом происходит только через API блока, которому принадлежит данный элемент.
Работа с модификаторами
Поведение блока описывается с помощью состояний. Модификаторы содержат информацию о состояниях блока. Перевод блока в другое состояние производится при помощи установки/снятия модификатора. Изменение модификатора создает событие, которое можно использовать для работы с блоком.
Например, чтобы отметить чекбокс, блоку checkbox
нужно установить модификатор checked
в значение true
.
Для корректной работы JavaScript в БЭМ-проекте все манипуляции с модификаторами должны производиться при помощи методов-хелперов. Изменять значение модификаторов следует с помощью спец методов, а не менять напрямую CSS-класс на соответствующем DOM-узле.
Примеры реализации доступны в документации к i-bem.js.
Реакция на изменение модификаторов
В БЭМ реакция на установку/снятие модификатора описывается декларативно: изменение состояния автоматически вызывает код, который задекларирован для этого состояния. Если появляется модификатор (добавляется новый класс к DOM-узлу), то вся функциональность, свойственная этому модификатору, также применяется. Если модификатор исчезает, функциональность отключается.
Переход блока из одного состояния в другое часто сопровождается изменениями его внешнего вида. Так как добавление модификатора блоку изменяет его класс на DOM-узле, а стили написаны опираясь только на классы, изменение класса автоматически приводит к изменению внешнего вида блока.
Чтобы динамически изменять состояния блоков и элементов, используются специальные методы для установки и снятия модификаторов.
Примеры реализации доступны в документации к i-bem.js.
Рассмотрим форму отправки сообщения, в которой выполняется следующее условие: если введен неправильный email, кнопка отправки (блок button
) становится недоступна (получает модификатор button_disabled
).
Один из способов решения этой задачи — жестко прописать все условия в коде и постоянно выполнять проверку. Такой подход неудобен, так как любое изменение потребует изменений в коде вручную.
Другой способ — использовать методологию БЭМ и задекларировать поведение блока таким образом, чтобы получить возможность перекрывать каждый модификатор отдельно на новом уровне переопределения. В декларации можно указать, как блок или элемент должен отреагировать на изменение модификатора.
block('button').onSetMod({
focused: {
true: this.onFocus,
false: this.onBlur
}
});
Такой подход дает возможность:
Определять каждому состоянию свой внешний вид, добавив стили модификатору.
Изменять или полностью перекрывать поведение блока с помощью уровней переопределения.
Как перейти на JavaScript по БЭМ
Чтобы реализовать принципы БЭМ в проекте, необходимо:
работать в единых терминах блоков, элементов и модификаторов во всех технологиях;
создавать независимые компоненты (блоки) на уровне JavaScript;
описывать поведение блока как набор действий и условий их выполнения;
обращаться к элементам блока только через API самого блока и не нарушать принцип инкапсуляции;
изменять поведение блоков, элементов и модификаторов с помощью уровней переопределения по аналогии с CSS;
разделять код на мелкие независимые части для удобства работы с отдельными блоками;
повторно использовать блоки.
Чтобы начать использовать JavaScript по БЭМ в рабочем проекте, можно начать применять принципы БЭМ-методологии без использования специализированного фреймворка. Пример проекта с JavaScript по БЭМ на jQuery описан в статье БЭМ — это не только про CSS.