Templates client-side externos com Underscore.js

Templates client-side têm sido amplamente utilizados devido à praticidade que trazem à criação e manutenção de websites e aplicações web, cada vez mais feature-rich.

Um projeto em que trabalhei recentemente já utilizava uma excelente biblioteca chamada Underscore.js, ou, como seu próprio site diz, um cinto de utilidades para JavaScript, conforme a definição retirada de lá:

Underscore is a utility-belt library for JavaScript that provides a lot of the functional programming support that you would expect in Prototype.js (or Ruby), but without extending any of the built-in JavaScript objects. It’s the tie to go along with jQuery’s tux, and Backbone.js’s suspenders.

Dentre os oitenta métodos muito úteis para o desenvolvimento front-end, o Underscore.js também oferece o _.template, que compila templates JavaScript em funções que podem ser renderizadas em tela. O exemplo abaixo mostra como é simples atingir este objetivo:

var compiled = _.template("hello: <%= name %>");
compiled({name : 'moe'});
=> "hello: moe"

var list = "<% _.each(people, function(name) { %> <li><%= name %></li> <% }); %>";
_.template(list, {people : ['moe', 'curly', 'larry']});
=> "<li>moe</li><li>curly</li><li>larry</li>"

var template = _.template("<b><%- value %></b>");
template({value : '<script>'});
=> "<b>&lt;script&gt;</b>"

Apesar da simplicidade que pode ser notada na utilização do método _.template, pode-se notar que o código não possui uma legibilidade boa. Para elementos simples, como uma simples impressão de <li> pode se suficiente, mas, e se for necessário código mais extenso, como uma tabela ou um bloco de informações que exija alguns div, h[1-6], ul, dl etc?

Uma prática comum é a utilização da concatenação de strings (arghh)…

var template = "<div class="info-box"><h2>Lista de Pessoas</h2>";
template += "<ul class="modal-list">";
template += " <% _.each(people, function(name) { %> ";
template += "</ul>"

…que, em vias de fato, seria o típico “tapar o sol com a peneira”, pois, além de exigir um extremo cuidado com a utilização das aspas, deixa a manutenção lenta e improdutiva pois, a cada linha deve ser instanciada a variável do template.

Ok, então podemos utilizar o caractere de escape ao final de cada linha. Assim deixamos um código mais legível:

var template = "<div class="info-box"> 
<h2>Lista de Pessoas</h2> 
<ul class="modal-list"> 
    <% _.each(people, function(name) { %> 
    <li><%= name %></li> 
    <% }); %> 
</ul> 
</div>;

Mais legível, concordo. Entretanto, ainda me incomoda ver os escapes de fim de linha, e, IMHO, não gostaria de ter de cuidar da manutenção de um código assim.

Pensando de maneira mais simples, podemos alcançar soluções melhores.

A primeira é utilizar o template embedded no próprio HTML, conforme no exemplo abaixo:

<script id="meu-template" type="text/template">
  <div class="info-box">
    <h2>Lista de Pessoas</h2>
    <ul class="modal-list">
      <% _.each(people, function(name) { %>
        <li><%= name %></li>
      <% }); %>
    </ul>
  </div>
</script>

Atente para o atributo type. Ele não precisa ser text/template. Ele apenas não deve ser text/javascript para seu navegador não tentar processar seu conteúdo como código JavaScript.

Dica: se você não gosta de ver código com delimitadores no estilo ERB, você pode padronizar para um que prefira ao utilizar:

_.templateSettings = {
  interpolate : /{{(.+?)}}/g
};

var template = _.template("Hello {{ name }}!");
template({name : "John"});
=> "Hello John!"

Finalmente temos uma opção legível, de manutenção simples (temos apenas código HTML e notações do Underscore no bloco) e fácil implementação, bastando, na chamada do _.template, referenciar o conteúdo do bloco inserido dentro das tags <script>:

var template = $("#meu-template").html()
_.template template, {people : ['moe', 'curly', 'larry']}

Pensei em ir ainda um passo além. Como minha aplicação possui muitas interações em tela e exige a atualização constante de elementos em tela, além de eu gostar de olhar meu código-fonte e vê-lo as clean as possible, pensei: por que não deixar estes templates fora do arquivo HTML? Desta forma, a manutenção ficaria perfeita, com templates organizados em uma pasta específica do projeto.

Desta forma, crio um arquivo HTML com o exato conteúdo presente dentro da tag <script> acima e o salvo com a extensão .html em uma pasta de meu projeto. Não poderia ser melhor, certo?

Diretório para armazenameto dos templates

Agora, para chamar o arquivo externo, nada como o bom e velho $.ajax do jQuery. Para atomizar as chamadas, basta criar uma função que realizará todo o processo:

render = (tmplName, tmplData) ->
    render.tmplCache = {}  unless render.tmplCache
    unless render.tmplCache[tmplName]
      tmplDir = "/templates"
      tmplURL = tmplDir + "/" + tmplName + ".html"
      $.ajax
        url: tmplURL
        method: "GET"
        dataType: "html"
        async: false
        success: (data) ->
          tmplString = data
      render.tmplCache[tmplName] = _.template(tmplString)
    render.tmplCache[tmplName] tmplData

O código acima é uma função JavaScript (escrita em CoffeeScript) que recebe como parâmetros o nome do arquivo do template e o objeto que será processado no template. Ou seja, o retorno desta função já será o código HTML que será inserido no contêiner desejado:

var obj = {people : ['moe', 'curly', 'larry']}
var content = render "info-box", {obj}
$('#container').html(content)

Agora sim ficou perfeito, certo?

Algumas considerações sobre o método render acima:

  • A utilização async: false garante que o código deve aguardar o carregamento do template.
  • É possível a criação de templates pré-carregados e pré-compilados
  • Esta excelente solução foi proposta por koorchik no Stackoverflow.
  • É importante analisar a aplicação para saber quando se deve utilizar esta abordagem, uma vez que uma nova requisição ajax será feita para cada template utilizado.

Esta solução adequou-se muito bem às minhas necessidades do projeto. Lembro que parti da proposição de que estava utilizando o Underscore.js. Existem outras bibliotecas interessantíssimas que fazem o templating de forma inteligente e DRY, como EJS, Mustache.js, Handlebars, dust.js etc. Vale a pena dar uma olhada na proposta de cada um deles. Para um overview, recomendo a leitura deste post de Veena Basavaraj, Engenheira de Software do LinkedIn.

Thanks to @pablocantero.

Nenhum comentário.

Leave a Reply

Your email address will not be published. Required fields are marked *