Condividi tramite


Il presente articolo è stato tradotto automaticamente.

Cliente Insight

Funzionalità avanzate per la creazione di modelli JsRender

John Papa

Scaricare il codice di esempio

John PapaI modelli sono potenti, ma a volte bisogno di più le caratteristiche standard di che un motore modello fornisce out of the box. Potrebbe voler convertire dati, definire una funzione di supporto personalizzato o creare il proprio tag. La buona notizia è che è possibile utilizzare le funzionalità di base di JsRender per fare tutto questo e molto altro.

Mio articolo di aprile (msdn.microsoft.com/magazine/hh882454) esplorato le caratteristiche fondamentali della biblioteca ai modelli JsRender. Questa colonna continua l'esplorazione di JsRender in più scenari quali modelli esterni, cambiando il contesto con il {{di}} di rendering tag e utilizzando espressioni complesse. Illustrerò inoltre come utilizzare alcune delle funzioni più potenti JsRender, compresa la creazione di tag personalizzati, convertitori e aiutanti di contesto e permettendo il codice personalizzato. Tutti gli esempi di codice che possono essere scaricati da archive.msdn.microsoft.com/mag201205ClientInsight, e JsRender può essere scaricato da bit.ly/ywSoNu.

{{di}} Variazioni

Ci sono diversi modi il {{di}} tag può essere la soluzione ideale. Nel mio articolo precedente ho dimostrato come il {{di}} tag può aiutare a scorrere matrici utilizzando un blocco e come può scorrere più oggetti contemporaneamente:

    

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

Il {{di}} (o qualsiasi blocco tag) può essere convertito da un tag di blocco (con contenuto) a un tag di chiusura automatica sostituendo il contenuto di blocco con un modello esterno, che si posiziona in modo dichiarativo come proprietà tmpl. Il tag quindi esegue il rendering del modello esterno al posto di contenuto inline.

Ciò rende più semplice di adottare un approccio modulare a modelli dove puoi riutilizzare codice modello in luoghi diversi e organizzare e comporre i modelli:

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

Dati sono raramente pianeggiante, motivo per cui immersioni dentro e fuori le gerarchie di oggetti è una caratteristica importante per i modelli. Ho dimostrato le tecniche fondamentali a tuffarsi in una gerarchia di oggetti nel mio articolo precedente, utilizzando la notazione con punto e quadre, ma è anche possibile utilizzare il {{di}} tag per contribuire a ridurre il codice. Questo diventa più evidente quando si dispone di una struttura oggetto dove stai immersioni in una gerarchia di oggetti e hanno bisogno di eseguire il rendering di un insieme di proprietà da un oggetto figlio. Ad esempio, durante il rendering di una persona indirizzo dell'oggetto, si potrebbe scrivere il modello nel modo seguente, dove il termine "indirizzo" nel percorso viene ripetuto più volte:

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

Il {{di}} può rendere il codice per il rendering di un indirizzo molto più semplice eliminando la necessità di ripetere l'oggetto di indirizzo, come illustrato di seguito:

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

Il {{di}} è operativo sulla proprietà indirizzo, che è un singolo oggetto con proprietà, non una matrice di oggetti. Se l'indirizzo è truthy (esso contiene un valore non-falsey), il contenuto del {{di}} blocco verrà eseguito il rendering. Il {{di}} inoltre cambia il contesto dati corrente dall'oggetto persona all'oggetto indirizzo; così si comporta come un comando "con" che hanno molte librerie e lingue. Così, nell'esempio precedente, il {{di}} tag cambia il contesto dati all'indirizzo e rende il contenuto dei modelli di una volta (perché c'è un solo indirizzo). Se la persona non ha un indirizzo (la proprietà address è null o non definito), il contenuto non essere reso a tutti. Questo rende il {{di}} bloccare grande per contenere i modelli che devono essere visualizzati solo in determinate circostanze. In questo esempio (dal file del download codice 08 per variations.html) dimostra come l'esempio utilizza il {{di}} per visualizzare informazioni sui prezzi, se esistente:

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

