HandleBars
O HandleBars, vem se tornando um dos Templates Engines mais utilizados, devido ao suporte a separação de responsabilidade, fazendo com que ele seja o pedido ideal para quem possui uma estruturua MVC
com NodeJS.
Com ele, nós podemos gerar HTML
dinâmico, e fazer com que nossos arquivos de template se comuniquem de forma direta com a lógica da nossa aplicação (nos força a não executar lógica dentro do HTML).
Ele é uma extensão do Mustache, outra engine de templates, e segue uma sintaxe semelhante, mas com mais funcionalidades 😉
Observação: no caso do HandleBars
, ele é um Template Engine que não é exclusivo do NodeJS, logo, pode ser usada em conjunto com outras linguagens de programação (consulte a documentação para mais detalhes sobre isso).
Criando nosso projeto de testes
Antes de colocarmos a mão na massa, é deveras importante que você configure o seu projeto inicial.
Para isso, eu criei uma pasta chamada de HandleBars dentro da minha área de trabalho (desktop):
Com o seu terminal (Prompt de Comando) aberto na pasta raiz que acabamos de criar, precisamos inicializar o nosso projeto por meio do NPM
, sendo assim, execute o seguinte comando abaixo:
npm init -y
A flag -y
, como você já deve saber, cria um novo projeto de forma enxuta, respondendo SIM para tudo 😅
Feito isso, vamos instalar também a biblioteca do express
, pois iremos precisar dela nesta lição:
npm install express
Por fim, não se esqueça de criar seu index.js
, junto com a lógica inicial do seu servidor web:
const express = require('express');
const app = express();
const port = 3000;
// Rota principal
app.get('/', (req, res) => {
res.send('Olá, mundo!');
});
// Iniciar o servidor
app.listen(port, () => {
console.log(`Servidor rodando em http://localhost:${port}`);
});
Instalando o HandleBars
Como estamos usando o NodeJS em conjunto com a biblioteca express
, você deve instalar o pacote express-handlebars
no seu projeto.
Para isso, execute o comando abaixo na pasta raiz do projeto:
npm install express-handlebars
Feito isso, vamos entender um pouco mais o uso dessa biblioteca 😉
Configuração inicial do HandleBars
Com as configurações inicias que nós fizemos no index.js
no tópico anterior, a configuração inicial do HandleBars
se mistura um pouco com as configurações do express
.
A primeira coisa que precisamos fazer, é realizar a importação da biblioteca:
const exphbs = require("express-handlebars");
Em seguida, devemos instanciar a biblioteca em conjunto com o express
da seguinte forma:
app.engine('handlebars', exphbs.engine());
app.set('view engine', 'handlebars');
app.engine('handlebars', exphbs.engine())
: este método permite registrar um mecanismo de template no Express
.
Um "mecanismo de template" processa arquivos HTML
dinâmicos, permitindo a renderização de páginas no servidor com conteúdo dinâmico.
'handlebars'
: é o nome do mecanismo que você está registrando. Neste caso, você está dizendo ao Express
que quer registrar um mecanismo de template chamado handlebars
.
exphbs.engine()
: está utilizando a função engine()
da biblioteca express-handlebars
(referida como exphbs), que registra o Handlebars
como um mecanismo de template.
O exphbs.engine()
é responsável por lidar com a renderização dos templates que levam a extensão .handlebars
.
defaultLayout: false
: este comando é responsável por desativar a procura pelo arquivo view > layouts > main.handlebars, que seria considerado um arquivo de layout padrão. Como não precisamos dele, deixamos essa configuração como false
.
app.set('view engine', 'handlebars')
: esse método configura uma opção no Express
. No caso, você está definindo o mecanismo de renderização de views
(páginas HTML) para handlebars
.
Por fim, basta chamar o método render
que vai passar a existir dentro a variável res
, você pode fazer isso da seguinte forma:
app.get('/', (req, res) => {
res.render('home', { layout: false });
});
res.render('home', ...)
: o método render
é usado para renderizar uma view
(ou página) com base no mecanismo de template configurado.
O primeiro parâmetro home
é o nome da view
que você deseja renderizar.
Neste caso, o Express
procurará um arquivo de template chamado home.handlebars
(ou .hbs, dependendo da configuração) na pasta de views
.
{ layout: false }
: o segundo parâmetro, representa um objeto contendo as opções que nós podemos repassar para dentro do nosso template. Ele controla como a renderização deve ser feita, neste caso, especificamente com relação ao uso do layout.
No final das contas, o seu arquivo index.js
ficará representado desta forma:
const express = require('express');
const exphbs = require('express-handlebars');
const app = express();
const port = 3000;
//Configuração do HandleBars
app.engine('handlebars', exphbs.engine({
defaultLayout: false // Desativa o uso de layouts padrão
}));
app.set('view engine', 'handlebars');
// Rota principal
app.get('/', (req, res) => {
res.render('home', { layout: false });
});
// Iniciar o servidor
app.listen(port, () => {
console.log(`Servidor rodando em http://localhost:${port}`);
});
Mas ainda, tudo o que nós fizemos até então, não é o suficiente para rodarmos o nosso projeto, pois precisamos criar o nosso layout 😉
Criando a pasta Views
Dentro da pasta raiz do seu projeto, vamos criar uma nova pasta chamada de views:
Dentro dela, nós iremos criar todos os nossos templates (arquivos HTML) que levarão a extensão .handlebars
em vez de .html
.
Criando nosso arquivo de home
No tópico anterior, você configurou a rota principal (/) para renderizar (render
) um arquivo chamado de home
, que representa o nosso layout.
Sendo assim, nada mais justo que criarmos o arquivo home.handlebars
dentro da pasta views:
Lembrando que você pode escrever códigos HTML
normalmente como faria em qualquer outro arquivo .html
.
Passando dados para dentro das nossas Views
Antes de estilizarmos as nossas views, que tal aprendermos a passar alguns dados para ela?
O processo é bem simples, vamos criar uma série de variáveis que irão representar desde strings
, arrays
, objects
, booleans
e etc...
E em seguida, vamos passar todos esses dados diretamente para a nossa view.
Essa passagem de dados, acontece por meio de um objeto, onde conseguimos setar nossas chaves e valores.
Observe abaixo como isso pode ser feito:
// Rota principal
app.get('/', (req, res) => {
//Variaveis que serão enviadas a nossa View:
const nome = "Micilini Roll";
const rank = 128;
const isAtivo = true;
const dinheiro = 12.98;
const numerosDaSorte = [23, 98, 87, 76];
const informacoesAdicionais = {
site: 'https://micilini.com',
isHttps: true
}
//Passamos todos os parâmetros para a nossa view como segundo parâmetro dentro de uma estrutura de chave e valor:
res.render('home', {
nome: nome,
rank: rank,
isAtivo: isAtivo,
dinheiro: dinheiro,
numerosDaSorte: numerosDaSorte,
informacoesAdicionais: informacoesAdicionais
});
});
Observe que passamos cada um dos nossos dados como o segundo argumento do método render()
.
Lembrando que o nome das chaves (que você estiver colocando no segundo parâmetro), será o nome das variáveis que você terá acesso globalmente dentro dos seus arquivos de template (.handlebars
).
Feito isso, vamos ver como recuperar, e usar cada um dos valores recebidos pelo template 😉
Observação: Você pode passar o segundo argumento como null
, ou um objeto vazio ({}
), isto é, caso não precise passar nenhuma informação para o seu template:
res.render('home', {});
....
res.render('home', null);
Mostrando dados em uma view
A coisa mais simples que veremos até agora é mostrar o conteúdo que uma view pode receber.
Para isso, você só precisa mencionar o nome da variável global dentro de duas chaves duplas ({{ nomeDaVariavel }}
), por exemplo:
<h1>Bem Vindo: {{ nome }}</h1>
Lembrando que você pode mostrar quaisquer tipo de variáveis, com exceção dos tipos arrays
e objects
, que precisam de um tratamento especial para serem exibidos.
Para exibir um array, basta informar o seu index
da seguinte forma:
<p>Meu número favorito é: {{ numerosDaSorte.[0] }}</p>
Para exibir um objeto, basta informar a sua chave da seguinte forma:
<p>Meu site é: {{ informacoesAdicionais.site }}</p>
Renderizações condicionais com uma view
Com o HandleBars
, nós conseguimos executar algumas renderizações condicionais (estruturas If
e Else
) dentro dos nossos templates.
No caso de um If
simples, basta seguir a sintaxe abaixo:
{{#if LOGICA_CONDICIONAL }}
....
{{/if}}
Lembrando que o HandleBars
só vai mostrar o pedaço daquele HTML
se o resultado da condicional for verdadeiro ou falso.
Isso quer dizer que no local onde está escrito LOGICA_CONDICIONAL
, só podemos passar valores como TRUE
ou FALSE
.
🤖 Nesse caso, toda a lógica comparativa, já deve vir preparada antes de passarmos os dados para o nosso template 🤖
Veja um pequeno exemplo:
{{#if isAtivo }}
<p>O usuário está ATIVO</p>
{{/if}}
No caso do If
simples acima (sem o Else
), ele só irá mostrar a mensagem "O usuário está ATIVO", caso a condição for verdadeira, ou seja, se o isAtivo
for true
.
Veja alguns exemplos do que não é possível fazer com o HandleBars
:
{{#if !isAtivo }}
<p>O usuário está ATIVO</p>
{{/if}}
{{#if rank < 128 }}
<p>Você faz parte de um grupo seleto, está quase lá...</p>
{{/if}}
{{#if rank > 128 }}
<p>O seu Rank está baixo...</p>
{{/if}}
{{#if nome == "Micilini Roll" }}
<p>Você deve ser o MICILINI!</p>
{{/if}}
O HandleBars
é um Template Engine muito simples, tão simples que ele não aceita uma comparação condicional de forma direta, tal qual como aquelas que vimos nos comandos acima.
No caso das verificações acima, você precisa resolvê-las primeiro no Javascript, antes de jogar para o template, por exemplo:
....
isNotAtivo = false;
if(!isAtivo){
isNotAtivo = true;
}
...
res.render('home', { isNotAtivo: isNotAtivo });
rankMoreThan128 = false;
rankLessThan128 = false;
if(rank < 128){
rankLessThan128 = true;
}else{
rankMoreThan128 = true;
}
...
res.render('home', { rankLessThan128: rankLessThan128, rankMoreThan128: rankMoreThan128 });
Note que em todas as lógicas implementadas acima, eu já realizei todas as comparações necessárias, antes de enviá-las ao template.
No caso do else
, a nossa renderização condicional, passa a funcionar de uma forma mais completa, vejamos a sua sintaxe:
{{#if isAtivo }}
<p>O usuário está ATIVO</p>
{{else }}
<p>O usuário não está ATIVO!</p>
{{/if}}
Estrutura de Repetição com uma view
Com o HandleBars
, nós temos a marcação {{#each}}
para simular um forEach
do Javascript.
Vejamos como funciona a sua sintaxe:
{{#each umArray}}
<li>Valor: {{this}}</li>
{{/each}}
Observe que usamos a palavra reservada {{this}}
para me referir ao valor atual de cada item do nosso array
.
Veja um caso de uso real, usando os dados que a nossa view está recebendo:
<h1>Numeros da Sorte</h1>
<ul>
{{#each numerosDaSorte}}
<li>Número da sorte: {{this}}</li>
{{/each}}
</ul>
Caso você quisesse interar um objeto (em vez de um array), você poderia fazer isso da seguinte forma:
<h1>Informações Adicionais</h1>
<ul>
{{#each informacoesAdicionais}}
<li>{{@key}}: {{this}}</li>
{{/each}}
</ul>
No caso de um objeto, temos a palavra reservada @key
que é usada para se referir ao nome da nossa chave.
Contexto de dados com uma view
No HandleBars
, você pode criar um contexto geral com o comando {{with}}
, permitindo que você acesse as propriedades diretamente, sem precisar usar o caminho completo.
Sem o comando {{with}}
teríamos que acessar o caminho completo de um objeto dessa forma:
<p>Site: {{informacoesAdicionais.site}}</p>
<p>HTTPS: {{informacoesAdicionais.isHttps}}</p>
Já com o comando {{with}}
, podemos poupar o nome da variável principal, observe:
{{#with informacoesAdicionais}}
<p>Site: {{site}}</p>
<p>HTTPS: {{isHttps}}</p>
{{/with}}
Com o comando {{with}}
podemos abstrair um objeto, ou seja, acessar suas propriedades sem a necessidade de citá-lo novamente.
Criando Helpers com HandleBars
Em outros Templates Engines, você tem a possibilidade de chamar algumas funções, ou até métodos de determinada classes de dentro do seu próprio template, como é o caso do Twig em conjunto com o PHP:
{{ /class/meuUtilitario::formatarMoeda(troco) }}
No caso do HandleBars
, por ele ser bem simples, inicialmente ele não possui este tipo de suporte 🙁
Mas... em contrapartida, você pode usar helpers
que vão te ajudar a definir funções que podem ser chamadas diretamente no template.
Helpers
nada mais são do que funções, que você define no lado do servidor (NodeJS), e chamá-las diretamente no seu template Handlebars
.
Um Helper
é configurado do lado do NodeJS, mais especificamente dentro do método engine
da seguinte forma:
const express = require('express');
const exphbs = require('express-handlebars');
const path = require('path');
const app = express();
// Configurando o Handlebars e criando um helper personalizado
app.engine('handlebars', exphbs.engine({
defaultLayout: false,
helpers: {
formatCurrency: function(value) {
return `$${value.toFixed(2)}`; // Formata o valor como moeda
}
}
}));
app.set('view engine', 'handlebars');
app.set('views', path.join(__dirname, 'views'));
// Rota principal
app.get('/', (req, res) => {
const dinheiro = 12.98;
res.render('home_helpers', {
dinheiro: dinheiro
});
});
// Inicia o servidor
app.listen(3000, () => {
console.log('Servidor rodando na porta 3000');
});
Já no seu arquivo de template, basta usar o Helper
da seguinte forma:
<p>Dinheiro formatado: {{formatCurrency dinheiro}}</p>
Note que estamos chamando uma função Javascript chamada de formatCurrency
, responsável por formatar um determinado valor.
Você sabia que é possível criar um Helper, de modo a adicionar um suporte a uma renderização condicional customizada, ou seja, suporte a operadores de comparação, negação e entre outros?
Vejamos como isso pode ser feito:
const express = require('express');
const exphbs = require('express-handlebars');
const path = require('path');
const app = express();
// Configurando o Handlebars e criando helpers personalizados
app.engine('handlebars', exphbs.engine({
defaultLayout: false,
helpers: {
ifCond: function (v1, operator, v2, options) {
switch (operator) {
case '==':
return (v1 == v2) ? options.fn(this) : options.inverse(this);
case '===':
return (v1 === v2) ? options.fn(this) : options.inverse(this);
case '<':
return (v1 < v2) ? options.fn(this) : options.inverse(this);
case '<=':
return (v1 <= v2) ? options.fn(this) : options.inverse(this);
case '>':
return (v1 > v2) ? options.fn(this) : options.inverse(this);
case '>=':
return (v1 >= v2) ? options.fn(this) : options.inverse(this);
case '!=':
return (v1 != v2) ? options.fn(this) : options.inverse(this);
case '!==':
return (v1 !== v2) ? options.fn(this) : options.inverse(this);
default:
return options.inverse(this);
}
},
unlessCond: function (v1, operator, v2, options) {
switch (operator) {
case '==':
return (v1 != v2) ? options.fn(this) : options.inverse(this);
case '===':
return (v1 !== v2) ? options.fn(this) : options.inverse(this);
case '<':
return (v1 >= v2) ? options.fn(this) : options.inverse(this);
case '<=':
return (v1 > v2) ? options.fn(this) : options.inverse(this);
case '>':
return (v1 <= v2) ? options.fn(this) : options.inverse(this);
case '>=':
return (v1 < v2) ? options.fn(this) : options.inverse(this);
case '!=':
return (v1 == v2) ? options.fn(this) : options.inverse(this);
case '!==':
return (v1 === v2) ? options.fn(this) : options.inverse(this);
default:
return options.inverse(this);
}
}
}
}));
app.set('view engine', 'handlebars');
app.set('views', path.join(__dirname, 'views'));
// Rota principal
app.get('/', (req, res) => {
const rank = 128;
res.render('home_helpers_2', { rank: rank });
});
// Inicia o servidor
app.listen(3000, () => {
console.log('Servidor rodando na porta 3000');
});
Já no template, basta usar a lógica:
<h1>Rank</h1>
{{#ifCond rank '==' 128}}
<p>Seu rank é exatamente 128.</p>
{{else ifCond rank '>' 128}}
<p>Seu rank está acima de 128.</p>
{{else ifCond rank '<' 128}}
<p>Seu rank está abaixo de 128.</p>
{{/ifCond}}
<h2>Usando Negação com `unlessCond`:</h2>
{{#unlessCond rank '==' 128}}
<p>Seu rank não é 128.</p>
{{/unlessCond}}
Incrível, não?
Não! 😅
Isso não é nem de longe uma coisa 100% incrível, pois apesar do HandleBars
aceitar esse tipo de situação, isso fere um pouco a ideia de não trazer nenhum tipo de lógica para uma de nossas views 😫
Se fosse para trazer lógica para dentro de uma view, poderíamos optar por outro Template Engine, como um EJS
ou quem sabe um Twig
da vida, não?
Utilizando Partials com HandleBars
No Handlebars
, partials
são trechos de templates reutilizáveis que permitem criar partes de um layout que podem ser incluídas em outros templates (são conhecidos como mini-templates).
Eles são úteis para evitar duplicação de código, e garantir consistência em várias partes de suas views.
Como exemplo, você pode criar um template chamado de Header
, que representa um cabeçalho na sua aplicação.
Para que mais tarde, em outros templates, você pudésse chamar esse Header
de forma a reutilizá-lo em outras partes do seu código (ou você prefere repetir o código HTML toda vez?).
Para que o nosso servidor aceite o uso de partials
, nós precisamos realizar algumas modificações na implementação do nosso HandleBar
:
//Configuração do HandleBars
app.engine('handlebars', exphbs.engine({
defaultLayout: false, // Desativa o uso de layouts
partialsDir: ["views/partials"], //Define o diretório onde os arquivos do partials serão levados
}));
Nossos partials
(mini-templates), geralmente ficam na pasta views/partials, e seguem a mesma extenção .handlebars
de um template comum.
<h1>Meu Cabeçalho!</h1>
<p>Seu nome é: {{ nome }}</p>
Para chamar um determinado partial
dentro de um template comum, basta usar a sintaxe: {{>partial}}
, vejamos um exemplo:
{{> header}}
<p>Meu site é: {{ site }}</p>
Veja como ficou o index.js
:
const express = require('express');
const exphbs = require('express-handlebars');
const app = express();
const port = 3000;
//Configuração do HandleBars
app.engine('handlebars', exphbs.engine({
defaultLayout: false, // Desativa o uso de layouts
partialsDir: ["views/partials"], //Define o diretório onde os arquivos do partials serão levados
}));
app.set('view engine', 'handlebars');
// Rota principal
app.get('/', (req, res) => {
//Variaveis que serão enviadas a nossa View:
const nome = "Micilini Roll";
const site = "https://micilini.com/";
//Passamos todos os parâmetros para a nossa view como segundo parâmetro dentro de uma estrutura de chave e valor:
res.render('home_partials', {
nome: nome,
site: site
});
});
// Iniciar o servidor
app.listen(port, () => {
console.log(`Servidor rodando em http://localhost:${port}`);
});
Veja como ficou o resultado final:
Dessa forma, conseguimos modularizar ainda mais a nossa aplicação com a biblioteca HandleBars
😉
Utilizando arquivos estáticos com HandleBars
A inclusão de arquivos estáticos em nossos templates, funciona de forma muito semelhante ao que aprendemos durante a lição do express.
Para isso, precisamos definir uma pasta de arquivos estáticos, onde vai conter todos os nossos arquivos CSS, imagens e entre outros.
No meu caso, eu criei uma nova pasta chamada public, dentro da pasta raiz do nosso projeto:
Dentro dela, vamos criar um arquivo chamado de home.css
, com o seguinte conteúdo:
body, html{
background-color:green;
color:white;
}
Já dentro do seu index.js
, você precisa adicionar a seguinte configuração para ter suporte a esses arquivos:
//Configuração da pasta de arquivos estáticos
app.use(express.static('public'));
Por fim, basta que você chame o seu CSS
normalmente da seguinte forma:
<link rel="stylesheet" href="/home.css">
Lembrando que a mesma lógica também pode ser aplicada a outros arquivos existentes na pasta public
😉
Trabalhando com um layout padrão
Em tópicos anteriores, você notou que configuramos uma opção chamada de defaultLayout
para false
, o que faz com que o HandleBars
ignore um arquivo chamado de main.handlebars
, que pode existir dentro da pasta views > layouts.
Mas o que aconteceria se mudassemos essa opção para true
?
Quando mudamos essa opção para true
(ou simplesmente não declaramos o defaultLayout), o HandleBars
tentará usar um layout padrão chamado de main.handlebars
em todas as suas views que estiverem sendo carregadas.
Por padrão, o HandleBars
procura na pasta views > layouts, um arquivo chamado de main.handlebars
, que pode ser representado dessa forma:
<!DOCTYPE html>
<html>
<head>
<title>Título da Minha Aplicação</title>
</head>
<body>
{{{body}}} <!-- Conteúdo da view será injetado aqui -->
</body>
</html>
O arquivo main.handlebars
deve conter o esqueleto básico do layout da sua aplicação, e isso incluí um bloco de conteúdo onde o conteúdo específico da view será injetado.
Normalmente, usamos o marcador {{{body}}}
ou {{body}}
para indicar onde o conteúdo da view deve ser colocado.
Isso proporciona uma maneira eficaz de manter um layout consistente em várias páginas de sua aplicação, facilitando a manutenção e garantindo uma aparência uniforme.
Dessa forma, você não precisa ficar repetindo cada uma das tags principais do HTML
(DOCTYPE, HTML, HEAD, BODY...) em cada uma de suas views 😉
Arquivos da lição
Os arquivos que você viu durante o decorrer desta lição, podem ser encontrados neste link.
Conclusão
Nesta lição, você aprendeu a manusear um dos Templates Engines mais usados do mercado, o HandleBars
.
Até a próxima 🥳