Advertisement
  1. Code
  2. Web Development

Uma introdução ao Handlebars

Scroll to top
Read Time: 12 min

Portuguese (Português) translation by Claudia Conceicao (you can also view the original English article)

Se a informação do teu site muda regularmente, então deves querer dar uma olhadela ao Handlebars. Handlebars é um processador de templates que gera a tua página HTML dinamicamente, poupando-te o tempo de fazeres atualizações manuais. Neste tutorial, vou apresentar-te o Handlebars, e ensinar-te como criar um template básico para o teu site.


Template do site

Há duas principais razões pelas quais tu deverás querer fazer um template para o teu site. Primeiro que tudo, construir um template incentiva-te a separar o código baseado em lógica da visualização, ajudando-te a aderir ao modelo Visualização/Controlador. Em segundo lugar, os templates mantêm o teu código limpo e sustentável, que, por sua vez, tornam o processo de atualização do teu site mais simples. Não crias um site com Handlebars. Em vez disso, crias linhas orientadoras e estruturas que ditam como o site deverá ser sem te focares na informação da página. Vamos ver alguns dos princípios básicos.


Os princípios básicos

O Handlebars gera o teu HTML ao agarrar na estrutura JSON e executá-la através do template. Estes templates são escritos maioritariamente em HTML regular, e com placeholders que te permitem injectar informação, quando necessário. Por exemplo, o seguinte template, saúda o utilizador quando faz log in:

1
<h1>Welcome back, {{name}}</h1>

O atributo {{name}} é onde o nome do utilizador será injectado na página. Este placeholder corresponde a uma propriedade na estructura de informação JSON. Este é o exemplo mais básico possível, mas irás ver brevemente que basicamente tudo o resto se reduz a este conceito simples. Vamos continuar com a manipulação de arrays.

Arrays

O Handlebars vem com auxiliares integrados para te ajudar a trabalhar com informação mais complexa. Um destes auxiliares é o auxiliar each. Este auxiliar itera sobre um array e permite-te criar HTML dinâmico, por cada elemento do array. Por exemplo, o seguinte exemplo mostra a informação de um array que contém uma lista de concertos locais a ocorrer na minha área:

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>

Como podes ver, este código é muito mais limpo que código convencional, como usar um loop em PHP ou JavaScript para juntar HTML a uma variável. O Handlebars não é intrusivo, e isto é o que torna o Handlebars tão acessível. Podes também notar que usamos o nome do atributo, this, para retornar o elemento atual do array no loop each.

Este exemplo é bom para qualquer array com valores simples, mas como lidar com informação mais complexa? Bem, essencialmente fazes a mesma coisa. Por exemplo, vamos escrever um template simples para a seguinte informação:

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
]

Podemos facilmente mostrar esta informação usando o seguinte template:

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>

Podes guardar o teu template num elemento <script /> e carregá-lo com JavaScript.

No Handlebards, podes até aceder a propriedades agrupadas, como no exemplo acima (Albums.0.Name), e claro, podias ter usado outro loop each para iterar sobre os álbuns da banda. Vale a pena reparar que apesar da notação de ponto para aceder às propriedade agrupadas, também podes usar "../" para aceder às propriedades do pai.

E se não existir nenhuma banda a tocar? Certamente não irás querer uma tabela vazia, e o Handlebars felizmente fornece auxiliares if, else e unless. As declarações if e else funcionam como na maior parte das linguagens de programação: se o objeto que passas é false or falsey, então a declaração else é executada. Caso contrário, a declaração if é executada. A declaração unless é muito interessante; essencialmente é uma declaração if invertida. Se a expressão é true, o bloco unless NÃO vai ser executado. Vamos então incorporar estes auxiliares no nosso código:

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}}

Auxiliares personalizados