Modelli esterni

Il riutilizzo del codice è uno dei grandi vantaggi dell'utilizzo di modelli. Se un modello è definito all'interno di un <script> Tag nella stessa pagina che ha usato, allora il modello non è come riutilizzabile come potrebbe essere. I modelli che devono essere accessibili da più pagine possono essere creati nei propri file ed estratto come necessario. JavaScript e jQuery rendono facile per recuperare il modello da un file esterno e JsRender lo rende facile per renderlo.

Una convenzione che mi piace usare con i modelli esterni consiste nell'anteporre il nome del file con un carattere di sottolineatura, che è una convenzione di denominazione comune per una vista parziale. Anche io preferisco per indicare tutti i file di modello con il suffisso. tmpl.html. La .tmpl indica che è un modello e l'estensione. html semplicemente rende più facile per gli strumenti di sviluppo come Visual Studio per riconoscere che il modello contiene HTML. Figura 1 di seguito viene illustrato il rendering di un modello di esterno.

Figura 1 codice per il Rendering di un modello di esterno

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
    };
})()

Un modo per recuperare il modello da un file esterno è di scrivere una funzione di utilità che può chiamare il codice JavaScript in un'applicazione Web. Si noti che nel Figura 1che la funzione di renderExternalTemplate sull'oggetto my.utils prima recupera il modello utilizzando la funzione di .get $. Quando la chiamata viene completata, il modello JsRender viene creato utilizzando la funzione .templates $ dal contenuto della risposta. Infine, il modello viene eseguito utilizzando la funzione di rendering del modello e il codice HTML risultante viene visualizzato nella destinazione. Questo codice potrebbe essere chiamato utilizzando il seguente codice dove il nome del modello, il target di DOM e il contesto dati vengono passati alla funzione renderExternalTemplates personalizzato:

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

Il modello esterno per questo esempio è nella _medMo­vie.tm­pl.html file di esempio e contiene solo i tag HTML e JsRender. Esso non è avvolto con un <script> Tag. Io preferisco questa tecnica per i modelli esterni, perché l'ambiente di sviluppo riconosce che i contenuti sono HTML, che rende la scrittura del codice meno inclini errore perché IntelliSense funziona out of the box. Tuttavia, il file potrebbe contenere più modelli, con ogni modello di essere avvolto in un <script> etichetta e dato un id ad identificare in modo univoco. Questo è solo un altro modo di gestire modelli esterni. Il risultato finale è mostrato in Figura 2.

The Result of Rendering an External Template
Nella figura 2 il risultato del Rendering di un modello di esterno

Visualizzazione percorsi

JsRender fornisce diversi percorsi di visualizzazione speciale che lo rendono facile da accedere all'oggetto di visualizzazione corrente. #View consente di accedere alla visualizzazione corrente, #data consente di accedere al contesto di dati corrente per la vista, #parent la gerarchia di oggetti e #index restituisce una proprietà index:

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

