Visão do cliente

Usando o JsRender com JavaScript e HTML

John Papa

Baixar o código de exemplo

John PapaMuitas plataformas de desenvolvimento usam modelos para reduzir o código e simplificar a manutenção, e o HTML5 e o JavaScript não são exceção. O JsRender é uma biblioteca JavaScript que permite definir uma estrutura clichê uma vez e reutilizá-la para gerar HTML dinamicamente. O JsRender traz uma nova biblioteca de modelos ao desenvolvimento em HTML5 que possui uma sintaxe de marca sem código e alto desempenho, não tem dependência no jQuery e nem no DOM (Document Object Model), oferece suporte à criação de funções personalizadas e utiliza renderização pura baseada em cadeia de caracteres. Esta coluna aborda os cenários para os quais o JsRender é ideal e demonstra como usar seus vários recursos. Todos os exemplos de código podem ser baixados em archive.msdn.microsoft.com/mag201204Insight, e o JsRender pode ser baixado em bit.ly/ywSoNu.

De acordo com Boris Moore e com o blog da equipe do jQuery UI, em abril de 2011, a equipe decidiu suspender os modelos jQuery em favor da criação de uma biblioteca de renderização baseada em cadeia de caracteres e sem lógica. Isso fez com que Moore (uma das forças motrizes por trás dos modelos jQuery) criasse o JsRender e o JsViews. Essas duas bibliotecas são efetivamente as substituições para os modelos jQuery. Você pode ler mais sobre a história dos modelos e sobre a evolução do JsRender na postagem de Moore sobre os modelos jQuery e o mapa do JsRender (bit.ly/AdKeDk) e na postagem da equipe do jQuery (bit.ly/fhnk8A). A Visão do cliente explorará o JsRender e o JsViews nos próximos meses.

Por que modelos?

Usar modelos com o JavaScript reduz e simplifica o código. Sem modelos, adicionar uma série de itens de lista e outros elementos HTML para um conjunto de dados pode exigir a manipulação do DOM de um navegador. É nesse momento que um modelo usando um plug-in como o JsRender pode ser muito útil para fazer o trabalho pesado. Por exemplo, vamos supor que você recuperou um conjunto de filmes e deseja exibi-los. Você poderia escrever JavaScript e manipular o DOM, mas o código a seguir mostra que até mesmo essa tarefa simples pode se tornar difícil de ler, até mesmo com a ajuda do jQuery:

// Without a template
var i = 1;
$(my.vm.movies).each(function () {
  var movie = this;
  $("#movieContainer1").append(
    "<div>" + i++ + ": " + movie.name + " ("
    + movie.releaseYear + ")</div>");
});

O código é integralmente escrito com JavaScript e jQuery, mas pode ser difícil diferenciar o HTML e os dados do JavaScript. Usando um modelo, podemos separar a estrutura com mais facilidade e eliminar a maior parte do código JavaScript. O modelo a seguir (01-with-and-without-templates-with-jquery.html no download de códigos que acompanha este artigo) demonstra esse conceito:

