Войти с помощью github
Форум /

Телезритель из Воронежа Ваня @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.

Если у вас остались вопросы, смело задавайте их в комментариях. Мы обязательно ответим!