Quando si utilizzano i percorsi di vista (oltre #view), essi sono operanti su visualizzazione corrente già. In altre parole, i seguenti sono equivalenti:

#data
#view.data

I percorsi di vista sono utili durante la navigazione di gerarchie di oggetti quali clienti con ordini con dettagli dell'ordine o film nei magazzini in posizioni di archiviazione (come mostrato nel codice il download sample file 11-vista-paths.html).

Espressioni

Le espressioni comuni sono una parte essenziale della logica e possono essere utile al momento di decidere come eseguire il rendering di un modello. JsRender fornisce il supporto per le espressioni comuni, tra cui (ma non limitati a) quelle mostrate Figura 3.

Figura 3 espressioni comuni in JsRender

Expression Esempio Commenti
+ {{ :a + b }} Aggiunta
- {{ :a - b }} Sottrazione
* {{ :a * b }} Moltiplicazione
/ {{ :a / b }} Divisione
|| {{ :a || b }} Logica o
&& {{: un & & b}} Logica e
! {{ :!a }} Negazione
? : {{ :a === 1 ? b * 2: c * 2}} Espressione terziario
( ) {{ :(a||-1) + (b||-1) }} Ordinazione di valutazione utilizzando le parentesi
% {{ :a % b }} Operazione di modulo
< = e > = e < e > {{: un < = b}} Operazioni di confronto
= = = e! = = {{ :a === b }} Uguaglianza e la disuguaglianza

JsRender supporta la valutazione delle espressioni ma non assegnazione dell'espressione, né l'esecuzione di codice casuale. Questo impedisce espressioni che potrebbero altrimenti eseguono assegnazioni di variabili o eseguire operazioni come l'apertura di una finestra di avviso. L'intenzione di espressioni è di valutare un'espressione e sia il risultato, il rendering basato sul risultato di agire o utilizzare il risultato in un'altra operazione.

Ad esempio, eseguendo {{: un + +}} con JsRender si tradurrebbe in un errore perché tenta di incrementare la variabile. Inoltre, eseguendo {{: alert('hello')}} genera un errore perché tenta di chiamare una funzione, # view.data.alert, che non esiste.

Registrazione di Custom Tags

JsRender offre diversi punti di estendibilità potente come tag personalizzati, convertitori, funzioni di supporto e i parametri di template. La sintassi per la chiamata di ciascuno di questi è illustrata di seguito:

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

Ciascuno di questi serve diversi scopi; Tuttavia, essi possono sovrapporsi un po ' a seconda della situazione. Prima di mostrare come scegliere tra di loro, è importante capire che cosa ciascuno fa e come definirle.

Tag personalizzati sono ideale quando qualcosa deve essere sottoposto a rendering che ha caratteristiche di "controllo-come" e può essere autosufficienti. Ad esempio, voti stelle potrebbero essere reso semplicemente come una serie di dati, come questo:

{{:rating}}

Tuttavia, potrebbe essere meglio utilizzare la logica JavaScript per rendere i voti stelle usando CSS e una serie di immagini di stelle piene e vuote:

{{createStars averageRating max=5/}}

La logica per creare le stelle potrebbe (e dovrebbe) essere separata dalla presentazione. JsRender fornisce un modo per creare un tag personalizzato che avvolge questa funzionalità. Il codice in Figura 4 definisce un tag personalizzato denominato createStars e registra con JsRender, in modo che può essere utilizzato in qualsiasi pagina che carica questo script. Utilizzare questo tag personalizzato è necessario che il file JavaScript, jsrender.tag.js nell'esempio di codice è incluso nella pagina.

Figura 4 creando un Tag personalizzati

$.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;
  }

Tag personalizzati può avere proprietà dichiarativa come il max = 5 proprietà di {{createStars}} illustrato in precedenza. Essi sono accessibili nel codice attraverso this.props. Ad esempio, il seguente codice registra un tag personalizzato denominato sorta che accetta una matrice (se la proprietà denominata rovescio è impostata su true, {{ordinare la matrice inversa = true /}}, viene restituita la matrice in ordine inverso):

$.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;
}}

Una buona regola è di utilizzare un tag personalizzato quando è necessario eseguire il rendering di qualcosa di un po' più coinvolti (come un tag createStars o ordinamento) ed esso potrebbe essere riutilizzato. Tag personalizzati sono meno ideali per gli scenari di una tantum.

Convertitori

Mentre tag personalizzati sono ideali per la creazione di contenuti, convertitori meglio sono adatti per il semplice compito di conversione di un valore di origine su un valore diverso. Convertitori possono modificare i valori di origine (ad esempio, un valore booleano true o false) in qualcosa di completamente diverso (ad esempio il colore verde o rosso, rispettivamente). Ad esempio, il codice seguente utilizzerà il convertitore priceAlert per restituire una stringa contenente un avviso di prezzo sulla base del valore salePrice:

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

