Páginas

quinta-feira, 21 de janeiro de 2016

Criando modelos de HTML com Underscore.js

Em aplicações web, os dados que são retornados de serviços, como um REST retornando um objeto JSON, precisam ser exibidos aos usuários. Ou seja, pegamos essas informações e as transformamos em elementos HTML (parágrafos, tabelas, listas, etc). Uma das maneiras de se fazer isso é a manipulação desses dados através de scripts de forma a montar cada elemento. Por exemplo, o código abaixo pega um objeto JSON e o transforma em um parágrafo, através da concatenação do valor com as tags "<p>" e "</p>". Isso a princípio parece ser algo simples, mas no final acabamos com um código de difícil manutenção. Imagine um JSON que tenha um array de objetos para ser montado em uma tabela, com cada linha precisando gerar todas as respectivas tags " <tr>", "<td>" e etc. Pior, imagina esquecer de fechar uma tag ou concatenar sem fechar uma aspas. A próxima pessoa que for dar manutenção vai gastar um bom tempo entendendo o que foi feito (e qual é o resultado esperado).

var dado = { valor: 'teste' };
var valor = "<p>" + dado.valor + "<p>";
$("#lista").append(valor);

Obs. No exemplo, usamos jQuery para colocar o conteúdo HTML formatado dentro de um outro elemento.

