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