Convertitori sono grandi per cambiare gli URL troppo, come illustrato di seguito:

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

Nell'esempio seguente il convertitore ensureUrl deve convertire il valore di boxArt.smallUrl in un URL completo (entrambi questi convertitori vengono utilizzati nel file 12-converters.html e vengono registrati nel jsrender.helpers.js utilizzando il JsRender $. views.converters funzione):

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

Convertitori sono destinati senza parametri di conversione dei dati su un valore di rendering. Se lo scenario richiede parametri, quindi una funzione di supporto o di un tag personalizzato è più adatto di un convertitore. Come abbiamo visto in precedenza, tag personalizzati consentono i parametri denominati, quindi il tag createStars potrebbe avere parametri per definire le dimensioni delle stelle, i loro colori, classi CSS da applicare a loro, e così via. Qui il punto chiave è che convertitori sono per conversioni semplici, mentre i tag personalizzati sono per il rendering di chiavi in mano più coinvolti.

Funzioni di supporto e i parametri di Template

È possibile passare in funzioni di supporto o i parametri per l'uso durante il rendering di modello in un paio di modi. Uno è quello di registrarli utilizzando $. views.helpers, in modo analogo alla registrazione tag o convertitori:

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

Questo li renderà disponibile a tutti i modelli dell'applicazione. Un altro modo è quello di passarli come opzioni nella chiamata per eseguire il rendering:

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

Questo codice rende disponibili solo nel contesto di quel particolare chiamata modello di rendering. Ad ogni modo, gli helper possono essere letta all'interno del modello anteponendo il nome di parametro o funzione (o il percorso) con "~":

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

Funzioni di supporto possono fare quasi nulla, compresi i dati di convertire, eseguire calcoli, eseguire logica applicazione, restituiscono oggetti o matrici o anche restituire un modello.

Ad esempio, una funzione di supporto denominata getGuitars può essere creata per cercare attraverso una serie di prodotti e trovare tutti i prodotti di chitarra. Potrebbe anche accettare un parametro per il tipo di chitarra. Il risultato potrebbe essere utilizzato per eseguire il rendering di un singolo valore o per scorrere la matrice risultante (perché funzioni di supporto possono restituire qualsiasi cosa). Il codice seguente potrebbe ottenere una matrice di tutti i prodotti che sono chitarre acustiche e scorrere su di loro usando un {{di}} blocco:

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

Funzioni di supporto possono chiamare anche altre funzioni di supporto, come il calcolo totale prezzo utilizzando una matrice di elementi di un ordine linea e applicando i tassi di sconto e aliquote fiscali:

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

Funzioni di supporto che sono accessibili a più modelli sono definite passando un oggetto letterale che contiene le funzioni di supporto nella JsRender $. views.helpers funzione. Nell'esempio seguente, la funzione concat è definita per concatenare più argomenti:

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

La funzione di supporto concat può essere richiamata tramite {{: ~ concat (in primo luogo, età, ultima)}}. Supponendo che i valori per prima, medio e l'ultimo sono accessibili e sono John, 25 e Doe, rispettivamente, il valore John25Doe dovrebbe essere sottoposto a rendering.

Funzioni di supporto per gli scenari unici

Potrebbe incorrere in una situazione in cui si desidera utilizzare una funzione di supporto per un modello specifico, ma non riutilizzarlo in altri modelli. Ad esempio, un modello di carrello spesa potrebbe richiedere un calcolo che è unico per quel modello. Una funzione di supporto potrebbe eseguire il calcolo, ma non c'è alcuna necessità di rendere accessibile a tutti i modelli. JsRender supporta questo scenario con il secondo approccio accennato in precedenza, passando la funzione in con le opzioni in una chiamata di rendering:

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

In questo caso il modello carrello acquisti è il rendering e le funzioni di supporto e i parametri di modello che ha bisogno per il suo calcolo vengono forniti direttamente con la chiamata di rendering. La chiave qui è che, in questo caso, la funzione di supporto esiste solo durante il rendering di questo modello specifico.

