Помощник для создания других блоков
Блок i-bem — это блок-хелпер, позволяющий создавать другие блоки. Блок реализован в технологиях BEMHTML и JS. Обе эти реализации являются ядром библиотеки блоков в соответствующих технологиях.
js-реализация блока i-bem
Реализация блока i-bem в JS обеспечивает хелперы для представления блока в виде JS объекта с определёнными методами и свойствами. Это нужно, чтобы писать клиентский JS в терминах BEM. То есть JS оперирует более высоким уровнем абстрации, чем DOM представление.
Для того, чтобы js-представление блока использовало ядро i-bem, оно должно быть написано с соблюдением специальных правил.
Что описано на этой странице?- Какие бывают блоки
Блокам, реализованным на bem-js, могут соответствовать ноды в HTML. В этом случае говорится о том, что блоки имеют DOM представление. В HTML блоки на bem-js отличаются дополнительным CSS классом i-bem и специальным форматом записи параметров блока в onclick:
Блоки без DOM-представленияТехнология bem-js позволяет также создавать блоки, не имеющие DOM представления. Такие блоки тем не менее существуют в JS в виде объектов, манипулировать ими можно так же, как и блоками с DOM представлением.
О том, как создавать такие блоки, написано под заголовком Декларация блока.
Блоки, реализованные на bem-js, после инициализации представлены в js объектами, имеющими свои методы. Эти методы необходимо использовать, если нужно повлиять на внешний вид или поведение блока. Технология bem-js использует предметную область концепции BEM. Все сущности являются блоками или их элементами, управление их состояниями реализуется при помощи модификаторов. Представление блока в js не обязано иметь взаимное соответствие с одной DOM-нодой. Можно разместить несколько блоков на одной DOM-ноде (это называется mix), а также реализовать один блок на нескольких DOM-нодах. Далее блок, использующий технологию bem-js, будет называться блок, реализованный на bem-js, или bem-js-блок. Примером такого блока в библиотеке может служить b-link.
Декларативный принцип
Важной особенностью технологии bem-js является декларативный принцип. Подобно работе декларативных языков программирования, js-код содержит не последовательный алгоритм работы блока, а набор действий и условий, при которых эти действия необходимо выполнять.
Декларация блокаДекларативность проявляется в объявлении того, к каким блокам или их модификациям применим код компонента:
Реакция на изменение модификаторовСогласно концепции, состояния блока или его элементов определяются модификаторами. Поэтому, чтобы динамически изменять состояния блоков и элементов, в bem-js есть специальные методы для установки и снятия модификаторов.
В коде компонента можно записать, как блок или элемент должен отреагировать на изменение модификатора. Эта запись тоже декларативна. Например, блок b-dropdowna при установке модификатора disabled прячет показанный попап:
Подробно о декларации обработки модификаторов рассказано в пункте про создание собственного блока.
Доступ к другим блокам
Может возникнуть необходимость управлять другим блоком. Для любых манипуляций с блоком необходимо получить доступ к js-объекту этого блока и вызывать его методы.
Доступ к bem-js-блоку из другого bem-js-блока.В случае реализации собственного кастомного блока на технологии bem-js, блоку соответствует js-объект. Он наследует общие для всех блоков методы, позволяющие работать с DOM документа в терминах BEM. Среди этих методов есть методы поиска других блоков относительно текущего (findBlock*-методы). Они возвращают js-объект искомого блока, что позволяет затем напрямую вызывать его методы. Не используйте jQuery-селекторы для поиска блоков и элементов.
В этом примере вызывается метод val() у блока b-form-checkbox:
Доступ к bem-js-блоку не из bem-js-блокаВ случае работы не из bem-js-блока, методы findBlock* недоступны. js-объект блока можно получить, используя метод .bem() jQuery коллекции:
Этот способ не рекомендован. Лучшим вариантом работы с блоками, реализованными на i-bem, является создание собственного компонента на i-bem. Подробнее о создании собственного bem-js-компонента написано ниже.
Работа с модификаторами блока
Модификатор задаёт блоку определённое состояние. Каждому блоку можно присвоить один или несколько модификаторов (у блока также может не быть модификаторов вообще). У модификатора есть имя и значение.
Любой перевод блока в другое состояние должен производиться при помощи установки модификатора. Например, для того, чтобы сделать чекбокс выделенным в блоке b-form-checkbox, ему нужно установить модификатор checked в значение yes. На странице документации каждого блока есть список его элементов и модификаторов. Из этого списка можно однозначно определять, какие состояния блока доступны для использования.
Модификаторы нельзя устанавливать, напрямую меняя CSS класс на соответствующей DOM-ноде. Для корректной работы js все манипуляции с модификаторами должны производиться при помощи метода-хелпера setMod(). Также существуют методы hasMod(), getMod/getMods(), toggleMod() и delMod(). Сигнатуры этих методов доступны в референсе по BEM.
Изменение поведения существующих блоков
Используя bem-js, можно переопределять и доопределять методы блока и функций реакции на изменения модификаторов. Это делается аналогично кастомизации блоков на CSS или BEMHTML.
Переопределение поведенияНапример, на сервисе существует необходимость модифицировать все блоки b-dropdowna так, чтобы они не закрывались по второму клику на псевдо-ссылку. В этом случае на уровне переопределения сервиса нужно сделать файл blocks/b-dropdowna/b-dropdowna.js, кастомизирующий поведение блока из библиотеки:
Расширение поведенияВ предыдущем примере код кастомизации полностью переопределяет поведение блока. Технология bem-js позволяет также реализовывать «доопределение» блока. Для этого в методах кастомизирующего кода можно вызывать this._ _ base.apply(), передавая в качестве аргументов this и arguments. Вызов такого метода аналогичен использованию <xsl:apply-imports/>.
Например, можно доопределить реакцию на клик всех блоков b-link на проекте, так, чтобы после первого клика на псевдо-ссылку она приобретала красный цвет.
Содержание файла blocks/b-link/_pseudo/b-link_pseudo_yes.js
Содержание файла blocks/b-link/_status/b-link_status_clicked.css
Кастомизация с использованием модификаторовПредыдущие примеры кастомизации изменяют поведение всех определенных блоков на странице. Но очень часто возникает задача кастомизации конкретного блока без влияния на поведение всех таких блоков. Согласно концепции BEM, если блок чем-то отличается от других похожих, это выражается модификатором. Так что нужно реализовывать поведение для блока с таким модификатором.
Возвращаясь к примеру про псевдоссылку, приобретающую красный цвет после первого клика, сделаем модификацию reaction_odd. Псевдоссылка с таким модификатором приобретает красный цвет после каждого нечётного клика, а после каждого чётного возвращается к исходному цвету .
Содержание файла blocks/b-link/_reaction/b-link_reaction_odd.js:
Создание js-компонента для собственного блока или собственной модификации
Рекомендованным способом работы с bem-js-блоками является создание собственных bem-js-блоков (чаще всего — контейнеров), реагирующих на события других блоков страницы. Собственные bem-js-блоки могут вызывать методы других блоков (если нужно) и реализовывать свой функционал. js-код блока принято размещать в папке блока в файле с именем, соответствующим имени блока, и расширением .js. Если js-реализация относится не к блоку, а лишь к одной из его модификаций, можно разместить код в js-файле, соответствующем данному модификатору.
Декларация блокаСоздание js-компонента блока сводится к его декларации с помощью специальных хелперов. Существуют два хелпера для декларации блоков: один для блоков, которые имеют DOM-представление, второй — для блоков, не имеющих DOM представления (например i-request, i-update-session).
В первом случае блоки декларируются с помощью BEM.DOM.decl, во втором — с помощью BEM.decl.
Хелпер декларации блока принимает 3 параметра:
- Матчащий параметр Первым параметром может быть либо строка с именем блока, либо хеш. Хеш кроме имени блока содержит дополнительную информацию о том, к какому типу блоков применять компонент.
- Методы и свойства экземляра блока Методы и свойства, предметной областью которых является конкретный инстанс блока на странице. Это как функции обработки модификаторов, так и кастомные методы блока.
- Статические методы и свойства Методы и свойства, не относящиеся к конкретному инстансу блока. Подробнее
Вместо имени блока может быть указано более сложное описание, например, информация о предке:
Тут указано, что блок b-dataprovider наследуется от блока i-request и переопределяет его метод get.
В первом параметре (хеше) декларации может быть указано не только то, к какому блоку применить компонент, но и уточнён модификатор и/или его значение:
Все методы, описанные в такой декларации, будут вызываться для таких блоков b-popup, которые в данный момент имеют модификатор type, установленный в inplace.
Реакция на изменение модификаторовСогласно концепции BEM состояния блоков и его элементов определяются модификаторами. Блок может сам назначать себе или своему элементу модификатор, или получать это назначение из другого блока. Во время того, как блок или элемент получил определённый модификатор, может возникнуть необходимость отреагировать на установку (или снятие) модификатора.
Для этого в декларации в части описании методов и свойств экземпляра блока зарезервировано два специальных свойства: onSetMod и onElemSetMod, где описываются callback-функции, вызываемые при установке модификаторов для блока или его элементов.
Описание callback-функций для onSetMod представляет собой хеш вида:
Описание callback-функций для onElemSetMod аналогично, за исключением того, что на верхнем уровне указывается имя элемента:
- [elem] — элемент блока (если установка модификатора была для элемента)
- modName — имя модификатора
- modVal — устанавливаемое значение модификатора
- curModVal — текущее значение модификатора
Порядок вызовов callback-функций при установке модификатора modVal в значение modName:
- вызывается callback-функция на установку любого модификатора в любое значение (если она существует)
- вызывается callback-функция на установку модификатора modVal в любое значение (если она существует)
- вызывается callback-функция на установку модификатора modVal в значение modName (если она существует)
Если хоть один из вызовов этих функций вернет false, то установки модификатора не произойдет.
В данном примере при вызове toggleMod внутри onTriggerClick будет вызвана соответствущая ей callback-функция из onElemSetMod.
Callback функции, реагирующие на изменение модификатора, выполняются до установки модификатора. Если существует необходимость выполнить часть кода после установки модификатора, нужно воспользоваться методом .afterCurrentEvent().
Пример ниже демонстрирует, что квадратик становится больше только после установки модификатора:
Начало работы с блоком (модификатор js)Блок начинает свою работу с действий, описанных в callback-функции на установку его модификатора js в значение inited:
Этот модификатор присваивается блоку в момент инициализации. Поскольку код обработчика модификатора выполняется до установки модификатора, эта функция-обработчик и является первой выполняющейся функцией блока.
Модификаторы могут без ограничения присваиваться как блокам, имеющим DOM представление, так и блокам без него. Так что, у блоков без DOM представления первый исполняемый метод также задаётся как callback модификатора js_inited.
В коде блоков можно встретить callback функцию не на значение inited модификатора js, а на установку модификатора js в любое значение:
Это краткая декларация, возможная из-за того, что до инициализации блок не имеет модификатора js, а в момент инциализации приобретает значение inited. Другие значения модификатора сейчас не предусмотрены.
Методы блокаКроме реакции на модификаторы, в блоке могут быть определены его собственные методы. Определённые в блоке методы могут быть вызваны им самим или другими блоками.
Например, так выглядит метод .toggle() блока b-form-checkbox:
Переопределение и доопределение методов блокаЛюбой метод блока (в том числе и методы обработки модификаторов) может быть переопределён. Об этом написано выше в пункте Изменение поведения существующих блоков.
Статические методы блокаТретий параметр, передаваемый в функцию декларации блока, – это хеш статических методов блока.
Примером блока, использующего статические методы, может служить /blocks/b-flash/b-flash.wiki.
Для каждого блока может быть определен статический метод live, позвляющий реализовать инициализацию по требованию (liveinit ) .
Инициализация
Для того, чтобы у блока появился js-объект, описанный в декларации, происходит процесс инициализации блока. Инициализация блоков производится функцией BEM.DOM.init() на фрагменте DOM дерева. Если элемент i-bem_ _ dom задекларирован с модификатором init_auto (подключается файл i-bem_ _ dom_init_auto.js), то инициализация блоков происходит на всём документе по событию domReady. Также функцию BEM.DOM.init можно вызвать самостоятельно. Например, это делается для инициализации блоков после динамического изменения страницы, если появились новые блоки с js-представлением.
Инициализация блоков с DOM-представлениемДля инициализации блоков, представленных в DOM, на фрагменте дерева ищутся все блоки, помеченные классом i-bem, у них считываются параметры из атрибута onclick, и создаётся js-объект такого блока.
Формат параметров блока в onclickПараметры для блока записываются в виде возвращаемого атрибутом onclick хеша. Этот хеш должен содержать элементы с названиями, соответствующими названиям блоков, к которым они относятся. Значением каждого элемента должен быть вложенный хеш c параметрами. Такая запись позволяет задавать параметры для нескольких блоков в том случае, если они представлены в HTML одной и той же DOM-нодой.
Вот как выглядит DOM-нода произвольного блока, реализованного на bem-js:
В случае, если блоку необходим параметр, он указывается на том же уровне, что и элемент name. Формат параметра может быть любым: строка, число, массив, хеш, функция. Количество параметров также не ограничено.
Для нескольких блоков на одной DOM-ноде HTML представление будет аналогично следующему:
DOM-представление инициализированного блокаПосле инициализации DOM представление блока изменяется: у блока появляется дополнительный модификатор js_inited. Если DOM-ноде соответствуют несколько блоков, то появление модификатора у одного из них свидетельствует об инициализации только этого блока и не влияет на инициализацию другого (других).
DOM-представление блока после инициализации:
DOM представление двух блоков после инициализации:
DOM представление двух блоков, но инициализован только один из них:
Инициализация блоков без DOM-представленияВ том случае, если у блока нет DOM представления, в процессе инициализации просто возникает js-объект, соответствующий этому блоку. Дальнейшее зависит от кода блока.
Инициализация по требованию (liveInit)
Многим блокам (например, b-link, b-dropdown, b-smart-help) нет необходимости делать сразу же полную инициализацию. Инициализация может происходить только на ключевые события для этого блока, например, клик по элементу этого блока. Рассмотрим на примере блока b-link:
В статических свойствах блока предусмотрено специальное свойство live (Function|Boolean), отвечающее за инициализацию по требованию и за подписку на live события на DOM элементах внутри такого блока.
Если live определено как Function, то эта функция будет выполнена один раз — при попытке инициализации первого такого блока.
Существует несколько хелперов для live событий:
- liveInitOnEvent — хелпер для инициализации блока по событию на блоке или его внутреннем элементе
- liveBindTo — подписка на события на блоке или его внутреннем элементе
Оба этих хелпера инициализируют блок при возникновении первого такого события. Различие же заключается в том, что callback функция в liveInitOnEvent вызывается только один раз после инициализации блока, а в liveBindTo она будет вызываться при каждом событии. Контекстом такой callback функции является тот блок, в котором произошло событие.
В вышеприведенном примере блок b-link будет инициализирован при первом клике на себе и будет реагировать на каждый последующий клик.
Если же live определено как Boolean и установлено в true, то такой блок будет инициализирован только при попытке доступа к нему, например, из методов поиска findBlockInside/findBlockOutside.
Методы доступа к блокам и элементам
Работая с блоками, реализованными на bem-js, необходимо использовать встроенные методы для поиска блоков и их элементов. Эти методы доступны в каждом блоке и умеют возвращать другой блок или jQuery коллекцию (в случае поиска элементов).
Методы поиска блоковПоиск блоков осуществляется относительно текущего блока при помощи методов findBlock*.
Реализуем блок b-my-block, который находит первый из блоков b-form-checkbox внутри себя и вызывает у него метод toggle() для переключения чекбокса.
Поиск блока или блоков может быть выполнен одним из следующих методов:
- findBlockInside/findBlocksInside — поиск блока/блоков внутри DOM элементов текущего блока или его элементов
- findBlockOn/findBlocksOn — поиск блока/блоков на DOM элементах текущего блока или его элементов
- findBlockOutside/findBlocksOutside — поиск блока/блоков снаружи DOM элементов текущего блока или его элементов
Список методов поиска блоков и их сигнатуры можно посмотреть в референсе по BEM.DOM.
Примерами блоков, использующих методы поиска других блоков, могут быть: b-smart-help, b-screenshot и b-dropdowna.
Методы доступа к элементамДля поиска элементов внутри блока используется метод elem. Результат этого метода кэшируется.
Можно искать элементы внутри блока с учетом модификатора:
Некэширующий метод поиска элементов называется findElem().
Полный список методов для поиска элементов и их сигнатуры можно найти в референсе по BEM.DOM.
Работа с событиями
События на блокахБлоки предоставляют интерфейс для подписки/отписки/нотификации своих собственных (не DOM) событий:
- on(e, [data], fn, [ctx]) — подписка на событие e
- onFirst(e, [data], fn, [ctx]) — подписка только на первое событие e
- un([e], [fn], [ctx]) — отписка от конкретного события e или всех событий
- trigger(e, [data]) — нотификация о событии e
В bem-js есть события, реализованные по паттерну делегированных событий, они называются live события.
Следующий пример демонстрирует работу с live-событием click для блоков b-link, содержащихся в определённой DOM-ноде. В данном случае контейнер и блок совпадают:
Метод .liveCtxBind() реализует возможность реакции на bem-события блоков, вложенных в какой-либо DOM элемент. Это не DOM-события Использование live событий позволяет избежать лишнего поиска блоков в DOM дереве. Кроме того, при такой привязке к событию реакция на событие блока из контейнера будет происходить даже в том случае, если на момент привязки блока в контейнере не было, а он появился позже в результате динамического изменения документа.
Кроме возможности привязки к live событию блока, здесь также продемонстрированы поиск блока относительно текущего и live-инициалиация.