Доброго времени суток комрады. Столкнулся с необходимостью использовать конструкции сабжа, из доки, ничего не понял. Сделал вывод что для понимания нужно знать как это работает в 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 на форуме.