Quale usare?

JsRender offre diverse opzioni per creare potenti modelli con convertitori, tag personalizzati e funzioni di supporto, ma è importante sapere in cui deve essere utilizzato ogni scenario. Una buona regola è quello di utilizzare la struttura decisionale mostrato in Figura 5, che delinea come decidere quale di queste funzionalità da utilizzare.

Figura 5 struttura decisionale per scegliere il giusto supporto

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 la funzione è solo per essere utilizzato una sola volta, c'è necessario creare il sovraccarico di renderlo accessibile per tutta l'intera applicazione. Questa è la situazione ideale per un "one time" funzione di supporto che viene passata quando necessario.

Consentire di codice

Situazioni potrebbero sorgere dove è più facile scrivere codice personalizzato all'interno del modello. JsRender consente di incorporare codice, ma consigliamo di che farlo solo quando tutto il resto fallisce come il codice possono essere difficile da gestire perché mescola di presentazione e comportamento.

Codice può essere incorporati all'interno di un modello racchiudendo il codice con un blocco preceduto da un asterisco {*} e impostazione allowCode su true. Ad esempio, il modello denominato myTmpl (mostrato in Figura 6) incorpora il codice per valutare i luoghi appropriati per il rendering di un comando o la parola "e" in una serie di lingue. L'esempio completo può essere trovato nel file 13-allowcode.html. La logica non è complicato, ma il codice può essere difficile da leggere nel modello.

JsRender non permetterà il codice deve essere eseguito a meno che la proprietà allowCode è impostata su true (il valore predefinito è falsa). Il codice seguente definisce il modello compilato denominato movieTmpl, viene assegnato il markup da tag di script mostrato in Figura 6 e indica che debba allowCode nel modello:

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

Una volta creato il modello, quindi viene visualizzato. La caratteristica di allowCode può portare al codice che è difficile da leggere, e in alcuni casi una funzione di supporto può fare il lavoro. Per esempio, l'esempio in Figura 6 utilizza la funzionalità di allowCode di JsRender per aggiungere le virgole e la parola "e" dove necessario. Tuttavia, questo può essere effettuato anche mediante la creazione di una funzione di supporto:

$.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;
  }
})

Figura 6 consentendo il codice in un modello

<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>

Questa funzione di supporto languagesSeparator viene chiamata anteponendo il suo nome con "~." Questo rende il codice del modello che chiama il metodo di supporto molto più facile da leggere, come illustrato di seguito:

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

Muovendo la logica per una funzione di supporto rimosso il comportamento dal modello e si è trasferito in JavaScript, che segue modelli buona separazione.

Prestazioni e flessibilità

JsRender offrono una varietà di caratteristiche che vanno ben di là di valori di proprietà di rendering, incluso il supporto per espressioni complesse, l'iterazione e mutevole contesto utilizzando il {{di}} tag e visualizzazione percorsi per lo spostamento di contesto. Fornisce, inoltre, i mezzi per estendere le sue caratteristiche con l'aggiunta di tag personalizzati, convertitori e aiutanti come necessario. Queste caratteristiche e l'approccio basato su stringa puro alla creazione modelli aiutare JsRender beneficiare di grandi prestazioni e lo rendono molto flessibile.

John Papa è un ex evangelist di Microsoft su Silverlight e Windows 8 squadre, dove ha ospitato il popolare show "Silverlight TV". Egli ha presentato a livello globale al keynote e sessioni per costruire, MIX, PDC, TechEd, Visual Studio Live! ed eventi DevConnections. Papa è anche Microsoft Regional Director, un editorialista per Visual Studio Magazine (di Papa Perspective) e autore di video di formazione con Pluralsight. Seguirlo su Twitter a twitter.com/john_papa.

Grazie all'esperto tecnica seguente per la revisione di questo articolo: Boris Moore