Телезритель из Воронежа Ваня @voischev задает вопрос на давнюю тему «БЭМ — это не только про CSS», а мы с радостью и отвечаем:
Да, действительно, БЭМ — это не только про CSS.
БЭМ — это про компонентный подход к разработке в целом.
Он предполагает, что каждая полезная сущность (здесь мы называем ее блоком) может быть представлена в одной или сразу нескольких технологиях.
Например, если перед нами логотип, то скорее всего он будет реализован в двух технологиях: шаблоне и стилях.
<a class="logo" href="/">Ваша крутая компания</a>
и
.logo {
width: 150px;
height: 100px;
background: url(logo.png) no-repeat;
}
И шаблон и стили будет удобно положить в одну папку. Так при желании переиспользовать блок мы легко найдем все его части и спокойно перенесем на новое место в отличие от ситуации, когда CSS — это одна «портянка» в папке css/
, а JavaScript — в js/
, и чтобы перенести какую-то часть куда-то, нужно еще долго копаться в контексте.
Разумеется, встречаются блоки, которым требуется еще и JS для работы. Их реализацию по той же логике, что и выше, мы также кладем в папку с блоком. Выглядеть это может, например, так:
$('.logo').on('click', doSomething);
Конечно, бывают ситуации, когда блок состоит только из CSS (например, clearfix или только JS, как, скажем, блок для работы с куками.
Логика при этом не меняется: это по-прежнему блоки, которые по-прежнему имеют свою папку в файловой системе и аналогично другим блокам попадают в сборку.
Следуя все той же логике, мы реализуем разные технологии блока. Ими могут быть не только традиционные CSS и JS, но и картинки, тесты, документация, примеры использования, исходники и так далее. Со временем блок обрастает нужным «мясом», а мы все также легко можем его декомпозировать и масштабировать без урона для проекта.
В качестве примера можно взглянуть на блок button из библиотеки bem-components.
Когда-то давно БЭМ со своим «компонентным» подходом (тогда он еще так не назывался) нес в массы новые, не всегда понятые идеи. Сегодня ситуация изменилась. Этот же компонентный подход уже не нов и реализован не в одном, а многих продуктах, например, в Polymer или даже в стандарте Web Components.
Рассмотрим на примерах.
«Вы говорите, что блоки должны быть независимыми, но на уровне JavaScript они обязаны общаться друг с другом. Как?»
Давайте рассмотрим пример: у нас есть форма, перед отправкой которой необходимо проверить, что введено корректное значение, а в случае ошибки показать попап с предупреждением.
<form class="form" action="/">
<input class="input" name="email">
<input class="button" type="submit">
<div class="popup">Пожалуйста, введите корректный email</div>
</form>
.popup {
display: none;
}
.popup_visible {
display: block;
}
Как это могло быть реализовано в стиле old school?
$('.button').on('click', function(e) {
if (!/\S+@\S+\.\S+/.test($('.input').val())) {
$('.popup').addClass('popup_visible');
return false;
}
});
Всего 6 простых строчек, все работает. Однако, так делать плохо. Почему?
Эти 6 строк кода — отличный пример того, что называют сильной связанностью кода: кнопка «знает» про поле ввода и попап, кроме того, она явно подозревает, что находится внутри формы.
Если в процессе рефакторинга какой-либо из компонентов исчезнет, код сломается. Если появится еще одно поле, код снова сломается. Если вы захотите переиспользовать такую кнопку, вам обязательно придется тянуть за собой все компоненты с такими же классами и гарантировать, что больше нигде на странице они не встречаются.
В результате, несмотря на простоту кода, он доставит вам намного больше серьезных проблем с поддержкой, ведь код сложно поддерживать и практически бесполезно реиспользовать, если только вы не делаете точную копию проекта.
Что можно улучшить?
$('.form').on('submit', function(e) {
if (/\S+@\S+\.\S+/.test($('.input', this).val())) return true;
e.preventDefault();
$('.popup', this).addClass('popup_visible');
});
Что изменилось?
Мы переписали форму так, чтобы за все, что происходит с ней, отвечала она сама. Теперь компоненты внутри ничего не знают о существовании друг друга. А мы можем смело взять кнопку и перенести ее на другой проект, ведь она стала независимой: теперь за ней не потянется знание о какой-то форме, поле ввода и попапе.
Кроме того, мы вынесли все селекторы за рамки контекста формы и теперь можем добавлять сколько угодно новых полей ввода, попапов и кнопок за пределами формы — ничего не сломается.
Есть ли что-то еще, что можно улучшить?
Да. Если мы добавим еще одно поле, придется рефакторить код. Кроме того, чтобы гарантировать перекрытие попапом любых других элементов на странице, нам необходимо положить его в самом конце DOM-дерева, перед закрывающим тегом </body>
.
Продолжаем улучшать
Вынесем попап из формы и добавим еще одно поле. Сами поля смиксуем с элементами формы.
Микс — это объединение нескольких блоков на одном DOM-узле.
<form class="form" action="/">
<input class="input form__login" name="login">
<input class="input form__email" name="email">
<input class="button" type="submit">
</form>
<div class="popup form__hint">Пожалуйста, введите корректный email</div>
Теперь наш код выглядит так:
$('.form').on('submit', function(e) {
if (/\S+@\S+\.\S+/.test($('.form__email', this).val())) return true;
e.preventDefault();
$('.form__hint').addClass('popup_visible');
});
Мы исправили предыдущие проблемы, но появилась новая: если на странице окажется несколько форм, как каждая из них найдет свой попап?
Решение, да не одно
В качестве одного из решений мы можем реализовать механизм, который позволит выражать один блок на нескольких DOM-нодах. Схематично он может выглядеть так:
<form class="form" action="/" data-id="1">
<input class="input form__login" name="login">
<input class="input form__email" name="email">
<input class="button" type="submit">
</form>
<div class="popup form form__hint" data-id="1">Пожалуйста, введите корректный email</div>
Мы добавили форме data-атрибут с идентификатором и помимо элемента примиксовали к попапу саму форму с таким же идентификатором.
Теперь мы можем в коде сказать, что нам нужен элемент hint
именно этого блока form
, а не какого-то другого:
$('.form').on('submit', function(e) {
if (/\S+@\S+\.\S+/.test($('.form__email', this).val())) return true;
e.preventDefault();
$('.form__hint').filter('.form[data-id=' + $(this).data('id') + ']').addClass('popup_visible');
});
Следующее решение поможет нам сохранить независимость блоков, но избавиться от необходимости вносить изменения в DOM.
Воспользуемся паттерном проектирования Посредник в очень упрощенном виде.
С ним наши компоненты будут знать о существовании посредника, но не будут ничего знать друг о друге. Вся коммуникация будет происходить на основе сообщений, которые компоненты будут публиковать и слушать на посреднике.
Чтобы максимально упростить пример, сделаем таким посредником body
. Он всегда присутствует в коде и определенно знает о всех компонентах, которые находятся внутри, + может обеспечить обмен сообщениями.
<body class="page">
<form class="form" action="/">
<input class="input form__login" name="login">
<input class="input form__email" name="email">
<input class="button" type="submit">
</form>
<div class="popup"></div>
</body>
var page = $('.page');
page.on('error', function(e, data) {
$('.popup')
.text(data)
.addClass('popup_visible');
});
$('.form').on('submit', function(e) {
if (/\S+@\S+\.\S+/.test($('.form__email', this).val())) return true;
e.preventDefault();
page.trigger('error', 'Ошибка валидации');
});
Теперь в случае ошибки валидации форма сообщит об этом посреднику — page
. Все компоненты, которые должны реагировать на это событие, могут «подписаться» на него через page.on()
.
В качестве еще одного решения можно воспользоваться паттерном MVC и обеспечить валидацию формы на уровне модели.
Подытожим: методология БЭМ — не только про CSS. Она затрагивает все технологии реализации блока, включая JS, и и помогает писать JavaScript-код, который сохранит независимость ваших блоков, упростит рефакторинг и продлит жизнь проекту.
«Зачем нужен i-bem.js
, если можно писать JS для независимых блоков на привычном jQuery
?»
Такой вариант возможен. И более того, i-bem.js
написан с использованием jQuery
.
Зачем же нам понадобился отдельный блок?
Решая одни и те же задачи на JavaScript в терминах блоков, элементов и модификаторов, мы регулярно делали одни и те же действия. И чтобы автоматизировать процесс и избавиться от копипаста, а также предоставить удобные хелперы пользователям, мы написали i-bem.js.
Если у вас остались вопросы, смело задавайте их в комментариях. Мы обязательно ответим!