Este artigo foi traduzido por máquina.

Visão do cliente

Recursos de modelos avançados do JsRender

John Papa

Baixar o exemplo de código

John PapaOs modelos são poderosos, mas às vezes você precisa mais do que os recursos padrão que fornece um mecanismo de modelagem da caixa. Você pode querer converter dados, definir uma função auxiliar personalizados ou criar sua própria marca. A boa notícia é que você pode usar os principais recursos do JsRender para fazer tudo isso e muito mais.

Minha coluna de Abril (msdn.microsoft.com/magazine/hh882454) exploraram as características fundamentais da biblioteca de templates do JsRender. Esta coluna continua a exploração do JsRender em mais cenários, como o processamento de modelos externos, mudando o contexto com a {{para}} marca e usando expressões complexas. Também demonstrarei como usar alguns dos recursos mais poderosos JsRender, incluindo criar tags personalizadas, conversores e ajudantes de contexto e permitir que o código personalizado. Todas as amostras de código podem ser descarregadas a partir archive.msdn.microsoft.com/mag201205ClientInsight, e JsRender pode ser baixado em bit.ly/ywSoNu.

{{para}} Variações

Existem várias maneiras do {{para}} marca pode ser uma solução ideal. Na minha coluna anterior demonstrei como a {{para}} marca pode ajudar iterar arrays usando um bloco e como pode iterar por meio de vários objetos ao mesmo tempo:

    

<!-- looping {{for}} -->
{{for students}}
{{/for}}           
<!--  combo iterators {{for}} -->
{{for teachers students staff}}
{{/for}}

O {{para}} (ou qualquer marca de bloquear) pode ser convertida de uma tag bloco (com conteúdo) a uma tag de fecho automático, substituindo o conteúdo do bloco com um modelo externo, que você aponte para declarativamente como uma propriedade de tmpl. A marca, em seguida, irá processar o modelo externo no lugar do conteúdo em linha.

Isso facilita uma abordagem modular para modelos onde você pode reutilizar markup de modelo em diferentes lugares e organizar e compor modelos:

<!--  self closing {{for}} -->
{{for lineItems tmpl="#lineItemsDetailTmpl" /}}

Dados são raramente planos, razão pela qual mergulho dentro e fora de hierarquias de objeto é um recurso importante para os modelos. Demonstrei técnicas de núcleo para mergulhar em uma hierarquia de objetos na minha coluna anterior usando notação de ponto e colchetes, mas você também pode usar a {{para}} marca para ajudar a reduzir o código. Isso fica mais evidente quando você possui uma estrutura de objeto onde você está mergulhando em uma hierarquia de objetos e precisa processar um conjunto de propriedades de um objeto filho. Por exemplo, ao processar uma pessoa endereço de um objeto, você pode escrever o modelo da seguinte forma, onde o termo "endereço" no caminho é repetido várias vezes:

    <div>{{:address.street1}}</div>
    <div>{{:address.street2}}</div>
    <div>{{:address.city}}, {{:address.state}} {{:address.postalCode}}</div>

O {{para}} pode tornar o código para processar um endereço muito mais simples, eliminando a necessidade de repetir o objeto do endereço, conforme mostrado aqui:

    <!--  "with" {{for}} -->
    {{for address}}
      <div>{{:street1}}</div>
      <div>{{:street2}}</div>
      <div>{{:city}}, {{:state}} {{:postalCode}}</div>
    {{/for}}

O {{para}} está a funcionar com a propriedade de endereço, que é um simples objeto com propriedades, não uma matriz de objetos. Se o endereço for truthy (ele contém algum valor não falsey), o conteúdo da {{para}} bloco será processado. O {{para}} também altera o contexto de dados atual do objeto person para o objeto de endereço; Assim, ele atua como um comando "com" que têm muitos idiomas e bibliotecas. Portanto, o exemplo anterior, o {{para}} marca muda o contexto de dados para o endereço e, em seguida, processa o conteúdo dos modelos de uma vez (porque há apenas um endereço). Se a pessoa não tiver um endereço (a propriedade address é nulo ou não definido), o conteúdo não vai ser processado em tudo. Isso torna o {{para}} bloquear grande que contenha modelos que só devem ser exibidos em determinadas circunstâncias. Este exemplo (do arquivo 08 para variations.html no download de código que acompanha) demonstra como a amostra usa o {{para}} para exibir informações tarifárias se ela existir:

    {{for pricing}}
      <div class="text">${{:salePrice}}</div>
      {{if fullPrice !== salePrice}}
        <div class="text highlightText">PRICED TO SELL!</div>
      {{/if}}
    {{/for}}