O Handlebars dá-te a oportunidade de criar o teu próprio auxiliar personalizado. Regista simplesmente a tua função no Handlebards, e qualquer template que compiles depois pode aceder ao teu auxiliar. Existem dois tipos de auxiliares que podes fazer:

  • Auxiliares de função são basicamente funções regulares que, uma vez registadas, podem ser chamadas em qualquer lugar no teu template. O Handlebars escreve no template o valor que a função retorna.
  • Auxiliares de bloco são de semelhante natureza que os auxiliares if, each, etc. Eles permitem-te mudar o contexto do que está dentro.

Deixa-me mostrar-te um exemplo rápido de cada. Primeiro, vou registar uma função auxiliar com o seguinte código:

1
Handlebars.registerHelper("Max", function(A, B){
2
	return (A > B) ? A : B;
3
});

O primeiro argumento que é passado para o registerHelper() é o nome do meu auxiliar personalizado; vou usar este nome no template. O segundo argumento é a função associada a este auxiliador.

Usar este auxiliador num template é extremamente simples:

1
{{Max 12 45}}

O template usa o auxiliador Max, e passa os valores 12 e 45 para a função associada. Os auxiliadores de função no Handlebars suportam múltiplos parâmetros. Podes inserir números diretamente no próprio template, ou usar atributos de uma estrutura JSON.

Vamos agora dar uma olhadela num auxiliar de bloco personalizado. Os auxiliares de bloco permitem-te definir o contexto antes de correr o código contido no bloco. Por exemplo, considera o objecto seguinte:

1
{
2
	Name: "Parent",
3
	Sub: {
4
		Name: "Child"
5
	}
6
}

De forma a mostrar ambos os nomes, podes escrever um auxiliar em bloco que corre o template assim que tiver o contexto do pai, e uma vez com o contexto do filho. Aqui está o auxiliar:

1
Handlebars.registerHelper("BothNames", function(context, options){
2
	return options.fn(context) + options.fn(context.Sub);
3
});

E o template tem este aspecto:

1
{{#BothNames this}}
2
	<h2>{{Name}}</h2>
3
{{/BothName}}

A tag hash antes do nome do auxiliar diz ao Handlebars que isto é um bloco auxiliador, e fechas o bloco como fecharias uma tag de HTML. A função options.fn executa a secção do template dentro do bloco com qualquer contexto que lhe dês.

Agora que temos as bases, vamos começar a criar uma demo completa.


Construir um template de um site

Tu não crias um site com Handlebars.

O template que vamos criar é para um site de receitas. Isto irá dar-te um bom conhecimento do Handlebards, na medida em que inclui obter dados de uma API e passá-los por um template.

Configurar um projecto Handlebars

Primeiro devemos carregar o código do nosso template, mas para tal, precisamos de criar um novo ficheiro HTML e incluir a nossa biblioteca 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>

Por conveniência, podes guardar o teu template num elemento <script /> e carregá-lo com JavaScript. Isto é muito mais limpo do que guardá-lo diretamente numa variável de JavaScript.

Vamos agora discutir como é que esta app irá funcionar. Primeiro, a app conecta-se a uma API (eu estou a usar Yummly) para extrair informação de algumas receitas. Depois, passamos esta informação ao Handlebars e corremo-la ao longo do template. Finalmente, substituímos a secção body com o HTML novo gerado. É um processo bastante claro; então, vamos começar por adicionar um segundo bloco script mesmo antes do fecho da tag body, e instanciar uma variável 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>

Se a informação do teu site muda regularmente, então deverás querer dar uma olhadela ao Handlebars.

Este é o código completo para compilar e gerar código HTML a partir de um template. Tecnicamente podes passar os dados JSON a partir de uma API diretamente para o Handlebars, mas podes encontrar problemas de origem. Ao invés de executar algum tipo de hack ou usar PHP para fazer "echo" dos dados numa variável JavaScript, decidi pôr tudo isso num ficheiro separado: Recipe.php Antes de começarmos a criar o template, vamos dar uma olhadela no ficheiro PHP.

Recebendo a Informação

A API Yummly é muito simples. Não existe nenhum sistema complexo de autenticação: Só tens que te registar, obter algumas credenciais, e inseri-las no URL. Podes fazer echo da informação diretamente se quiseres, mas eu quero informação um pouco mais detalhada de cada receita. Portanto, vou processar a informação da primeira chamada da API e fazer um segundo pedido para cada receita. Aqui está o código completo de 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
?>

Ao construires o teu site com um template Handlebars, podes produzir um site completo com bom código em apenas algumas linhas. Aqui está o template todo:

1
<script id="Handlebars-Template" type="text/x-handlebars-template">
2
	<div id="Content">
3
	  <h1>&Xi;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>

Vamos correr o nosso código. As primeiras sete linhas são apenas o logótipo que está no topo da página. Depois, para cada receita, criamos um cartão de receita com uma imagem, nome, e ingredientes.

A API Yummly retorna uma lista de dados sobre sabores (ex: quão doce, azedo, picante, etc..) para cada item. Escrevi uma função auxiliar, chamada getFlavor que pega nesta informação e retorna o sabor mais dominante no prato. Para que este template funcione, temos que chamar o auxiliar getFlavor no Handlebars antes de intepretar o template. Então no início da segunda secção de código, adiciona o seguinte código antes do código 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
});

