Некоторое время назад мне пришлось окунуться в Meteor. Не смотря на то, что javascript я не люблю, coffeescript еще больше, а node.js меня приводит и бешенство, Meteor очень быстро снискал моё признание и любовь. Я не работал с ним до версии 1.0+, насколько я знаю, там было достаточно много ужасов, однако сейчас, лично для меня, это очень удобный инструмент под небольшие интерактивные проекты. Потому очень хочется поделиться тем, что может облегчить использование данного инструмента другим людям.
Мой опыт работы с Meteor не такой большой, чтобы взять и начать что-то сложное раскладывать по полочкам. Однако я столкнулся с некоторыми вещами, побился о них головой и нашел какие-то решения. И в этой статье я хочу поделиться своими наблюдениями/советами по работе с jade в Meteor. К сожалению, с ним всё не так здорово, как с чистым jade-ом.
В своих проектах я использую пакет mquandalle:jade, и у него есть ряд неприятных особенностей. Кастомные темплейт хелперы работают не совсем так, как можно того ожидать. Стандартный синтаксис подразумевает, что мы просто берём и вызываем хелпер обычной строкой, если он используется сам по себе. Либо же обрамляем в #{}, если он используется в строке с чем-то. Вот пример (весь код на coffeescript):
Хелперы:
Template.Some.helpers(
ifHelper: ->
return true
plainTextHelper: ->
return 'Just some value'
)
Шаблон:
template(name='Some')
if ifHelper
.some-class #{someHelper}
Всё работает отлично, до того момента, как вы не попытаетесь передать значение в хелпер, для получения значения строки. А это требуется, и довольно часто. Если вы напишите #{someHelper someval}, то на выходе получите строку '#{someHelper someval}'. Возможно кто-то сходу решит, что раз не работает Jade вариант, то следует использовать синтаксис стандартного шаблонизатора. Но, например, я крайне не люблю использование не родных вещей в синтаксисе, а потому долго и упорно искал как заставить заработать нужную мне конструкцию на jade синтаксисе. Ответ — никак. По крайней мере на момент написания статьи я нигде не нашел рецепта. Пришлось поступиться принципом и написать не очень красиво {{someHelper someVal}}.
Однако если вам надо вернуть из хелпера html текст (и такое бывает), то нужно использовать тройные фигурные скобки {{{someHelper someVal}}}. Иначе html-код будет обезопасен, т. е. выведен в виде '&code;'
Так же в jade не работает отрицание not, да и вообще там нет нормальных js-условий, присущих этому шаблонизатору в вольной жизни. Посему довольно быстро у меня родился файлик, в который я складываю необходимый мне функционал для шаблонизатора, и таскаю его между проектами. Пока он выглядит вот так:
UI.registerHelper 'print', (obj)->
console.log('TEMPLATE DEBUG: type:', typeof(obj), 'val:', obj)
UI.registerHelper 'equal', (obj1, obj2)->
return obj1 == obj2
UI.registerHelper 'notequal', (obj1, obj2)->
return !(obj1 == obj2)
UI.registerHelper 'collectionIsEmpty', (col)->
error_flag = false
try col.fetch()
catch error
obj = col
error_flag = true
if !error_flag
obj = col.fetch()
return !obj.length
Если кто-то не знает, то UI.registerHelper функция регистрирует глобальный хелпер, который доступен во всех шаблонах. Что же касается самих функций, то первая используется для дебага переменных шаблона. Порой бывает полезно понять что же лежит в какой-то переменной и почему оно работает не так, как я ожидаю. Просто пишем
{{print someVar}}
или
{{print someHelper}}
и получаем вывод в консоль, где видим тип и значение.
Второй и третий хелперы — адаптация простых условий шаблона из Django, моего любимого веб-фреймворка (ну, куда же без рекламы?)
Последний хелпер служит для проверки возможности перебрать (итерировать) коллекцию. Чтобы не сомневаться, вернул ли я .fetch() от коллеции, или же её в чистом виде — хелпер слегка усложнён.
Хелпер для эмуляции not я не делал, т. к. до сих пор обходился просто правильным чередованием условий, вместо if not — if … else … но написать его, по аналогии, думаю никому не составит труда.
Так же, в работе с шаблонами я долгое время бился о синтаксис each. Когда мы вызываем each, мы меняем контекст шаблона, и this начинает указывать не на область видимости шаблона, а на область видимости итерируемого объекта.
.some-class #{this.title}
each MyBlogPostCollection
h1 #{this.title}
В первом случае .title будет либо helper шаблона с таким именем, либо свойство из data-объекта, если вы используете iron-router. Во втором же, .title будет взят из объекта коллекции MyBlogPostCollection. Кстати, this можно не писать для обращения к свойствам, достаточно просто #{title}
Но это всё довольно просто, а теперь собственно то, с чем я бился. Если в each нам нужно обратится к объекту из родительского контекста, то используется… (две точки), и там не надо писать this, его парсер не сожрёт. В итоге получаются вот такие вот, не всегда очевидные конструкции
{{someHelper ../../..}}
В рамках родительского контекста, если требуется, можно так же напрямую обращаться к свойствам. т. е.
{{someHelper ../../title}}
Еще я порядочно побился головой пытаясь динамически проставлять класс для элемента. Хотя, возможно, это только до меня так туго доходят некоторые вещи.
.contacts(class="{{#if isPdf}} border-radius-pdf {{/if}}")
Ну а если то же самое требуется с хелпером, то
.contacts(class="{{#if isPdf}} {{border-radius-pdf someVal}} {{/if}}")
И да, маленькое замечание, полностью конструкция if-else выглядит вот так: {{#if <...>}} {{else}} {{/if}} т. е. else не имеет #, что, на мой взгляд, не логично.
Чистый джейдовский if-else выглядит просто:
if
.some value
else
.another value
Но помните, что условие к if добавить нельзя.
Из тонкостей использования jade это, пожалуй, всё что я могу сейчас рассказать. Когда я садился писать статью, хотел поделиться несколькими хитростями не только в работе с шаблонами, но и с обработкой данных форм, загрузкой изображений, однако написав о jade, решил не закидывать всё в общую кучу. Так же на днях решил нетривиальную задачу — учился делать скриншот приложения с пользовательскими данными. Возможно что-то из этого будет интересно — оставляйте комментарии, если будет спрос — напишу.
Автор: Dominis