Modelos externos

Reutilização de código é uma das grandes vantagens do uso de modelos. Se um modelo é definido dentro de um <script> marca na mesma página que ele tem usado, em seguida, o modelo não é tão reutilizável como poderia ser. Modelos que devem ser acessíveis a partir de várias páginas podem ser criados em seus próprios arquivos e recuperados conforme necessário. JavaScript e jQuery facilitam recuperar o modelo de um arquivo externo e JsRender facilita a processá-lo.

Uma Convenção que eu gosto de usar com modelos externos é como prefixo o nome do arquivo com um sublinhado, que é uma Convenção de nomenclatura comum para modos de exibição parciais. Eu também prefiro sufixo todos os arquivos de modelo com. tmpl.html. O .tmpl denota que é um modelo e a extensão. html simplesmente torna mais fácil para ferramentas de desenvolvimento como Visual Studio para reconhecer que o modelo contém HTML. Figura 1 mostra o processamento de um modelo externo.

Figura 1 código para renderizar um modelo externo

my.utils = (function () {
  var
    formatTemplatePath = function (name) {
      return "/templates/_" + name + ".tmpl.html";
    },
    renderTemplate = function (tmplName, targetSelector, data) {
      var file = formatTemplatePath(tmplName);
      $.get(file, null, function (template) {
        var tmpl = $.templates(template);
        var htmlString = tmpl.render(data);
        if (targetSelector) {
          $(targetSelector).html(htmlString);
        }
        return htmlString;
          });
        };
    return {
      formatTemplatePath: formatTemplatePath,
        renderExternalTemplate: renderTemplate
    };
})()

Uma maneira de recuperar o modelo de um arquivo externo é escrever uma função de utilitário que pode chamar o JavaScript em um aplicativo da Web. Observe na Figura 1que a função de renderExternalTemplate no objeto my.utils primeiro recupera o modelo usando a função de obter $. Quando a chamada for concluída, o modelo de JsRender é criado usando a função de .templates $ do conteúdo da resposta. Finalmente, o modelo é processado usando a função de renderização do modelo e o HTML resultante é exibido no destino. Esse código pode ser chamado usando o seguinte código onde o nome do modelo, o destino de DOM e o contexto de dados são passados para a função personalizada renderExternalTemplates:

my.utils.renderExternalTemplate("medMovie", "#movieContainer", my.vm);

O modelo externo para esse exemplo é o _medMo­vie.tm­pl.html exemplo de arquivo e contém apenas as tags HTML e JsRender. Ele não é empacotado com um <script> marca. Eu prefiro essa técnica para modelos externos porque o ambiente de desenvolvimento irá reconhecer que o conteúdo é HTML, o que torna a escrever o código menos sujeito a erros, como o IntelliSense funciona fora da caixa. No entanto, o arquivo pode conter vários modelos, com cada modelo sendo envolto em um <script> marca e dado um id para identificá-lo exclusivamente. Esta é apenas uma outra maneira de lidar com modelos externos. O resultado final é mostrado na Figura 2.

The Result of Rendering an External Template
Figura 2 O resultado do processamento de um modelo externo

Exibir caminhos