Agora, sempre que o Handlebars vê getFlavor, chama a função associada e retorna a informação sobre o sabor.

Até este momento, podes brincar e desenhar o template que quiseres, mas vais provavelmente ver que este processo é lento. Isto deve-se principalmente às três chamadas da API antes do Handlebars carregar a página. Obviamente, isto não é o ideal, mas pré-compilar o teu template pode ajudar.


Pré-compilar

Tens duas opções diferentes, quando se trata de Handlebars. A primeira é apenas pré-compilar o template atual. Isto reduz o tempo de carregamento, e não terás que incluir o compilador Handlebars na tua página.

Isto também resulta num ficheiro com tamanho mais pequeno, mas na verdade não ajuda muito no nosso cenário.

O nosso problema é a comunicação entre o browser e a API. Se quiseres compilar o teu template, podes fazer download do Node.js através de npm com o seguinte comando:

1
npm install handlebars -g

Podes precisar de fazer isto na raíz (ex: adiciona 'sudo' antes do comando). Uma vez instalado, podes criar um ficheiro para o teu template e compilá-lo assim:

1
handlebars demo.handlebars -f demo.js

Deves dar ao teu ficheiro template a extensão .handlebars. Isto não é obrigatório, mas se o nomeares algo como demo.html, então o nome do template irá ser "demo.html" ao contrário de apenas "demo". Após nomeares o teu template, inclui apenas o ficheiro de saída com a versão do Handlebars que está a correr (podes usar a versão normal, mas é maior) e escrever o seguinte:

1
var template = Handlebars.templates['demo'];
2
var html = template({ Your Json Data Here });

A declaração unless é... essencialmente uma declaração if invertida.

Mas, como foi mencionado anteriormente, isto não nos ajuda muito neste cenário. Então o que podemos fazer? Bem, podemos pré-compilar e produzir o ficheiro inteiro. Isto faz com que possamos correr o template com a informação e guardar o output final de HTML - guardar em cache, por outras palavras. Isto acelera drasticamente o tempo de carregamento da tua aplicação. Infelizmente, o JavaScript do lado do cliente não tem um ficheiro com capacidades IO. Então, a forma mais simples de conseguir isto é simplesmente colocar o output de HTML numa caixa de texto e guardá-la manualmente. Está atento às normas das APIs relativamente a guardar os dados em cache. A maioria das APIs têm um máximo de tempo para a informação ser guardada em cache; garante que encontras essa informação antes de guardares páginas estáticas.


Conclusão

Isto foi uma rápida introdução ao Handlebars. Continuando, podes olhar para os "Partials" - templates pequenos que podem ser usados como funções. Como sempre, não hesites em deixar um comentário ou pergunta na secção de comentários abaixo.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.