<script id="myMovieTemplate" type="text/x-jsrender ">
  <div>{{:#index+1}}: {{:name}} ({{:releaseYear}})</div>
</script>

Observe que o modelo está inserido em uma marca de script, seu tipo está definido adequadamente e recebe uma ID para que possa ser identificado mais tarde. A renderização de um modelo exige três aspectos: um modelo, dados e um contêiner. O modelo define como os dados serão renderizados e o contêiner define onde renderizá-los. O código a seguir mostra como renderizar uma lista de filmes usando o modelo nomeado myMovieTemplate para um elemento com a ID do movieContainer:

$("#movieContainer").html($("#myMovieTemplate").render(my.vm.movies));

Esse exemplo usa o jQuery para simplificar a sintaxe. É importante observar que o JsRender não depende do jQuery. O código para usar o JsRender para renderizar os dados usando o modelo também pode ser escrito como mostrado aqui (02-jsrender-no-jquery.html no download de códigos):

my.vm = {
  movies: my.getMovies()
};
jsviews.templates({
  movieTemplate: document.getElementById("myMovieTemplate").innerHTML
});
document.getElementById("movieContainerNojQuery").innerHTML
  = jsviews.render.movieTemplate(my.vm.movies);

Renderizando modelos

Você pode renderizar modelos usando o JavaScript de várias formas. Primeiro, você deve definir seu modelo como uma cadeia de caracteres ou em uma marca de <script>. A opção de marca de <script> é boa quando você deseja definir seus modelos em HTML, atribuir uma ID a eles e reutilizá-los. Você também pode criar modelos com cadeias de caracteres, o que oferece a possibilidade de criá-los diretamente em código ou até mesmo retirá-los de um repositório de dados.

O método de renderização é usado para renderizar o conteúdo HTML dos dados usando um modelo. Um conjunto de dados pode ser renderizado com um modelo declarado em uma marca de <script> usando a sintaxe $(“#myTmpl”).render(data). Por exemplo, você pode renderizar uma lista de filmes com um modelo usando o código a seguir:

// #1: Render the my.vm data using the scriptTmpl from a script tag
var htmlString = $("#scriptTmpl").render(my.vm);
// Insert the htmlString into the DOM
$("#div1").html(htmlString);

Você também pode compilar um modelo com uma cadeia de caracteres usando a função $.templates(tmplString) e defini-lo para uma variável. Em seguida, você pode renderizar o modelo compilado, como mostrado aqui:

// #2: Compile a template from a string, return a compiled template
var tmpl2 = $.templates(tmplString);
htmlString = tmpl2.render(my.vm);
$("#div2").html(htmlString);

Você também pode compilar um modelo com uma cadeia de caracteres usando a sintaxe $.templates(name, template), como mostrado aqui:

// #3: Compile a template, name and register it
$.templates("myTmpl3", tmplString);
var htmlString = $.render.myTmpl3(my.vm);
$("#div3").html(htmlString);

Nesse exemplo, a função $.templates compila um modelo usando a cadeia de caracteres tmplString e o registra como um modelo nomeado. Em seguida, o modelo pode ser acessado por nome e renderizado usando a sintaxe $.render.name().

A função $.templates é semelhante aos métodos jQuery como .css ou .attrib, pois fornece uma sintaxe alternativa para registrar e compilar vários modelos em uma única chamada. Em vez de passar dois parâmetros (name e templateString), você pode passar apenas um parâmetro que consiste em um objeto de mapeamento com pares chave/valor para cada modelo que será registrado:

// #4: Compile multiple templates, register them and render
var tmplString2 = "<div>*** {{:movies.length}} Total Movies ***</div>";
$.templates({
  tmpl4a: tmplString,
  tmpl4b: tmplString2
});
htmlString = $.render.tmpl4a(my.vm);
htmlString += $.render.tmpl4b(my.vm);
$("#div4").html(htmlString);

Todas as propriedades no objeto se transformam em um modelo nomeado e registrado que pode ser renderizado. O valor da propriedade é a cadeia de caracteres que se tornará o modelo.

Você tem muitas opções para criar, registrar e renderizar modelos. A definição de modelos em marcas de script é uma abordagem comum para a maioria dos cenários. No entanto, a criação de modelos com cadeias de caracteres oferece muita flexibilidade. A sintaxe expandida anterior fornece ainda mais flexibilidade para associar outros recursos a modelos nomeados (como a declaração de funções auxiliares específicas para o modelo). Todos esses exemplos podem ser encontrados em 03-rendering-templates.html no download de códigos.

Conceitos básicos de JsRender

Os modelos JsRender consistem em marcação HTML mais marcas JsRender, como a marca {{for …}} ou {{: …}}. A Figura 1 mostra a sintaxe para as marcas JsRender mais básicas: as marcas {{: }} e {{> }}. Todas as marcas de modelo JsRender estão inseridas em chaves duplas. O nome da marca (neste caso, o caractere “:” ou “>”) pode ser seguido por um ou mais parâmetros ou expressões. (No caso da marca {{: }}, o resultado da expressão seria renderizado em seguida.) Uma vez que um modelo tiver sido definido e houver dados para renderizar nesse modelo, ele poderá ser renderizado.

Figura 1 Sintaxe básica do JsRender

Descrição Exemplo Saída
Valor da propriedade firstName do item de dados sem codificação {{:firstName}} Madelyn
Caminho do objeto simples para propriedade aninhada, sem codificação {{:movie.releaseYear}} 1987
Comparação simples {{:movie.releaseYear < 2000}} true
Valor sem codificação {{:movie.name}} Star Wars IV: Return of the Jedi
Valor codificado em HTML {{>movie.name}} Star Wars: Episode VI: <span style='color:purple;font-style: italic;'>Return of the Jedi</span>
Valor codificado em HTML {{html:movie.name}} Star Wars: Episode VI: <span style='color:purple;font-style: italic;'>Return of the Jedi</span>

O código a seguir contém um elemento HTML nomeado movie­Container (é nesse local que o modelo será renderizado):

<div id="movieContainer" class="resultsPanel movieListItemMedium"></div>
<script id="movieTemplate" type="text/x-jsrender">
  <div class="caption">{{:name}}</div>
  <div class="caption">{{>name}}</div>
  <img src="{{:boxArt.smallUrl}}"/>
  <div class="text">Year Released: {{:releaseYear}}</div>
  <div class="text">Rating: {{:rating}}</div>
</script>

O código anterior também contém o modelo nomeado movie­Template, que define um div para exibir o nome do filme sem usar codificação HTML, usando a sintaxe {{:name}}. O título nos dados de exemplo pode conter elementos HTML, por isso, para renderizar elementos que contém HTML é importante não usar codificação. No entanto, se desejar renderizar o HTML codificado, você pode usar a sintaxe com o caractere > ou usar HTML (como mostrado na Figura 1).

O valor da propriedade name contém elementos HTML, por isso, para fins apenas demonstrativos, o código anterior exibe o valor da propriedade name sem codificação ( {{:name}} ) e, em seguida, mostra o valor codificado em HTML ( {{>name}} ). Você pode executar o exemplo completo em 04-render-values.html no download de códigos.

Os dados de exemplo do filme passados para o modelo têm uma propriedade para boxArt que, por sua vez, tem uma propriedade para a smallUrl da imagem. O src da marca img é definido ao mergulhar na hierarquia de gráfico do objeto usando a sintaxe de ponto boxArt.smallUrl. Os caminhos também podem ser percorridos usando colchetes, portanto, em vez de escrever boxArt.smallUrl, o mesmo código de resultado pode ser obtido usando boxArt['smallUrl'].

É importante observar que a sintaxe do JsRender também pode ser usada para renderizar outros valores, como os nomes das classes ou IDs para elementos HTML.

A renderização do modelo JsRender detecta se o parâmetro de dados é uma matriz ou não. Se for uma matriz, o valor retornado será a concatenação das cadeias de caracteres que resultariam da passagem de cada um dos itens da matriz para o método de renderização. Portanto, o modelo será renderizado uma vez para cada item de dados e o resultado será a cadeia de caracteres concatenada.

O código a seguir demonstra como um único objeto de filme da matriz é renderizado para o movieTemplate (o código-fonte completo está disponível em 04-render-values.html):

my.vm = { movies: getMovies() };
$("#movieContainer").html($("#movieTemplate").render(my.vm.movies[1]));

A próxima seção demonstrará como o modelo poderia ser escrito para iterar em uma matriz também.

Drilling nos dados hierárquicos

Frequentemente, os modelos são usados para renderizar uma série de itens, que costumam conter dados aninhados e hierárquicos (gráficos de objeto). A Figura2 mostra como o JsRender pode iterar em uma série de dados usando a marca {{for}}. O parâmetro para a marca {{for}} pode ser uma matriz ou uma série de matrizes, que será iterada.

Figura 2 Noções básicas de iteração

Descrição Exemplo Saída
Loop em cada item de uma matriz, usando “for”

{{for cast}}

    <div>{{:stageName}}</div>

{{/for}}

Landon Papa

Ella Papa

Acesso ao contexto de dados usando #data {{for phone}}<div>{{:#data}}</div>{{/for}}

555-555-1212

555-555-1212

Os dados de exemplo do filme definem que cada filme tem uma matriz de estrelas de classificação, chamada RatingStars. A propriedade contém a classe CSS para exibir uma estrela de classificação para o filme. O código a seguir (de 05-for-data.html no exemplo de código) demonstra como iterar em cada item na matriz RatingStars e renderizar o nome de classe CSS usando a sintaxe {{:#data}}:

<ul>
  {{for ratingStars}}
  <li class="rating {{:#data}}"/>
  {{/for}}
</ul>

O token #data é uma palavra-chave do JsRender que representa o objeto iterado. Nesse caso, a matriz RatingStars contém uma matriz de cadeias de caracteres, portanto, #data representará uma cadeia de caracteres. A saída para esse exemplo é mostrada na Figura 3, na qual as estrelas de classificação são exibidas ao lado da imagem do filme.

Rendering an Object with and Without Encoding
Figura 3 Renderizando um objeto com ou sem codificação

Matrizes para modelos

Quando uma matriz é passada para um modelo, o modelo é renderizado uma vez para cada item na matriz. Um modelo é renderizado com base em um único item de dados, mas se incluir uma marca de modelo {{for}} com um parâmetro de matriz, a seção do modelo entre as marcas de abertura e de fechamento {{for}} e {{/for}} terá uma iteração adicional. O código a seguir passará um objeto my.vm que contém uma matriz de filmes para a função de renderização para exibir um item de lista para cada filme (o código-fonte completo pode ser encontrado no exemplo 06-if-else.html):

$("#movieList").html($("#movieTemplateMedium").render(my.vm));

O modelo será renderizado como conteúdo do elemento <ul> com a ID movieList:

 

<ul id="movieList"></ul>

A Figura 4 mostra que o modelo movie­TemplateMedium é iniciado pela renderização de <li>. Para cada filme na matriz, um <li> será renderizado no <ul> do contêiner.

O modelo na Figura 4 recebe o objeto my.vm como seu contexto e, em seguida, executa um loop na propriedade de matriz de filmes devido ao código {{for movies}}. Se o modelo receber a matriz my.vm.movies em vez de my.vm, o bloco {{for}} {{/for}} pode ser removido, pois o modelo seria executado para todos os itens na matriz.

Figura 4 Renderizando uma lista de itens e usando condicionais

{{for movies}}
  <li class="movieListItemMedium">
  <div class="caption">{{:name}}</div>
  {{if boxArt.smallUrl}}
    <img src="{{:boxArt.smallUrl}}"/>
  {{else}}
    <img src="../images/icon-nocover.png"/>
  {{/if}}
  <br/>
  <div class="text">Year Released: {{:releaseYear}}</div>
  <div class="text">rating: {{:rating}}</div>
  <ul>
    {{for ratingStars}}
      <li class="rating {{:#data}}"/>
      {{/for}}
  </ul>
  <br/>
  <div class="text">${{:salePrice}}</div>
  {{if fullPrice !== salePrice}}
    <div class="text highlightText">PRICED TO SELL!</div>
  {{/if}}
  </li>
{{/for}}

Percorrendo caminhos

Como vimos anteriormente, caminhos podem ser percorridos usando a sintaxe de ponto ou os colchetes. No entanto, você também pode voltar ao topo de uma hierarquia de objeto usando #parent ou identificar um elemento de matriz usando colchetes. Por exemplo, você pode substituir o código que exibe a marca img no modelo pelo seguinte:

<img src="{{:#parent.parent.data[2].boxArt.smallUrl}}"/>

O contexto para o modelo é um item na matriz my.vm.movies. Portanto, ao navegar para cima pelo principal duas vezes, o contexto se tornará o objeto my.vm object. Em seguida, você pode pegar o filme com o índice 2 e usar sua propriedade boxArt.smallUrl para a imagem. O resultado disso seria exibir o mesmo filme (o terceiro filme) para todos os filmes na matriz (como mostrado na Figura 5). Não é algo exatamente ideal nessa situação, mas serve para mostrar como percorrer objetos para cima e para baixo da hierarquia.

Traversing Paths to Display the Same Movie Image for Every Movie
Figura 5 Percorrendo caminhos para exibir a mesma imagem de filme para todos os filmes

Condicionais

A sintaxe do JsRender também oferece suporte para condicionais usando as marcas {{if}} e {{else}} (como mostrado na Figura 6). A marca {{if}} pode ser seguida de zero, uma ou mais marcas {{else}} e depois de uma marca de fechamento {{/if}}. O conteúdo do bloco entre a marca {{if}} e {{/if}} (ou até a primeira marca {{else}}, se houver) será renderizado na saída apenas se o valor da expressão em {{if}} for “truthy”.

Figura 6 Condicionais, expressões e operadores

Descrição Exemplo Saída
Criando um bloco if avaliado em uma comparação

{{if fullPrice !== salePrice}}

    <div class="text highlightText">

    PRICED TO SELL!</div>

{{/if}}

PRICED TO SELL!
Um bloco if/else

{{if qtyInStock >= 10}}

    In Stock

{{else qtyInStock}}

    Only {{:qtyInStock}} left!

{{else}}

    Not available.

{{/if}}

Only 5 left!

A marca {{else}} pode agir como um ifelse quando incluir uma expressão própria. Observe o segundo exemplo na Figura 6, que demonstra uma condicional com duas marcas else. Esse código avalia três condições em sequência.

O bloco que segue uma marca {{else someCondition}} será enviado se a condição for avaliada como true (ou, mais precisamente, truthy). Essa sintaxe pode ser usada efetivamente como uma instrução switch/case para avaliar um número n de condições.

As expressões usadas nas marcas JsRender podem ser expressões complexas usando operadores de avaliação, incluindo caminhos, cadeias de caracteres, números, chamadas de função e operadores de comparação, como ===, !== e <=. Esse é o caso para as expressões avaliadas e renderizadas pela marca {{: }} tag e, aqui, para as expressões condicionais usadas nas marcas {{if}} e {{else}}.

Usando contadores e expressões condicionais complexas

Às vezes, é necessário exibir ou usar uma variável de contador em um modelo. Um exemplo desse tipo de cenário é quando você deseja exibir um número de linha no modelo. Outro exemplo desse tipo de cenário pode ser quando você deseja atribuir uma ID exclusiva a cada elemento em um modelo para que possa referenciá-lo mais tarde. Por exemplo, você itera em uma lista de filmes e insere todos eles em uma marca <div>. Você pode atribuir a ID da marca div para ser movieDiv1, movieDiv2 e assim por diante. Em seguida, você pode encontrar o elemento por meio de sua ID quando necessário, talvez para associá-lo a um manipulador de eventos usando a função de delegação do jQuery. Para esses dois cenários, você pode usar a palavra-chave do JsRender #index (que começa no 0). Como mostrado na Figura 7, #index pode ser exibida em um modelo muito facilmente.

Figura 7 Contadores e condicionais em várias expressões

Descrição Exemplo Saída
Usando um índice para contar dentro de um modelo

{{for movies}}

<div class="caption">

    {{:#index}}: {{:name}}

</div>

{{/for}}

3: The Princess Bride
Usando operadores lógicos

{{if stars.length || cast.length}}

    <div class="text">Full Cast:</div>

{{/if}}

Full Cast:

Muitas vezes, uma única expressão pode não ser adequada. O JsRender oferece suporte a várias expressões usando operadores lógicos, como || e && (“or” e “and”, respectivamente). Isso facilita a combinação de várias expressões quando você deseja avaliar qualquer uma delas como true.

Iterar várias matrizes juntas

A marca {{for}} também pode iterar em várias matrizes de uma vez. Isso é útil quando há duas matrizes que precisam ser percorridas juntas. Por exemplo, o código a seguir demonstra como a marca for irá iterar nas matrizes Stars e Cast juntas:

{{for stars cast}}
  <div class="text">{{:name}}</div>
{{/for}}

Se houver dois itens na matriz Stars e três na matriz Cast, o conteúdo dos cinco itens será renderizado.

Essa é uma boa situação para usar a palavra-chave #data também, se os itens nas matrizes não forem objetos (com propriedades), mas valores de cadeia de caracteres simples. Por exemplo, as matrizes podem ser as matrizes de cadeia de caracteres MobilePhones e HomePhones, e talvez você deseje listá-las em uma única lista.

Modelos aninhados

Os aplicativos frequentemente recuperam gráficos de objeto em que os objetos têm propriedades cujos valores são matrizes de objetos que também podem conter outras matrizes de objetos. Esses cenários podem rapidamente se tornar difíceis de gerenciar ao aninhar várias instruções {{for}}. Considere que um objeto de cliente pode conter pedidos, que têm itens de pedido, que têm um produto que, por sua vez, tem depósitos. Esses dados hierárquicos podem ser renderizados usando várias marcas {{for}}. Essa é uma ótima situação em que o conteúdo de um bloco {{for}} pode ser empacotado como um modelo separado e renderizado como um modelo aninhado. Isso pode reduzir o código e torná-lo mais fácil de ler e manter, além de permitir a reutilização e a modularidade dos modelos.

Você pode usar um modelo aninhado com uma marca {{for}} ao remover o conteúdo do bloco (fazendo com que se torne de fechamento automático) e especificar um modelo externo usando o parâmetro tmpl. Por exemplo, você pode definir o parâmetro tmpl para ser o nome do modelo nomeado (registrado usando $.templates) ou pode usar um seletor do jQuery para um modelo declarado em um bloco de scripts. A sintaxe a seguir usa o modelo nomeado myTmpl para cada item na matriz nomeada myArray:

{{for myArray tmpl="myTmpl"/}}

O trecho de código a seguir usará o modelo com a ID de movieTemplateMedium para cada item na matriz de filmes (do exemplo 04-tmpl-combo-iterators.html):

<ul>
  {{for movies
    tmpl="#movieTemplateMedium"/}}
</ul>

Você pode iterar em várias matrizes e aplicar um modelo a elas também. Por exemplo, o trecho de código a seguir usará castTemplate para cada item nas propriedades de Stars e Cast (que são matrizes do exemplo 07-tmpl-combo-iterators.html):

<ul>
  {{for stars cast tmpl="#castTemplate"/}}
</ul>

Observe que em cada um dos trechos de código anteriores a marca {{for}} é uma marca de fechamento automático, em vez de uma marca de bloco que tem conteúdo. Em vez de iterar no conteúdo do bloco, delega a iteração para o modelo aninhado indicado pela propriedade tmpl e renderiza esse modelo uma vez para cada item na matriz. 

A marca de modelo {{if}} também pode ser usada como uma marca de fechamento automático referenciando outro modelo para seu conteúdo, como a marca {{for}}. Por exemplo, o código a seguir renderiza o modelo nomeado myAddressTmpl se o endereço for truthy:

{{if address tmpl="myAddressTmpl"/}}

Os exemplos disponíveis para download demonstram todos os recursos discutidos neste artigo. A Figura 8 mostra a lista de filmes renderizados usando todos esses recursos do arquivo do exemplo 07-tmpl-­combo-iterators.html.

Putting It All Together
Figura 8 Unindo todos os elementos

Mais nos bastidores

Esta coluna demonstra os recursos básicos do JsRender, mas há muito mais nos bastidores. Por exemplo, embora as marcas condicionais possam conter várias marcas {{for}} com condições (como uma instrução switch/case), pode haver formas mais claras de lidar com essa situação, como o uso de marcas personalizadas. O JsRender oferece suporte a marcas personalizadas para lógica complexa, funções auxiliares, navegação para cima do gráfico do objeto usando caminhos, funções de conversor de clientes, permissão de código JavaScript nos modelos, encapsulamento dos modelos e muito mais. Explorarei algumas dessas técnicas na próxima edição.

John Papa é ex-divulgador da Microsoft nas equipes do Silverlight e do Windows 8, onde apresentava o popular programa de TV do Silverlight. Ele se apresentou globalmente em palestras e sessões de eventos de BUILD, MIX, PDC, TechEd, Visual Studio Live! e DevConnections. Papa também é diretor regional da Microsoft, um colunista da Visual Studio Magazine (Papa’s Perspective) e autor de vídeos de treinamento com a Pluralsight. Siga-o no Twitter em twitter.com/john_papa.

Agradecemos ao seguinte especialista técnico pela revisão deste artigo: Boris Moore