Open tadatuta opened 9 years ago
Спасибо! Кажется это должны быть серия статей :) теперь хочется узнать про модульную систему с использованием i-bem.js и шаблонизаторы
@voischev Давай какие-нибудь наводящие вопросы. Иначе получится пересказ существующей документации.
@tadatuta Можно начать как форму описанную выше представить в i-bem.js плюс показать как круто можно доопределять код в зависимости от уровня. Показать на простом примере и тоже самое как бы это выглядело с использованием инструментов БЭМ. И что таким образом можно положить код в библиотеку блоков и их потом реиспользовать. Зачем вообще можно использовать модульную систему и i-bem.js в простых проектах без сборки bem-tools
Про шаблонизаторы хотелось бы описать проблему реиспользуемости кода и доопределения. Организовать так что бы можно было использовать одну верстку с разными темами оформления. Сравнить какой нибудь шаблонизатор с тем что предлагает БЭМ стек. Например нам нужно делать много лендинг-пейдж и тогда в случае с простым шаблонизатором у нас скорее всего был бы постоянный копи паст. А в случае с bemhtml есть возможность организовать какой то common код и в проекте уже точечно доопределять что-то.
Описать например подобную ситуацию — "к нам пришел старый клиент и попросил заменить на всех десяти его лендинг пейджах все списки с ul>li>a
на nav>a
потому например так СЕОшники сказали, а еще добавить микроразметку к этим спискам"
Приведу пример реализации для предпоследнего варианта разметки с небольшими изменениями под i-bem.js
:
<form class="form i-bem" data-bem='{"form":{"id":1}}'>
<input class="form__login input" name="login">
<input class="form__email input" name="email">
<button type="submit">Отправить</button>
</form>
<div class="form popup i-bem" data-bem='{"form":{"id":1}}'>Пожалуйста, введите корректный email</div>
Видно, что к форме добавился класс i-bem
, который позволяет определить, что на данном DOM-узле необходимо инициализировать JS-блок. Кроме того, в атрибуте data-bem
мы явно указываем какому блоку принадлежит id
по которому связываем форму и попап. Это необходимо для того, чтобы иметь возможность миксовать на одном DOM-узле несколько разных блоков с JS-реализацией.
// декларируем модуль form, который зависит от i-bem__dom
modules.define('form', ['i-bem__dom'], function(provide, BEMDOM) {
// т.к. модульная система работает асинхронно, передаем модуль в callback
provide(BEMDOM.decl(this.name, { // декларируем блок
onSetMod: { // когда установится модификатор
js: { // js
inited: function() { // в значение inited (это произойдет в момент инициализации блока)
this.bindTo('submit', this._onSubmit); // начинаем слушать событие submit
}
}
},
_onSubmit: function(e) {
// чтобы получить элемент текущей формы, мы просто используем хелпер i-bem.js,
// нам не нужно заботиться о соблюдении контекста или следить за именами при рефакторинге
if (/\S+@\S+\.\S+/.test(this.elem('email').val())) return true;
e.preventDefault();
// findBlockOn позволяет найти блок на DOM-узле текущего блока. При этом i-bem.js автоматически учитывает, что блок form представлен двумя разными DOM-узлами и ищет по обоим
// а setMod — работать с модификаторами гораздо удобнее, чем простой конкатенацией классов
this.findBlockOn('popup').setMod('visible', true);
}
}));
});
Разумеется, профит от использования фреймворка проявляется тем сильнее, чем больше становится проект.
@tadatuta Сложновато написал реализацию для popup
)
@voischev сходу не вижу, а что именно тебя смущает?
@tadatuta да это я по началу не понял что ты через микс связал блоки.
@tadatuta Но тут вопрос. А что если у формы будут какие то стили? Они обязательно повлияют на попап? Нам бы этого не хотелось.
И я бы спросил у читателей. Все понимают как подключить модульную систему (ymodules) и i-bem.js к своему проекту. Кто как это делает?
@voischev ну по поводу подключения i-bem.js я как-то спрашивал и я подключаю просто файлик i-bem.js к странице. Но там нет использования ymodules. Там что-то типа такого кода:
"use strict"
BEM.DOM.decl('b-button', {
onSetMod : {
'js' : {
'inited': function() {
this.bindTo('click', function(e) {
var domElem = $(e.currentTarget); // DOM-элемент, на котором слушается событие
// в данном случае то же, что this.domElem
this.setMod('size', 'big');
});
}
}
}
});
BEM.DOM.init($('body'));
А вот как подключить ymodules? P.S. Я не использую полный БЭМ стек. P.P.S: Спасибо за добавление функции предпросмотра сообщений!
@kuflash Тоже нужно подключить файлик к странице. Скачивать можно из NPM https://www.npmjs.com/package/ym или https://github.com/ymaps/modules Вот это описание, зачем оно нужно, мне больше всего нравится https://github.com/ymaps/modules/blob/master/what-is-this.md Там как раз пример с формой и кнопкой.
Еще есть тестовый проект на котором я учился работать с модулями https://github.com/voischev/YModules-demo возможно будет полезен.
@kuflash судя по коду, используется i-bem
из bem-bl
.
У меня в примере из bem-core
— можно сказать, следующем поколении bem-bl
.
Про отличия можно почитать тут — изменения в версии 1.0.0
даны по отношению к bem-bl
.
@voischev при текущей реализации — да.
Как один из вариантов решения можно завернуть содержимое формы в form__inner
и писать стили уже на него.
Еще вариант — описывать логику не в универсальной form
, а в неком конкретном блоке про получение логина и ящика (скажем, credentials
). Тогда никаких пересечений не будет.
Еще — описывать внешний вид не прямо в блоке, а в модификаторе про тему, тогда у попапа будет <div class="form popup popup_theme_cool">
, так что стили из .form_theme_cool
не него не повлияют.
Ну и так далее ;)
В целом про подключение i-bem.js
отдельно мы давно обещаем, что будет возможность подключить предсобранный файл с нашего CDN. К сожалению, все никак не доходят руки :(
Но обязательно сделаем и расскажем.
Самая полезная серия постов. Спасибо.
А разве можно, чтобы элемент был вне ноды родителя? Мой мир рухнул. А если хотим убрать блок со страницы, нужно по всему коду бегать убирать элементы, вне ноды блока?
<form class="form" action="/">...</form>
<div class="popup form__hint">Пожалуйста, введите корректный email</div>
@hudochenkov В общем случае BEM-дерево не обязано соответствовать DOM-дереву.
Вы цитируете "промежуточный" вариант решения проблемы, по сути отражающего ход мысли. Уже в следующем сниппете на вынесенный элемент добавляются 2 существенные детали: класс блока form
и js параметр data-id="1"
связывающий 2 dom ноды одного js блока.
@tadatuta поправит меня, если я ошибся.
@Guria да, все так.
@Guria @tadatuta спасибо. Я думал, что каждый из представленных сниппетов один из вариантов BEM блока, пусть и не всегда самый лучший.
Ну вообще примеры довольно абстрактны и построены на обычном jquery без использования i-bem.js просто для демонстрации подхода применения методологии к javascript.
Более того ни один из примеров не является реализацией "bem-core совместимого БЭМ-блока"
@tadatuta Скажите, пожалуйста, зачем нужно this в .test($('.input', this) ?
Телезритель из Воронежа Ваня @voischev задает вопрос на давнюю тему «БЭМ — это не только про CSS», а мы с радостью и отвечаем:
Да, действительно, БЭМ — это не только про CSS.
БЭМ — это про компонентный подход к разработке в целом.
Он предполагает, что каждая полезная сущность (здесь мы называем ее блоком) может быть представлена в одной или сразу нескольких технологиях.
Например, если перед нами логотип, то скорее всего он будет реализован в двух технологиях: шаблоне и стилях.
и
И шаблон и стили будет удобно положить в одну папку. Так при желании переиспользовать блок мы легко найдем все его части и спокойно перенесем на новое место в отличие от ситуации, когда CSS — это одна «портянка» в папке
css/
, а JavaScript — вjs/
, и чтобы перенести какую-то часть куда-то, нужно еще долго копаться в контексте.Разумеется, встречаются блоки, которым требуется еще и JS для работы. Их реализацию по той же логике, что и выше, мы также кладем в папку с блоком. Выглядеть это может, например, так:
Конечно, бывают ситуации, когда блок состоит только из CSS (например, clearfix или только JS, как, скажем, блок для работы с куками.
Логика при этом не меняется: это по-прежнему блоки, которые по-прежнему имеют свою папку в файловой системе и аналогично другим блокам попадают в сборку.
Следуя все той же логике, мы реализуем разные технологии блока. Ими могут быть не только традиционные CSS и JS, но и картинки, тесты, документация, примеры использования, исходники и так далее. Со временем блок обрастает нужным «мясом», а мы все также легко можем его декомпозировать и масштабировать без урона для проекта.
В качестве примера можно взглянуть на блок button из библиотеки bem-components.
Когда-то давно БЭМ со своим «компонентным» подходом (тогда он еще так не назывался) нес в массы новые, не всегда понятые идеи. Сегодня ситуация изменилась. Этот же компонентный подход уже не нов и реализован не в одном, а многих продуктах, например, в Polymer или даже в стандарте Web Components.
Рассмотрим на примерах.
«Вы говорите, что блоки должны быть независимыми, но на уровне JavaScript они обязаны общаться друг с другом. Как?»
Давайте рассмотрим пример: у нас есть форма, перед отправкой которой необходимо проверить, что введено корректное значение, а в случае ошибки показать попап с предупреждением.
Как это могло быть реализовано в стиле old school?
Всего 6 простых строчек, все работает. Однако, так делать плохо. Почему?
Эти 6 строк кода — отличный пример того, что называют сильной связанностью кода: кнопка «знает» про поле ввода и попап, кроме того, она явно подозревает, что находится внутри формы.
Если в процессе рефакторинга какой-либо из компонентов исчезнет, код сломается. Если появится еще одно поле, код снова сломается. Если вы захотите переиспользовать такую кнопку, вам обязательно придется тянуть за собой все компоненты с такими же классами и гарантировать, что больше нигде на странице они не встречаются.
В результате, несмотря на простоту кода, он доставит вам намного больше серьезных проблем с поддержкой, ведь код сложно поддерживать и практически бесполезно реиспользовать, если только вы не делаете точную копию проекта.
Что можно улучшить?
Что изменилось?
Мы переписали форму так, чтобы за все, что происходит с ней, отвечала она сама. Теперь компоненты внутри ничего не знают о существовании друг друга. А мы можем смело взять кнопку и перенести ее на другой проект, ведь она стала независимой: теперь за ней не потянется знание о какой-то форме, поле ввода и попапе.
Кроме того, мы вынесли все селекторы за рамки контекста формы и теперь можем добавлять сколько угодно новых полей ввода, попапов и кнопок за пределами формы — ничего не сломается.
Есть ли что-то еще, что можно улучшить?
Да. Если мы добавим еще одно поле, придется рефакторить код. Кроме того, чтобы гарантировать перекрытие попапом любых других элементов на странице, нам необходимо положить его в самом конце DOM-дерева, перед закрывающим тегом
</body>
.Продолжаем улучшать
Вынесем попап из формы и добавим еще одно поле. Сами поля смиксуем с элементами формы.
Микс — это объединение нескольких блоков на одном DOM-узле.
Теперь наш код выглядит так:
Мы исправили предыдущие проблемы, но появилась новая: если на странице окажется несколько форм, как каждая из них найдет свой попап?
Решение, да не одно
В качестве одного из решений мы можем реализовать механизм, который позволит выражать один блок на нескольких DOM-нодах. Схематично он может выглядеть так:
Мы добавили форме data-атрибут с идентификатором и помимо элемента примиксовали к попапу саму форму с таким же идентификатором. Теперь мы можем в коде сказать, что нам нужен элемент
hint
именно этого блокаform
, а не какого-то другого:Следующее решение поможет нам сохранить независимость блоков, но избавиться от необходимости вносить изменения в DOM. Воспользуемся паттерном проектирования Посредник в очень упрощенном виде.
С ним наши компоненты будут знать о существовании посредника, но не будут ничего знать друг о друге. Вся коммуникация будет происходить на основе сообщений, которые компоненты будут публиковать и слушать на посреднике.
Чтобы максимально упростить пример, сделаем таким посредником
body
. Он всегда присутствует в коде и определенно знает о всех компонентах, которые находятся внутри, + может обеспечить обмен сообщениями.Теперь в случае ошибки валидации форма сообщит об этом посреднику —
page
. Все компоненты, которые должны реагировать на это событие, могут «подписаться» на него черезpage.on()
.В качестве еще одного решения можно воспользоваться паттерном MVC и обеспечить валидацию формы на уровне модели.
Подытожим: методология БЭМ — не только про CSS. Она затрагивает все технологии реализации блока, включая JS, и и помогает писать JavaScript-код, который сохранит независимость ваших блоков, упростит рефакторинг и продлит жизнь проекту.
«Зачем нужен
i-bem.js
, если можно писать JS для независимых блоков на привычномjQuery
?»Такой вариант возможен. И более того,
i-bem.js
написан с использованиемjQuery
.Зачем же нам понадобился отдельный блок?
Решая одни и те же задачи на JavaScript в терминах блоков, элементов и модификаторов, мы регулярно делали одни и те же действия. И чтобы автоматизировать процесс и избавиться от копипаста, а также предоставить удобные хелперы пользователям, мы написали i-bem.js.
Если у вас остались вопросы, смело задавайте их в комментариях. Мы обязательно ответим!