JsRender fornece vários caminhos de modo de exibição especial que tornam mais fácil para acessar o objeto de modo de exibição atual. #View fornece acesso ao modo de exibição atual, #data fornece acessar para o atual contexto de dados para o modo de exibição, #parent percorre a hierarquia de objetos e sendo retorna uma propriedade de índice:

    <div>{{:#data.section}}</div>
    <div>{{:#parent.parent.data.
    number}}</div>
    <div>{{:#parent.parent.parent.parent.data.
    name}}</div>
    <div>{{:#view.data.section}}</div>

Ao usar os caminhos de exibição (não #view), eles estão operando em modo de exibição atual já. Em outras palavras, a seguir é equivalentes:

#data
#view.data

Os caminhos de modo de exibição são úteis quando se navega hierarquias de objeto como clientes com pedidos com detalhes do pedido, ou filmes em armazéns em locais de armazenamento (como mostra o código baixar amostra arquivo 11-vista-paths.html).

Expressões

Expressões comuns são uma parte essencial da lógica e podem ser útil ao decidir como processar um modelo. JsRender fornece suporte para expressões comuns, incluindo (mas não limitado a) as indicadas no Figura 3.

Figura 3 expressões comuns em JsRender

Expressão Exemplo Comentários
+ {{ :a + b }} Adição
- {{ :a - b }} Subtração
* {{ :a * b }} Multiplicação
/ {{ :a / b }} Divisão
|| {{ :a || b }} Lógica ou
&& {{: um & & b}} Lógica e
! {{ :!a }} Negação
? : {{ :a === 1 ? b * 2: c * 2}} Expressão terciária
( ) {{ :(a||-1) + (b||-1) }} Ordenação avaliação usando parênteses
% {{ :a % b }} Operação do módulo de elasticidade
< = e > = e < e > {{: um < = b}} Operações de comparação
= = = e! = = {{ :a === b }} Igualdade e desigualdade

JsRender oferece suporte a avaliação da expressão, mas não a atribuição da expressão, nem a execução de código aleatório. Isso impede que expressões que caso contrário poderiam realizar atribuições de variável ou executar operações como abrir uma janela de alerta. A intenção de expressões é para avaliar uma expressão e quer processar o resultado, agir com base no resultado ou usar o resultado em outra operação.

Por exemplo, realizando {{: um + +}} com JsRender resultaria em um erro porque ele tenta incrementam a uma variável. Também, realizando {{: alert('hello')}} resulta em um erro, porque ele tenta chamar uma função, # view.data.alert, que não existe.

Registrando marcas personalizadas

JsRender oferece vários pontos de extensibilidade poderosa como tags personalizadas, conversores, funções auxiliares e parâmetros de modelo. A sintaxe para chamar cada um destes é mostrada aqui:

{{myConverter:name}}
{{myTag name}}
{{:~myHelper(name)}}
{{:~myParameter}}

Cada uma delas tem finalidades diferentes; no entanto, eles podem se sobrepor um pouco dependendo da situação. Antes de mostrar como escolher entre eles, é importante entender o que cada um faz e como defini-los.

Tags personalizadas são ideais quando algo precisa ser renderizado que tem "controle" características e podem ser independente. Por exemplo, classificações poderiam ser renderizadas simplesmente como um número usando dados, como este:

{{:rating}}

No entanto, talvez seja melhor usar JavaScript lógica para processar as classificações usando CSS e uma série de imagens de estrelas vazias e preenchidas:

{{createStars averageRating max=5/}}

A lógica para criar as estrelas pode (e deve) ser separada da apresentação. JsRender fornece uma maneira para criar uma marca personalizada que envolve essa funcionalidade. O código no Figura 4 define uma marca personalizada chamada createStars e registra-lo com JsRender, portanto, ele pode ser usado em qualquer página que carrega esse script. Usar esta tag personalizada requer que seu arquivo JavaScript, jsrender.tag.js no código de exemplo, é incluído na página.

Figura 4 Criando uma marca personalizada

$.views.tags({
  createStars: function (rating) {
    var ratingArray = [], defaultMax = 5;
    var max = this.props.max || defaultMax;
    for (var i = 1; i <= max; i++) {
      ratingArray.push(i <= rating ? 
        "
rating fullStar" : "rating emptyStar");
    }
    var htmlString = "";
    if (this.tmpl) {
      // Use the content or the template passed in with the template property.
htmlString = this.
renderContent(ratingArray);
    } else {
        // Use the compiled named template.
htmlString = $.render.compiledRatingTmpl(ratingArray);
    }
    return htmlString;
  }

Tags personalizados podem ter propriedades declarativas como o max = 5 propriedade de {{createStars}} mostrado anteriormente. Eles são acessados no código através do this.props. Por exemplo, o código a seguir registra uma marca personalizada denominada classificação que aceita uma matriz (se a propriedade chamada reversa é definida como true, {{classificar a matriz inversa = true /}}, a matriz é retornada em ordem inversa):

$.views.tags({
sort: function(array){
  var ret = "";
  if (this.props.reverse) {
    for (var i = array.length; i; i--) {
      ret += this.tmpl.render(array[i - 1]);
    }
  } else {
      ret += this.tmpl.render(array);
  }
  return ret;
}}

Uma boa regra é usar uma marca personalizada quando você precisa processar algo um pouco mais complexo (como uma marca de classificação ou createStars) e pode ser reutilizado. Tags personalizadas são menos ideais para situações pontuais.

Conversores

Enquanto marcas personalizadas são ideais para criação de conteúdo, conversores são mais adequados para a tarefa simple de converter um valor de origem para um valor diferente. Conversores podem alterar valores de origem (como um valor booleano de true ou false) para algo completamente diferente (como a cor verde ou vermelho, respectivamente). Por exemplo, o código a seguir usará o conversor priceAlert para retornar uma Cadeia de caracteres contendo um alerta do preço baseado no valor de salePrice:

    <div class="text highlightText">{{priceAlert:salePrice}}</div>

Conversores são grandes para alterar URLs também, como mostrado aqui:

    <img src="{{ensureUrl:boxArt.smallUrl}}" class="rightAlign"/>

No exemplo a seguir o conversor ensureUrl deve converter o valor de boxArt.smallUrl para uma URL qualificada (ambos esses conversores são usados no arquivo 12-converters.html e são registrados em jsrender.helpers.js usando o $ JsRender. views.converters função):

$.views.converters({
  ensureUrl: function (value) {
    return (value ?
value : "/images/icon-nocover.png");
  },
  priceAlert: function (value) {0
    return (value < 10 ? "
1 Day Special!" : "Sale Price");
  }
});

Conversores são destinados não parametrizada conversão de dados para um valor renderizado. Se o cenário exige parâmetros, então uma função auxiliar ou uma marca personalizada é melhor adaptada do que um conversor. Como vimos anteriormente, tags personalizadas permitem para parâmetros nomeados, portanto, a marca de createStars poderia ter parâmetros para definir o tamanho das estrelas, suas cores, classes CSS para aplicar a eles e assim por diante. O ponto-chave aqui é que os conversores são para conversões simples, enquanto tags personalizadas são para renderização completa mais envolvida.

Funções do auxiliar e parâmetros de modelo

Você pode passar em parâmetros ou funções auxiliares para uso durante o processamento de modelo de duas maneiras. Um é para registrá-los usando $. views.helpers, de forma semelhante para registar marcas ou conversores:

$.views.helpers({
  todaysPrices: { unitPrice: 23.40 },
  extPrice:function(unitPrice, qty){
    return unitPrice * qty;
  }
});

Isso irá torná-los disponíveis para todos os modelos na aplicação. Outra maneira é passá-las em como opções na chamada para processar:

$.render.myTemplate( data, {
  todaysPrices: { unitPrice: 23.40 },
  extPrice:function(unitPrice, qty){
    return unitPrice * qty;
  }
});

Esse código torna disponíveis apenas no contexto do que chamar processamento de modelo específico. De qualquer maneira, os ajudantes podem ser acessados de dentro do modelo por prefixando o nome do parâmetro ou função (ou caminho) com "~":

{{: ~extPrice(~todaysPrices.unitPrice, qty) }}

Funções auxiliar podem fazer qualquer coisa, inclusive converter dados, realizar cálculos, execução da lógica de app, retornam arrays ou objetos ou até mesmo retornar um modelo.

Por exemplo, uma função de auxiliar chamada getGuitars poderia ser criada para pesquisa através de uma variedade de produtos e encontrar todos os produtos para guitarra. Ele também pode aceitar um parâmetro do tipo de guitarra. O resultado, em seguida, poderia ser usado para processar um único valor ou para iterar através da matriz resultante (porque funções auxiliar podem retornar nada). O código a seguir pode obter uma matriz de todos os produtos que são violões e iterar sobre eles usando um {{para}} bloco:

{{for ~getGuitars('acoustic')}} ...
{{/for}}

Funções do auxiliar também podem chamar outras funções auxiliares, tais como calcular um total usando uma matriz de itens de linha da ordem e aplicação das taxas de desconto dos preços e das taxas de imposto:

{{:~totalPrice(~extendedPrice(lineItems, discount), taxRate}}

Funções auxiliares que são acessíveis para vários modelos são definidas por passar um objeto literal que contém as funções do auxiliar para o $ JsRender. views.helpers função. No exemplo a seguir, é definida a função concat para concatenar Múltiplo argumentos:

$.views.helpers({
  concat:function concat() {
    return "".concat.apply( "", arguments );
  }
})

A função de auxiliar de concat pode ser chamada usando {{: ~ concat (da primeira, idade, última)}}. Supondo que os valores para o primeiro, último e médio estão acessíveis e são João, 25 e Doe, respectivamente, o valor John25Doe poderia ser processado.

Funções do auxiliar para cenários exclusivos

Você pode executar em uma situação onde você quer usar uma função auxiliar para um modelo específico, mas não reutilizá-lo em outros modelos. Por exemplo, um modelo de carrinho de compras pode exigir um cálculo que é exclusivo para esse modelo. Uma função auxiliar poderia realizar o cálculo, mas não é necessário para torná-lo acessível a todos os modelos. JsRender oferece suporte a esse cenário com a segunda abordagem mencionada anteriormente — passando a função de com as opções em uma chamada de processamento:

$.render.shoppingCartTemplate( data, {
  todaysPrices: { unitPrice: 23.40 },
  extPrice:function(unitPrice, qty){
    return unitPrice * qty;
  }
});

Neste caso o modelo de carrinho de compras é processado, e as funções auxiliares e parâmetros de modelo que ele precisa para o seu cálculo são fornecidos diretamente com a chamada de render. A chave aqui é que, neste caso, a função auxiliar só existe durante o processamento deste modelo específico.

Qual usar?

JsRender oferece várias opções para criar poderosos modelos com conversores, marcas personalizadas e funções do auxiliar, mas é importante saber em qual cenário cada deve ser usado. Uma boa regra é usar a árvore de decisão mostrada na Figura 5, que descreve como decidir quais desses recursos para usar.

Figura 5 árvore de decisão para escolher o direito auxiliar

if (youPlanToReuse) {
  if (simpleConversion && !parameters){
    // Register a converter.
}
  else if (itFeelsLikeAControl && canBeSelfContained){
    // Register a custom tag.
}
  else{
    // Register a helper function.
}
}
else {
  // Pass in a helper function with options for a template.
}

Se a função só deve ser usado uma vez, não é necessário para criar a sobrecarga de torná-lo acessível em todo o aplicativo. Esta é a situação ideal para um "one time" função auxiliar que é passada no quando necessário.

Permitir que o código

Podem surgir situações onde é mais fácil escrever código personalizado dentro do modelo. JsRender permite que o código ser incorporado, mas eu recomendo que você faça isso somente quando tudo mais falhar como o código pode ser difícil de manter porque ele mistura apresentação e comportamento.

Código pode ser incorporado dentro de um modelo por quebra automática o código com um bloco prefixado com um asterisco {*} e configuração allowCode como true. Por exemplo, o modelo chamado myTmpl (mostrado na Figura 6) incorpora código para avaliar os locais adequados para processar um comando ou a palavra "e" em uma série de línguas. O exemplo completo pode ser encontrado no arquivo 13-allowcode.html. A lógica não é tão complicado, mas o código pode ser difícil de ler no modelo.

JsRender não vai permitir que o código seja executado a menos que a propriedade allowCode é definida como true (o padrão é false). O código a seguir define o modelo compilado chamado movieTmpl, atribui a ele a marcação da tag script mostrado na Figura 6 e indica que ele deve allowCode no modelo:

$.templates("movieTmpl", {
  markup: "#myTmpl",
  allowCode: true
});
$("#movieRows").html(
  $.render.movieTmpl(my.vm.movies)
);

Uma vez que o modelo é criado, ele então é processado. O recurso de allowCode pode levar a código que é difícil de ler, e em alguns casos uma função auxiliar pode fazer o trabalho. Por exemplo, o exemplo em Figura 6 usa o recurso de allowCode de JsRender para adicionar vírgulas e a palavra "e" onde for necessário. No entanto, isso também poderia ser feito criando uma função auxiliar:

$.views.helpers({
  languagesSeparator: function () {
    var view = this;
    var text = "";
    if (view.index === view.parent.data.length - 2) {
      text = " and";
    } else if (view.index < view.parent.data.length - 2) {
      text = ",";
    }
    return text;
  }
})

A Figura 6, que permite código em um modelo

<script id="myTmpl" type="text/x-jsrender">
  <tr>
    <td>{{:name}}</td>
    <td>
      {{for languages}}
        {{:#data}}{{*
          if ( view.index === view.parent.data.length - 2 ) {
        }} and {{*
          } else if ( view.index < view.parent.data.length - 2 ) {
        }}, {{* } }}
      {{/for}}
    </td>
  </tr>
</script>

Esta função de auxiliar languagesSeparator é chamada por prefixar seu nome com "~." Isso torna o código do modelo que chama o auxiliar muito mais fácil de ler, como mostrado aqui:

{{for languages}}
  {{:#data}}{{:~languagesSeparator()}}
{{/for}}

Movendo-se a lógica para uma função auxiliar removido o comportamento do modelo e moveu-o em JavaScript, que segue padrões de boa separação.

Desempenho e flexibilidade

JsRender oferecem uma variedade de recursos que vão muito além de valores de propriedade de processamento, incluindo suporte para expressões complexas, iteração e a mudança de contexto usando o {{para}} marca e exibir caminhos para navegação em contexto. Ele também fornece os meios para estender seus recursos, adicionando marcas personalizadas, conversores e ajudantes conforme necessário. Esses recursos e a abordagem baseada em cadeia de caracteres de pura para modelagem ajudam JsRender beneficiar de excelente desempenho e torná-lo muito flexível.

John Papa é um antigo Evangelista para a Microsoft sobre os Silverlight e Windows 8 equipes, onde ele organizou o popular show "TV 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 de Visual Studio Magazine (ponto de vista do Papa) e autor de vídeos de treinamento com Pluralsight. Siga-o no Twitter em twitter.com/john_papa.

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