Введение в Handlebars
() translation by (you can also view the original English article)
Если данные вашего сайта регулярно меняются, вы можете взглянуть на Handlebars. Handlebars - это шаблонный процессор, который динамически генерирует вашу HTML-страницу, что экономит ваше время на ручном обновлении. В этом уроке я расскажу вам о Handlebars и покажу, как создать базовый шаблон для вашего сайта.
Шаблон сайта
Существуют две основные причины, по которым вы хотите создать шаблон для своего сайта. Прежде всего, создание шаблона побуждает вас отделять логический код от фактического представления, помогая вам придерживаться шаблона View/Controller. Во-вторых, шаблоны сохраняют ваш код чистым и поддерживаемым, что, в свою очередь, делает процесс обновления вашего сайта быстрым. Вы не создаете сайт с Handlebars. Вместо этого вы создаете рекомендации и структуры, которые определяют, как должен выглядеть сайт, не фокусируясь на данных страницы. Давайте рассмотрим некоторые из основ.
Основы
Handlebars генерирует ваш HTML, используя структуру JSON и запуская ее через шаблон. Эти шаблоны написаны в основном в обычном HTML и набиты заполнителями, которые позволяют вам при необходимости вводить данные. Например, следующий шаблон приветствует пользователя при входе в систему:
1 |
<h1>Welcome back, {{name}}</h1> |
Атрибут {{name}}
- это имя пользователя, которое будет выведено на страницу. Этот заполнитель соответствует свойству в структуре данных JSON. Это самый простой пример, но вы скоро увидите, что все остальное в основном сводится к этой простой концепции. Перейдем к обработке массивов.
Массивы
Handlebars поставляется с некоторыми встроенными помощниками, которые помогут вам работать с более сложными данными. Один из этих помощников - each
. Этот помощник выполняет итерацию через массив и позволяет создавать динамический HTML для каждого элемента массива. Например, следующий шаблон отображает данные массива, которые содержат список локальных концертов, играющих в моей области:
1 |
<table>
|
2 |
<tr>
|
3 |
<th>Local Concerts</th> |
4 |
</tr>
|
5 |
{{#each Concerts}} |
6 |
<tr>
|
7 |
<td>{{this}}</td> |
8 |
</tr>
|
9 |
{{/each}} |
10 |
</table>
|
Как вы можете видеть, этот код намного чище, чем обычный код, например, используя цикл PHP или JavaScript для добавления HTML к переменной. Handlebars не является навязчивым, и именно это делает Handlebars таким доступным. Вы также можете заметить, что мы используем имя атрибута, this
, чтобы извлечь текущий элемент массива в каждом цикле each
.
Этот пример хорош для массива простых значений, но как вы обрабатываете более сложные данные? Ну, вы по существу делаете то же самое. Например, мы собираемся написать шаблон для следующих данных:
1 |
[ |
2 |
{ |
3 |
Name : "Band", |
4 |
Date : "Aug 14th, 2012", |
5 |
Albums : [ |
6 |
{ |
7 |
Name : "Generic Name" |
8 |
}, |
9 |
{ |
10 |
Name : "Something Else!!" |
11 |
} |
12 |
] |
13 |
}, |
14 |
{ |
15 |
Name : "Other Guys", |
16 |
Date : "Aug 22nd, 2012" |
17 |
Albums : [ |
18 |
{ |
19 |
Name : "Album One" |
20 |
} |
21 |
] |
22 |
} |
23 |
] |
Мы можем легко отобразить эту информацию, используя следующий шаблон:
1 |
<table>
|
2 |
<tr>
|
3 |
<th>Band Name</th> |
4 |
<th>Date</th> |
5 |
<th>Album Name</th> |
6 |
</tr>
|
7 |
{{#each Bands}} |
8 |
<tr>
|
9 |
<td>{{Name}}</td> |
10 |
<td>{{Date}}</td> |
11 |
<td>{{Albums.0.Name}}</td> |
12 |
</tr>
|
13 |
{{/each}} |
14 |
</table>
|
Вы можете сохранить свой шаблон в элементе
<script />
и загрузить его с помощью JavaScript.
В Handlebars вы можете даже получить доступ к вложенным свойствам, например, в примере выше (Albums.0.Name
), и, конечно же, вы могли бы использовать еще один цикл each
для итерации по альбомам группы. Стоит отметить, что помимо точечной нотации для доступа к вложенным свойствам вы также можете использовать «../» для доступа к свойствам родителя.
Что делать, если нет каких-либо групп? Вы, конечно же, не хотите получить пустую таблицу, и Handlebars с благодарностью предоставляет if
, else
и unless
помощников. Операторы if
и else
работают как и в большинстве языков программирования: если объект, который вы передаете, является пустым или false
, то выполняется инструкция else
. В противном случае выполняется оператор if
. Заявление unless
является довольно интересным; это, по сути, инвертированный оператор if
. Если выражение true
, блок unless
НЕ будет запущен. Поэтому давайте включим этих помощников в наш код:
1 |
{{#if Bands}} |
2 |
<table>
|
3 |
<tr>
|
4 |
<th>Band Name</th> |
5 |
<th>Date</th> |
6 |
<th>Album Name</th> |
7 |
</tr>
|
8 |
{{#each Bands}} |
9 |
<tr>
|
10 |
<td>{{Name}}</td> |
11 |
<td>{{Date}}</td> |
12 |
<td>{{Albums.0.Name}}</td> |
13 |
</tr>
|
14 |
{{/each}} |
15 |
</table>
|
16 |
{{else}} |
17 |
<h3>There are no concerts coming up.</h3> |
18 |
{{/if}} |
Пользовательские помощники
Handlebars дает вам возможность создать собственный пользовательский помощник. Просто зарегистрируйте свою функцию в Handlebars, и любой шаблон, который вы затем компилируете, может получить доступ к вашему помощнику. Есть два типа помощников, которые вы можете сделать:
- Функциональные помощники - это в основном обычные функции, которые после регистрации могут быть вызваны в любом месте вашего шаблона. Handlebars записывает возвращаемое значение функции в шаблон.
- Блок-помощники похожи по своему характеру на помощников
if
,each
и т.д. Они позволяют изменить контекст того, что внутри.
Позвольте мне показать вам быстрый пример каждого из них. Во-первых, я зарегистрирую функцию helper со следующим кодом:
1 |
Handlebars.registerHelper("Max", function(A, B){ |
2 |
return (A > B) ? A : B; |
3 |
}); |
Первый аргумент, переданный в registerHelper()
, - это имя моего помощника; Я буду использовать это имя в шаблоне. Второй аргумент - это функция, связанная с этим помощником.
Использование этого помощника в шаблоне чрезвычайно просто:
1 |
{{Max 12 45}} |
Этот шаблон использует помощник Max
и передает значения 12 и 45 соответствующей функции. Функции помощники Handlebars поддерживают несколько параметров. Вы можете напрямую вставлять числа в сам шаблон или использовать атрибуты из структуры JSON.
Теперь давайте посмотрим на пользовательский вспомогательный блок. Блочные помощники позволяют установить контекст перед запуском кода, содержащегося в блоке. Например, рассмотрим следующий объект:
1 |
{ |
2 |
Name: "Parent", |
3 |
Sub: { |
4 |
Name: "Child" |
5 |
} |
6 |
} |
Чтобы отобразить оба имени, вы можете написать вспомогательный блок, который запускает шаблон один раз с контекстом родителя, и один раз с контекстом дочернего элемента. Вот помощник:
1 |
Handlebars.registerHelper("BothNames", function(context, options){ |
2 |
return options.fn(context) + options.fn(context.Sub); |
3 |
}); |
И шаблон выглядит следующим образом:
1 |
{{#BothNames this}} |
2 |
<h2>{{Name}}</h2> |
3 |
{{/BothName}} |
Хэш-тег перед именем помощника говорит Handlebars, что это вспомогательный элемент блока, и вы закрываете блок, который не похож на HTML-тег. Функция options.fn
выполняет раздел шаблона внутри блока с любым контекстом, который вы ему даете.
Теперь, когда у нас есть основы, давайте начнем создавать полноценную демоверсию.
Создание шаблона сайта
Вы не создаете сайт с Handlebars.
Шаблон, который мы будем строить, предназначен для сайта рецептов. Это даст вам хорошее представление о Handlebars, поскольку оно охватывает получение данных из API и передачу их через шаблон.
Настройка проекта Handlebars
Мы должны сначала загрузить наш шаблонный скрипт, но для этого нам нужно создать новый HTML-файл и подключить нашу библиотеку Handlebars:
1 |
<html>
|
2 |
<head>
|
3 |
<title>Handlebars Demo</title> |
4 |
<script type="text/javascript" src="Handlebars.js"></script> |
5 |
</head>
|
6 |
<body>
|
7 |
<script id="Handlebars-Template" type="text/x-handlebars-template"> |
8 |
</script>
|
9 |
</body>
|
10 |
</html>
|
Для удобства вы можете сохранить свой шаблон в элементе <script />
и загрузить его с помощью JavaScript. Это намного чище, чем хранить его непосредственно в переменной JavaScript.
Теперь давайте обсудим, как это приложение будет работать. Во-первых, приложение подключается к API (я использую Yummly), чтобы извлекать информацию о некоторых рецептах. Затем мы передаем эту информацию в Handlebars и пропускаем ее через шаблон. Наконец, мы заменяем раздел тела новым HTML. Это довольно простой процесс; поэтому давайте начнем с добавления второго блока script
непосредственно перед закрывающим тегом body
и создадим экземпляр переменной Ajax
:
1 |
<script>
|
2 |
|
3 |
var Ajax = (window.XMLHttpRequest) ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"); |
4 |
|
5 |
Ajax.onreadystatechange = function(){ |
6 |
if (Ajax.readyState == 4 && Ajax.status == 200) |
7 |
{
|
8 |
//Parse the JSON data
|
9 |
var RecipeData = JSON.parse(Ajax.responseText); |
10 |
|
11 |
//Get the Template from above
|
12 |
var Source = document.getElementById("Handlebars-Template").textContent; |
13 |
|
14 |
//Compile the actual Template file
|
15 |
var Template = Handlebars.compile(Source); |
16 |
|
17 |
//Generate some HTML code from the compiled Template
|
18 |
var HTML = Template({ Recipes : RecipeData }); |
19 |
|
20 |
//Replace the body section with the new code.
|
21 |
document.body.innerHTML = HTML; |
22 |
}
|
23 |
}
|
24 |
|
25 |
Ajax.open("GET","Recipe.php", true); |
26 |
Ajax.send(); |
27 |
|
28 |
</script>
|
Если данные вашего сайта регулярно меняются, вы можете взглянуть на Handlebars.
Это полный код для компиляции и генерации HTML-кода из шаблона. Вы можете технически передавать данные JSON из API непосредственно в Handlebars, но вы сталкиваетесь с проблемами cross origin. Вместо того, чтобы выполнять какой-то взлом или использовать PHP для «эхо» данных в переменную JavaScript, я решил поместить все это в отдельный файл: Recipe.php
. Поэтому, прежде чем мы начнем создавать шаблон, давайте взглянем на этот файл PHP.
Получение данных
API Yummly довольно прост. Нет сложной системы аутентификации; вам просто нужно зарегистрироваться, получить учетные данные и вставить их в URL-адрес. Вы можете напрямую отразить данные, если хотите, но я хочу получить более подробную информацию по каждому рецепту. Поэтому я обработаю данные из первого вызова API и сделаю второй запрос для каждого рецепта. Вот полный скрипт PHP:
1 |
<?php
|
2 |
//Empty Array to hold all the recipes
|
3 |
$Json = []; |
4 |
|
5 |
$UserID = //Your ID Here; |
6 |
|
7 |
$UserKey = //Your Yummly key; |
8 |
|
9 |
//This searches Yummly for cake recipes
|
10 |
$Recipes = file_get_contents("http://api.yummly.com/v1/api/recipes?_app_id=" . $UserID . "&_app_key=" . $UserKey . "&maxResult=2&requirePictures=true&q=Cake"); |
11 |
|
12 |
//Decode the JSON into a php object
|
13 |
$Recipes = json_decode($Recipes)->matches; |
14 |
|
15 |
|
16 |
//Cycle Through The Recipes and Get full recipe for each
|
17 |
foreach($Recipes as $Recipe) |
18 |
{
|
19 |
$ID = $Recipe->id; |
20 |
$R = json_decode(file_get_contents("http://api.yummly.com/v1/api/recipe/" . $ID . "?_app_id=" . $UserID . "&_app_key=" . $UserKey . "&images=large")); |
21 |
|
22 |
|
23 |
//This is the data we are going to pass to our Template
|
24 |
array_push($Json, array( |
25 |
Name => $R->name, |
26 |
Ingredients => $R->ingredientLines, |
27 |
Image => $R->images[0]->hostedLargeUrl, |
28 |
Yield => $R->yield, |
29 |
Flavors => $R->flavors, |
30 |
Source => array( |
31 |
Name => $R->source->sourceDisplayName, |
32 |
Url => $R->source->sourceRecipeUrl |
33 |
)
|
34 |
));
|
35 |
}
|
36 |
|
37 |
//Print out the final JSON object
|
38 |
echo json_encode($Json); |
39 |
?>
|
Создав свой сайт с помощью шаблона Handlebars, вы можете создать код всего сайта в нескольких строках. Вот весь шаблон:
1 |
<script id="Handlebars-Template" type="text/x-handlebars-template"> |
2 |
<div id="Content"> |
3 |
<h1>ΞRecipeCards |
4 |
<span id='BOS'>Recipe search powered by |
5 |
<a id='Logo' href='http://www.yummly.com/recipes'> |
6 |
<img src='http://static.yummly.com/api-logo.png'/> |
7 |
</a> |
8 |
</span> |
9 |
</h1> |
10 |
{{#each Recipes}} |
11 |
<div class='Box'> |
12 |
<img class='Thumb' src="{{{Image}}}" alt="{{Name}}"> |
13 |
<h3>{{Name}} <a id='Logo' href="{{Source.Url}}"> - {{Source.Name}}</a></h3> |
14 |
<h5>{{getFlavor Flavors}}</h5> |
15 |
<h5>{{Yield}}</h5> |
16 |
<p>Ingredients:</p> |
17 |
<ul> |
18 |
{{#each Ingredients}} |
19 |
<li>{{this}}</li> |
20 |
{{/each}} |
21 |
</ul> |
22 |
</div> |
23 |
{{/each}} |
24 |
</div> |
25 |
</script>
|
Давайте рассмотрим этот код. Первые семь строк - это только логотип в верхней части страницы. Затем для каждого рецепта мы создаем «карточку» рецепта с изображением, именем и ингредиентами.
Yummly API возвращает список данных о вкусе (то есть, как сладкий, кислый, пряный и т.д.) Для каждого элемента. Я написал функцию helper, называемую getFlavor
, которая принимает эту информацию и возвращает самый доминирующий вкус в блюде. Чтобы этот шаблон работал, нам нужно загрузить в помощник getFlavor
в Handlebars перед разбором шаблона. Поэтому в начале второго раздела скрипта добавьте следующий код перед кодом Ajax:
1 |
Handlebars.registerHelper("getFlavor", function(FlavorsArr){ |
2 |
var H = 0; |
3 |
var Name = ''; |
4 |
for(var F in FlavorsArr) |
5 |
{ |
6 |
if(FlavorsArr[F] > H) |
7 |
{ |
8 |
H = FlavorsArr[F]; |
9 |
Name = F; |
10 |
} |
11 |
} |
12 |
return "This Dish has a " + Name + " Flavor"; |
13 |
}); |
Теперь, когда Handlebars видит getFlavor
, он вызывает связанную функцию и извлекает информацию о вкусе.
На этом этапе вы можете свободно играть и разрабатывать шаблон, как хотите, но скорее всего увидите, что этот процесс идет медленно. Это связано прежде всего с тремя вызовами API, прежде чем Handlebars загрузит страницу. Очевидно, что это не идеально, но может помочь прекомпиляция вашего шаблона.



Прекомпиляция
У вас есть два разных варианта, когда дело касается Handlebars. Во-первых, это просто прекомпиляция фактического шаблона. Это сокращает время загрузки, и вам не придется включать компилятор Handlebars со своей страницей.
Это также приводит к уменьшению размера файла, но это не помогает в нашем сценарии.
Наша проблема - связь между браузером и API. Если вы хотите предварительно скомпилировать свой шаблон, вы можете загрузить пакет Node.js через npm
с помощью следующей команды:
1 |
npm install handlebars -g |
Возможно, вам понадобится сделать это как root (т. е. добавить «sudo» перед командой). После установки вы можете создать файл для своего шаблона и скомпилировать его так:
1 |
handlebars demo.handlebars -f demo.js |
Вы должны дать вашему файлу шаблона расширение .handlebars
. Это необязательно, но если вы назовете его чем-то вроде demo.html
, то имя шаблона будет «demo.html». После того, как вы назовете свой шаблон, просто включите выходной файл вместе с версией Handlebars во время выполнения (вы можете использовать обычную версию, но она больше) и введите следующее:
1 |
var template = Handlebars.templates['demo']; |
2 |
var html = template({ Your Json Data Here }); |
Оператор
unless
является, по существу, инвертированным выражениемif
.
Но, как я упоминал ранее, это не очень помогает нам в этом сценарии. Что же тогда мы можем сделать? Ну, мы можем предварительно скомпилировать и вывести весь файл. Другими словами мы можем запустить шаблон с данными и сохранить итоговый вывод HTML. Это значительно ускоряет время загрузки вашего приложения. К сожалению, клиентский JavaScript не имеет возможностей ввода-вывода файлов. Таким образом, самый простой способ добиться этого - просто вывести HTML в текстовое поле и сохранить его вручную. Имейте в виду рекомендации API по кэшированию. Большинство API-интерфейсов имеют максимальное время, в течение которого данные могут быть кэшированы; обязательно сохраните эту информацию перед сохранением статических страниц.
Вывод
Это было краткое введение в Handlebars. Двигаясь вперед, вы можете посмотреть « Partials» - небольшие шаблоны, которые можно использовать как функции. И как всегда, не стесняйтесь оставлять комментарий или вопрос в разделе комментариев ниже.