O que precisamos é de uma forma fácil de poder visualizar a estrutura HTML desejada e inserir nos pontos específicos as informações. Para isso, uma boa maneira é trabalhar com ferramentas de templates (modelos). O Angular.js (https://angularjs.org/) e o Knockout (http://knockoutjs.com/) fazem isso muito bem, mas na falta da possibilidade do seu uso, temos que pensar em alternativas.

O Underscore (http://underscorejs.org/) é uma biblioteca JavaScript que existe há um bom tempo, e funciona como um "cinto de utilidades" para o desenvolvedor. Há várias funcionalidades nela, e uma telas é o template. Em linhas gerais, funciona assim:

  1. O desenvolvedor cria um modelo HTML (um trecho HTML que será repetido várias vezes);
  2. Dentro desse modelo, ele define os pontos que serão substituídos com valores;
  3. Via código, esse modelo HTML é obtido e "compilado" pelo Underscore;
  4. Esse objeto "compilado" recebe um objeto JSON, e internamente faz a substituição de valores, gerando como resultado um trecho HTML completo.

Vejamos o exemplo de HTML abaixo. Note que existe uma tag script cujo tipo é "text/template". Dentro desta tag está o nosso modelo, o trecho HTML que será repetido a partir de um objeto JSON (que no nosso exemplo, contém uma lista de estados e cidades brasileiras).

Figura mostrando uma página HTML com caixas com o nome dos estados e algumas cidades


Esse modelo é composto por uma div de classe item contendo um parágrafo onde será colocado o nome do estado e uma lista onde serão colocadas as cidades. Cada conjunto de objetos HTML será repetido tantas vezes quanto forem os dados dentro o objeto JSON.

<!doctype html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Exemplo de uso de templates do Underscore.js</title>
        <link rel="stylesheet" type="text/css" href="estilo.css">
    </head>
    <body>
        <script type="text/javascript" src="bower_components/jquery/dist/jquery.min.js"></script>
        <script type="text/javascript" src="bower_components/underscore/underscore-min.js"></script>
        <script type="text/template" id="modelo">
            {% _.each(estados, function(estado) { %}
                <div class="item">
                    <p>{{ estado.nome }}</p>
                    <div>
                        <ul>
                            {% _.each(estado.cidades, function(cidade) { %}
                                <li>{{ cidade }}</li>
                            {% }); %}
                        </ul>
                        {% if(estado.cidades.length === 0) { %}
                            <div>N/D</div>
                        {% } %}
                    </div>
                </div>
            {% }); %}
        </script>
        <h2>Lista de Estados e suas cidades</h2>
        <div id="lista">
            <!-- conteúdo vai aqui -->
        </div>
        <script type="text/javascript" src="exemplo.js"></script>
    </body>
</html>

Veja que temos alguns caracteres especiais que definem o comportamento do modelo. Logo na primeira linha temos dentro das tags "{%" e "%}" um código JavaScript que efetua um loop dentro do array estados. Note também que no final do script há o fechamento deste bloco de código JavaScript. Quando a esse modelo for aplicado o objeto JSON, esse loop irá executar para permitir que os dados sejam criadas as divs que representam cada um dos estados brasileiros.

Dentro da div, temos na tag "<p>" os caracteres "{{" e "}}". Eles definem o conteúdo que seja colocado dentro deste elemento HTML. No nosso exemplo, o parágrafo será preenchido com o nome do estado.

Há também uma lista (UL) que irá conter as cidades de cada estado. Novamente temos um loop que irá percorrer um array, que neste caso é o array de cidades. E por fim, caso não haja nenhuma cidade na lista, temos uma instrução if em JavaScript, devidamente inserida dentro dos caracteres especiais, para exibir a mensagem N/D (não disponível) caso o tamanho do array de cidades seja zero.

Vamos ver agora o código JavaScript por trás dessa página (que está referenciado no final do HTML, no arquivo exemplo.js.

var dados = {
    estados: [
        {
            nome: 'São Paulo',
            cidades: [
                'São Paulo', 
                'Santo André',
                'Guarulhos'
            ]
        },
        {
            nome: 'Minas Gerais',
            cidades: []
        },
        {
            nome: 'Rio de Janeiro',
            cidades: [
                'Niterói', 
                'Angra dos Reis'
            ]
        }        
]    
};

(function(){
    
    _.templateSettings = {interpolate : /\{\{(.+?)\}\}/g,      // print value: {{ value_name }}
                        evaluate    : /\{%([\s\S]+?)%\}/g,   // excute code: {% code_to_execute %}
                        escape      : /\{%-([\s\S]+?)%\}/g}; // excape HTML: {%- <script> %} prints <script>  
    
    var modelo = _.template($("#modelo").html());
    var conteudo = modelo(dados);
    $("#lista").append(conteudo);
})();

A primeira coisa que foi feita e a declaração do objeto JSON de estados e cidades. Talvez agora fique mais simples de entender a estrutura do modelo, visualizando a estrutura do JSON. É um objeto que possui um array chamado "estados", e para cada elemento temos uma propriedade de nome e uma propriedade que é outro array de cidades.

Em seguida temos um trecho de código JavaScript. A primeira coisa que fazemos aqui é definir os caracteres especiais que o Underscore irá entender para fazer a aplicação de dados no modelo. Eu não disse, mas existe um padrão já do Underscore que usa "<%=" e "%>", por exemplo, como caracteres especiais de escape. Mas eu não gosto dele assim pois confunde se formos trabalhar com uma página dentro do ASP.NET (Webforms, zuado...). Além disso, usar chaves dá uma cara mais parecida com a forma de trabalhar do Angular.js :-).

Bom, depois de definido, temos o código que faz a geração do HTML. Pegamos o conteúdo do modelo através do jQuery (o nome do elemento script é "modelo"), aplicamos a função template do Underscore para obter o gerador de HTML, e por fim passamos o objeto JSON para a criação do HTML final. Esse HTML final então é colocado na página, através novamente do método append do jQuery. Como resultado final, temos o HTML montado!

Um detalhe. Veja que tanto o método each, usando nos loops do modelo, quanto o ajuste de configurações do Underscore, com a propriedade templateSettings, é de um objeto chamado "_". Exatamente o caractere de underscore (sublinhado). Nada é por acaso...

E por fim, para fechar o exemplo, aqui está o CSS que define as propriedades visuais da página de exemplo.

body{
    font-family: Verdana, Geneva, Tahoma, sans-serif;
}

.item {
    width: 10em;
    background-color: lightyellow;
    border-style: solid;
    border-width: 0.1em;
    border-color: sandybrown;
    float: left;
    margin: 0.5em;
    padding: 0.5em;
}

.item > p {
    text-align: center;
    background-color: moccasin;
}

.item > div {
    background-color: moccasin;
    margin: 0.1em;
}

.item > div > div {
    width: 100%;
    text-align: center;
    font-weight: bold;
}

O template é só uma das inúmeras funcionalidades que o Underscore provê. Ele ainda dá suporte para métodos de pesquisa em arrays (find, filter), muito úteis para a manipulação de objetos JSON (algo parecido com o LINQ do .NET).

Até a próxima.