Доброго времени суток комрады. Столкнулся с необходимостью использовать конструкции сабжа, из доки, ничего не понял. Сделал вывод что для понимания нужно знать как это работает в XSL. Но с XSL'ом не работал, кто нибудь может объяснить как эти конструкции работают более подробно и в чем разница между ними? Было бы очень круто если на примерах.
А суть моей проблемы в том, что один шаблон должен помимо своей основной сущности, так же выводить результаты еще 1 шаблона при условии что у его родителя есть элемент этого шаблона.
@campykid Предложу экспресс-туториал, который не раскрывает всех возможностей, но, надеюсь, дает возможность решить 90% возникающих задач.
Для начала важно понять принцип работы BEMHTML.
Работает он примерно так. 1) Скомпилированный BEMHTML — это функция, на вход которой подается дерево (в данном случае BEMJSON).
2) Это дерево рекурсивно обходится и для каждого узла вызываются все описанные в шаблонах предикаты (они же условия или матчеры).
3) Если для какого-то узла условие возвращает
true
, то выполняется тело шаблона (в нем обычно описывается, что нужно сделать с узлом, для которого условие оказалось истинным).В общем случае условие записывается в виде
Можно писать проверку на произвольные условия типа
match('a' == 'a')
,match(3 < 5)
илиmatch(true)
, но чаще всего нас интересуют условия, проверяющие текущий узел BEMJSON-дерева. Он доступен в полеthis.ctx
(текущий контекст).Например, чтобы написать какой-то шаблон для блока
b1
, нужно проверить, чтоthis.ctx.block === 'b1'
. Это можно сделать с помощью универсальногоА можно воспользоваться шоткатом:
Аналогичные шоткаты есть и для остальных БЭМ-сущностей — элементов (
elem()
) и модификаторов (mod()
).Вот так можно записать условие, которое отработает, когда во входящем BEMJSON окажется
{ block: 'b1', elem: 'e1', elemMods: { m1: 'v1' } }
:4) Помимо условий про БЭМ-сущности, есть еще моды про конкретную часть результирующего HTML:
tag()
— тегattrs()
— атрибутыmix()
— миксы с другими БЭМ—сущностямиjs()
js-параметрыcls
— дополнительные классыcontent()
— содежимое тега.Все эти моды являются шоткатами для проверки поля
_mode
на равенство соответствующей строке. Например,эквивалентно
tag()
.Когда условие на одну из этих мод для данного узла BEMJSON-дерева истинно, в теле шаблона можно повлиять на соответствующую часть HTML, получаемого на выходе:
5) Вызов этих мод происходит благодаря базовым шаблонам, описанным в блоке
i-bem.bemhtml
.Грубо говоря, сначала для каждого узла BEMJSON-дерева, вызывается шаблон по моде
''
(пустая строка). В этом шаблоне вызывается модаdefault
. В ней в нужном порядке зовутся все моды из пункта 4.6) Помимо использования стандартных мод, чтобы избежать копипаста, может быть удобно описать какую-то произвольную моду. Например,
Чтобы ее вызвать пригодится
apply()
. Выполнениеapply('my-mode') вернет
42` — результат вычисления тела данной моды.7) Условия проверяются снизу вверх. Например, если написать
То в результате получим
<div class="b1">второй</div>
.Т.е. принцип ровно такой же, как в CSS:
8) Если возникает необходимость повлиять на процесс выполнения стандартных мод из пункта 4, можно написать собственный шаблон для нужной сущности по моде
def()
и т.к. он окажется ниже базового шаблона по этой моде, отработает новая реализация.9) Может возникнуть ситуация, когда из нижнего шаблона нужно получить результат выполнения предыдущего. Для этого можно вызвать
applyNext()
:Частая задача, когда в нем возникает необходимость — это добавить обертку:
или дополнительную сущность рядом:
Еще с его помощью можно повлиять на поля объекта
this
и продолжить выполнение остальных шаблонов для данного узла BEMJSON с уже измененнымthis
:При этом очень часто приходится менять какие-то поля
this.ctx
. Это можно сделать с помощьюapplyNext({ 'this.ctx': 'новый контекст' })
, а можно воспользоваться шоткатом для такого случая:applyCtx('новый контекст')
.Это удобно, например, когда нужно полностью перезаписть какой-то узел входного BEMJSON на новый и выполнить для него шаблоны:
Краткое резюме
applyCtx({})
— этоapplyNext({ 'this.ctx': {} })
.applyNext()
— это вызов предыдущих шаблонов по текущей моде (наследование). Может как вернуть результат для дальнейшего использования, так и просто выполнить остальные шаблоны с какими-то изменениямиthis
.apply()
в чистом виде чаще всего нужен для получения результата выполнения определенной моды. Все остальные случаи нужны крайне редко и скорее всего указывают на ошибку в архитектуре.А что будет, если в двух шаблонах матчащихся на один блок будут конструкции applyNext() ? Что будет, если шаблон один, в нем используется applyNext , но больше никто на этот блок не матчится?
Всем привет! @tadatuta, скажу от себя спасибо за разъяснение, стало немного понятнее=) Видимо все-таки от недопонимания всей технологии задам здесь еще вопрос, который имеет отношение к apply. Возможно ли контролировать порядок применения apply для модификаторов. Поясню на примере. Допустим у нас есть блок link (ссылка). Есть к нему модификатор "loading" со значением "yes", который добавляет справа от ссылка еще маленький span с иконкой загрузки. И есть модификатор external опять же со значением "yes". Этот модификатор, добавляет справа иконку extrenal ссылки (например http://fortawesome.github.io/Font-Awesome/3.2.1/icon/external-link/). Хотелось сделать их примерно одинаково.
Но в таком
случае получаем ошибку "Maximum call stack size exceeded". Видимо по-очереди дергаются applyNext в коде модификаторов и уходит в бесконечную рекурсию. Как разрулить? Такая проблема возникала у меня пару месяцев назад и пример был чуть менее надуманный, но сейчас я его уже точно не могу вспомнить, поэтому привожу пример с ссылками).
Хотелось бы узнать как лучше решать такую задачу. И еще важно, что мне нужно контролировать в каком порядке идет html. Т.е. если оно заработает мне важно, чтобы иконка загрузки была всегда после иконки extrenal, если она есть.
UPDATE Сообщение @escaton в момент написания своего не видел. Похоже, что спрашивается примерно тоже.
@escaton
Отвечу простым примером. Пусть есть такой BEMJSON:
{ block: 'b1', content: 'initial' }
.Напишем такие шаблоны:
В результате сгенерируется вот такой HTML:
<div class="b1">new content after second tmpl after third tmpl</div>
и вот такой лог окажется в консоли:Думаю, эта иллюстрация отвечает на вопрос?
Правильнее сказать не «больше», а «раньше».
applyNext()
— это про вызов предыдущих шаблонов. И тут можно быть спокойным — вызовется базовый шаблон изi-bem.bemhtml
.@2rist В примере что-то напутано: матчеры написаны на
link
, а судя по всему предполагалсяmrkt-link
, но даже так не очень понятно, что хотелось сделать, чтобы шаблоны зациклились.Но в целом, если ситуация является не ошибкой проектирования архитектуры и при этом все равно возникает зацикливание, есть универсальное решение: можно вручную выставлять защиту с помощью флага в
this
, который проверять в предикате:@tadatuta результат выполнения базовых шаблонов — html?
@escaton результат выполнения BEMHTML — строка.
@2rist помогло с флагом? ;) Какие у вас версии библиотек используются? @tadatuta А разве эту проблему с зацикливанием не решает какая то версия
bem-core
?@tadatuta, @voischev Натупил с примером. По ходу написания коммента, кое-что переделывал и накосячил с копипастой. Попробую еще раз. Есть блок mrkt-link (на префиксы mrkt не обращайте внимание, специфика проекта)
И есть 2 модификатора external и loading, работающих одинаково - добавляющих справа ссылки "иконку внешней ссылки" или значок загрузки. Код модификаторов
Если использовать модификаторы по одному, то все работает и получается html вида
Если же включать оба модфикатора одновременно
то получаем Maximum call stack size exceeded
Теперь надеюсь более понятно получилось.
Попробовал решить флажками.
Проблема с Maximum call stack size exceeded исчезла. Теперь отображается нормально
Не совсем понял почему был косяк, я думал у applyNext есть защита от зацикливания. Т.е. получается, если я в двух модификаторах одного и того же блока использую applyNext, то мне нужно в обоих добавлять guard-а, чтобы я мог применить модификаторы одновременно? Или я что-то принципиально не так делаю?
И еще вопрос, можно ли в моем случае как-то управлять порядком применяемых модификаторов. Т.е. Не важно в каком порядке я указываю модфикаторы loading: 'yes', external: 'yes'
в результате
Извиняюсь за "много букав", и возможно не совсем понятный код. Проблема не в том,чтобы решить конкретно эту задачу, а в том, чтобы разобраться как это работает.
Спасибо за овтеты!
@2rist Ты все очень понятно написал :) можно же написать в одном шаблоне) что-то типа:
Тут как раз удобны булевы значения модификаторов ;)
Если я что-то не так написал, думаю меня поправят.
@voischev Можно и в одном шаблоне, но это не удобно=) Пример сильно упрощен для наглядности, кода может быть сильно больше, чем просто добавление одного элемента. К тому же у нас к блоку ссылки уже 6 возможных модификаторов с различными значениями, а блоку кнопки 7 модификаторов (юзать так юзать) и не хотелось бы их в одном файле держать. Так что как вариант твое решение подходит, но хотелось бы узнать есть ли еще варианты=).
Получается вопрос еще в том - должны ли модификаторы знать друг о друге? Нормально ли в коде одного модификатора проверять есть ли другой? Или же они должны быть полностью независимыми. Вопрос идеологический=)
@2rist ох) какие все идеологичные стали) Ты же знаешь что мы делаем 100500 сайтов на БЭМ? Примерно представляешь их сложность. Интерес в том, что почему то, у нас не возникает таких мыслей как то сильно менять представление в зависимости от модификатора какой то блок. Скорее всего у нас так получается потому что мы более сильно дробим на блоки какую то функциональность и уже в месте объявления блока рулим расположением контента в массиве, а не хотим модификатором все поменять) (хотя часто что-то меняется модификатором и в наши проектах, но чаще модификаторы про разное) А если что-то по расположению меняется модификатором — то скорее всего достаточно CSS для этого. )))
Я бы в этом примере иконку сделал блоком, и миксовал к нему элемент ссылки что бы какие то css свойства касательно элемента блока навесить.
А расположением блоков рулит бы в месте где объявляется блок. (Повторил мысль))) )
Надеюсь очевидные вещи пересказал)
PS: Кстати да. У тебя модификаторы про одно. По хорошему нужно писать модификатор так
Но такая запись диктует что у тебя может быть только одна иконка.
@voischev Разве это сильное изменение представления? По-моему нет=) Опять же повторюсь, что этот код просто для "примера". Естественно можно решить и другими способами без applyNext, с помощью css или еще как-то. Мне интересно разобраться в том как это работает. На мой взгляд, ситуация в которой у одного блока в разных модификаторах есть applyNext не такая уж надуманная и может случиться. Поэтому хотелось бы знать, что в таком случае надо делать, как предлагает методология БЭМ справляться с такими задачами. Если методология такова, что "старайтесь разбить все как можно меньше, чтобы не надо было применять applyNext", то ок, нет проблем, я хотя бы буду знать,что такая best practice. P.S.: Модификаторы не совсем про одно. По-факту у нас есть модификатор view с возможными значениями ["extrenal", "dashed", "without-underline"], вот этот модификатор у нас про одно и то же и отвечает за вид ссылки и не может быть одновременно несколько значений (потому как есть ui kit и в нем прописано как должны выглядеть все внешние ссылки, и что они не могут быть подчеркнутыми). И есть отдельно модификатор loading yes, он может добавляться к любой ссылке в зависимости от бизнес логики. Я упростил пример немного, чтобы не вдаваться в детали.
@2rist
applyNext()
добавляет защиту от зацикливания для самого себя, но в описанном случае происходит примерно следующее:applyNext()
, который должен выполнить матчи на все остальные шаблоны, кроме самого себя.applyNext()
, который требует вызвать все шаблоны, кроме текущего. Т.к. в этот момент предыдущий шаблон еще не выполнился, то он вызывается снова. И так до бесконечности.По поводу порядка выполнения, предположу, что нужно в
_external
добавитьmustDeps
от_loading
.Захотелось немного поднять ветку форума :)
А почему название функции для вызова предыдущей версии шаблона
applyNext()
? Почему неapplyBase()
илиapplyPrevious()
(applyPrev()
)?@belozyorcev Шаблоны применяются снизу вверху от более частного к более общему (как в CSS). И
applyNext()
означает «выполни следующий подходящий для данного узла шаблон, как если бы текущего шаблона не было».@tadutata теперь ясней стало :) Спасибо
@JiLiZART у меня связь пропала, но здесь в комментах есть ответ про applyCtx()
Всем привет!
Потихоньку разбираюсь с apply*. Но меня не покидает ощущение, что копаю я не в том направлении и это совсем не то, что мне нужно. Прошу направить в нужное русло.
Какие дела: Есть project-stub со стремительно растущими bemjson файлами и мешаниной из данных и описания блоков. Рост создают повторяющиеся блоки(трёхуровневые менюхи, несколько десятков товаров на странице или теги). Вместе с ростом bemjson растёт и моё желание сделать их чище и меньше. Cначала попробовал генерировать bemjson в bemjson. Забивал массив данных и делал по ним map. Сильно не помогло тк и данные остались в bemjson и при изменении структуры страницы описание блоков необходимо править в нескольких bemjson файлах. Сейчас пытался вынести генерацию bemjson вместе с логикой в bemhtml. Попробовал... Дико усложнились bemhtml...
Можно ли сложить данные в одном месте, а блоки описывать в другом месте (единственном)?
@Roman-Fov Ответ на этот вопрос называется BEMTREE. Начать знакомство можно с https://github.com/bem/bem-forum-content-ru/issues/716
А в качестве примера реализации посмотреть на https://github.com/tadatuta/bem-bemtree-static-project-stub/tree/one-bundle-gulp-watcher (важно: это не основная ветка репозитория, не перепутать с master при чекауте).
Если возникнут вопросы — можно завести отдельный тред про BEMTREE на форуме.