Modèle externe dans Underscore

121

J'utilise le modèle Underscore . Est-il possible de joindre un fichier externe comme modèle ?

Dans Backbone View, j'ai:

 textTemplate: _.template( $('#practice-text-template').html() ),

 initialize: function(){                                            
  this.words = new WordList;            
  this.index = 0;
  this.render();
 },

Dans mon html est:

<script id="practice-text-template" type="text/template">
   <h3>something code</h3>
</script>

Ça marche bien. Mais j'ai besoin d'un modèle externe . J'essaie:

<script id="practice-text-template" type="text/template" src="templates/tmp.js">

ou

textTemplate: _.template( $('#practice-text-template').load('templates/tmp.js') ),

ou

$('#practice-text-template').load('templates/tmp.js', function(data){ this.textTemplate = _.template( data ) })

Mais cela n'a pas fonctionné.

Tomáš
la source

Réponses:

51

EDIT: Cette réponse est ancienne et dépassée. Je le supprimerais, mais c'est la réponse «acceptée». Je vais plutôt injecter mon avis.

Je ne recommanderais plus de faire ça. Au lieu de cela, je séparerais tous les modèles en fichiers HTML individuels. Certains suggéreraient de les charger de manière asynchrone (Require.js ou une sorte de cache de modèle). Cela fonctionne bien sur les petits projets, mais sur les grands projets avec beaucoup de modèles, vous vous retrouvez à faire une tonne de petites requêtes asynchrones lors du chargement de la page, ce que je n'aime vraiment pas. (ugh ... ok, vous pouvez le contourner avec Require.js en pré-compilant vos dépendances initiales avec r.js, mais pour les modèles, cela me semble toujours faux)

J'aime utiliser une tâche grunt (grunt-contrib-jst) pour compiler tous les modèles HTML dans un seul fichier templates.js et l'inclure. Vous obtenez le meilleur de tous les modèles IMO du monde ... les modèles vivent dans un fichier, la compilation desdits modèles se produit au moment de la construction (pas au moment de l'exécution), et vous n'avez pas cent petites requêtes asynchrones lorsque la page démarre.

Tout ce qui est en dessous est indésirable

Pour moi, je préfère la simplicité d'inclure un fichier JS avec mon modèle. Donc, je pourrais créer un fichier appelé view_template.js qui inclut le modèle en tant que variable:

app.templates.view = " \
    <h3>something code</h3> \
";

Ensuite, c'est aussi simple que d'inclure le fichier de script comme un fichier normal et de l'utiliser ensuite dans votre vue:

template: _.template(app.templates.view)

Pour aller plus loin, j'utilise en fait coffeescript, donc mon code ressemble plus à ceci et évite les caractères d'échappement de fin de ligne:

app.templates.view = '''
    <h3>something code</h3>
'''

L'utilisation de cette approche évite d'ajouter require.js là où ce n'est vraiment pas nécessaire.

Brian Genisio
la source
46
cette approche perdrait toutes les fonctions de coloration syntaxique, de reformatage et de refactoring disponibles avec l'ide. ne pas voter cependant.
Kinjal Dixit
1
Je suis désolé, mais j'ai dû voter contre cette réponse. C'est horriblement maladroit car il conservera toujours les fichiers de modèle en tant que fichiers de script, juste un peu obligé de ressembler à des modèles. Les modèles doivent être des modèles, donc si vous devez apporter Require.js ou utiliser la solution brillante de koorchik ci-dessous, je pense que cela en vaut vraiment la peine.
Tommi Forsström
3
@ TommiForsström Je suis d'accord. Je me suis éloigné de cette approche. Hou la la! Le 4 décembre 2011 est il y a très longtemps dans le monde du développement de Backbone.js :)
Brian Genisio
En fait, j'aimerais supprimer cette réponse, mais je ne peux pas car c'est la réponse acceptée. Il est dépassé et il existe de bien meilleures solutions que cela. Aujourd'hui, je les aurais en tant que fichiers de modèle séparés et utiliserais une tâche grognante (JST, par exemple) pour les créer dans un fichier templates.js séparé afin d'éviter la nature asynchrone de les récupérer tous individuellement. C'est une approche du meilleur des deux mondes de l'OMI.
Brian Genisio
et bien s'il n'y a pas beaucoup de templates je pense que l'ancienne solution est vraiment la plus efficace.
silkAdmin
107

Voici une solution simple:

var rendered_html = render('mytemplate', {});

function render(tmpl_name, tmpl_data) {
    if ( !render.tmpl_cache ) { 
        render.tmpl_cache = {};
    }

    if ( ! render.tmpl_cache[tmpl_name] ) {
        var tmpl_dir = '/static/templates';
        var tmpl_url = tmpl_dir + '/' + tmpl_name + '.html';

        var tmpl_string;
        $.ajax({
            url: tmpl_url,
            method: 'GET',
            dataType: 'html', //** Must add 
            async: false,
            success: function(data) {
                tmpl_string = data;
            }
        });

        render.tmpl_cache[tmpl_name] = _.template(tmpl_string);
    }

    return render.tmpl_cache[tmpl_name](tmpl_data);
}

Utiliser "async: false" ici n'est pas un mauvais moyen car dans tous les cas il faut attendre que le template soit chargé.

Donc, la fonction "render"

  1. vous permet de stocker chaque modèle dans un fichier html distinct dans un répertoire statique
  2. est très léger
  3. compile et met en cache des modèles
  4. résume la logique de chargement du modèle. Par exemple, à l'avenir, vous pourrez utiliser des modèles préchargés et précompilés.
  5. est facile à utiliser

[Je modifie la réponse au lieu de laisser un commentaire parce que je pense que c'est important.]

si les modèles n'apparaissent pas dans l' application native , et que vous voyez HIERARCHY_REQUEST_ERROR: DOM Exception 3, regardez la réponse de Dave Robinson à Qu'est-ce qui peut exactement provoquer une erreur "HIERARCHY_REQUEST_ERR: DOM Exception 3"? .

En gros, vous devez ajouter

dataType: 'html'

à la requête $ .ajax.

Koorchik
la source
3
@BinaryNights - devrions-nous toujours ajouter dataType: 'html'à notre requête ajax, juste au cas où?
Matt
Cela fonctionne-t-il également pour les vues imbriquées? Apparemment, je ne peux pas le faire fonctionner si une vue fait référence à une autre vue.
T. Rossi
1
Oui, cela devrait également fonctionner pour les modèles imbriqués. Ajoutez simplement un assistant de rendu et appelez-le comme: <% = render ('nested_template', data)%>
koorchik
Bonjour, pouvez-vous expliquer un peu plus les "modèles de compilation et de cache"? Quand j'ai essayé d'appeler la fonction de rendu, elle n'ajoutait pas le tmpl_data pour renvoyer la valeur, elle l'a juste passée telle quelle. J'ai dû appeler la méthode "Handlebars.compile" après cela. Je vous remercie.
cdagli
18

Ce mixin vous permet de rendre modèle externe à l' aide de soulignement de manière très simple: _.templateFromUrl(url, [data], [settings]). L'API de méthode est presque la même que _.template () de Underscore . Mise en cache incluse.

_.mixin({templateFromUrl: function (url, data, settings) {
    var templateHtml = "";
    this.cache = this.cache || {};

    if (this.cache[url]) {
        templateHtml = this.cache[url];
    } else {
        $.ajax({
            url: url,
            method: "GET",
            async: false,
            success: function(data) {
                templateHtml = data;
            }
        });

        this.cache[url] = templateHtml;
    }

    return _.template(templateHtml, data, settings);
}});

Usage:

var someHtml = _.templateFromUrl("http://example.com/template.html", {"var": "value"});
Dmitriy
la source
2
Vraiment joli petit mixin là-bas très soigné! :) bravo pour le partage
Nick White
Très cool D, c'était le genre de solution que je recherchais. et je pense que cela pourrait être utilisé pour garder un ensemble de modèles privés.
bigmadwolf
@abhi il est fourni dans la réponse. De plus, vous avez besoin de jQuery pour charger le modèle, mais vous pouvez réécrire une partie du code qui charge le modèle via AJAX à votre goût en utilisant n'importe quelle autre bibliothèque.
Dmitriy
@Dmitriy async: false est obsolète, donc si j'appelle sans paramètre async, cela ne fonctionne pas, je pense que c'est parce que par défaut async est vrai cela signifie appeler syncronisilly, alors avez-vous une solution pour ce problème
abhi
@abhi, cela fonctionne pour jQuery 1. * Voir aussi cette réponse stackoverflow.com/a/11755262/541961
Dmitriy
17

Je ne voulais pas utiliser require.js pour cette tâche simple, j'ai donc utilisé la solution modifiée de koorchik.

function require_template(templateName, cb) {
    var template = $('#template_' + templateName);
    if (template.length === 0) {
        var tmpl_dir = './templates';
        var tmpl_url = tmpl_dir + '/' + templateName + '.tmpl';
        var tmpl_string = '';

        $.ajax({
            url: tmpl_url,
            method: 'GET',
            contentType: 'text',
            complete: function (data, text) {
                tmpl_string = data.responseText;
                $('head').append('<script id="template_' + templateName + '" type="text/template">' + tmpl_string + '<\/script>');
                if (typeof cb === 'function')
                    cb('tmpl_added');
            }
        });
    } else {
        callback('tmpl_already_exists');
    }
}

require_template('a', function(resp) {
    if (resp == 'tmpl_added' || 'tmpl_already_exists') {
        // init your template 'a' rendering
    }
});
require_template('b', function(resp) {
    if (resp == 'tmpl_added' || 'tmpl_already_exists') {
        // init your template 'b' rendering
    }
});

Pourquoi ajouter des modèles au document, plutôt que de les stocker dans un objet javascript? Parce que dans la version de production, je voudrais générer un fichier html avec tous les modèles déjà inclus, donc je n'aurai pas besoin de faire de requêtes ajax supplémentaires. Et en même temps, je n'aurai pas besoin de refactoriser mon code, car j'utilise

this.template = _.template($('#template_name').html());

dans mes vues Backbone.

Tyth
la source
1
En utilisant cela aussi, c'est génial pour le scénario où j'essaie d'utiliser Jasmine pour TDD et souhaite tester des modèles avant d'avoir implémenté requirejs et son plugin textjs. Bravo @Tramp
Nicholas Murray
L'appel à $ .ajax est asynchrone, tout ce qui dépend des résultats doit être exécuté dans la méthode done de la promesse retournée.
JoshRoss
Merci pour cela. Je l'ai utilisé. Une suggestion: aucune raison d'ajouter une balise de script - pourrait simplement aller de l'avant et convertir en modèle et le conserver dans un hachage de recherche. Voici un exemple de violon (non fonctionnel): jsfiddle.net/PyzeF
webnesto
async: falseest obsolète maintenant
ProblemsOfSumit
Depuis async: falseest obsolète, j'ai amélioré la réponse, en ajoutant un completerappel.
Alexander le
16

Cela peut être légèrement hors sujet, mais vous pouvez utiliser Grunt (http://gruntjs.com/) - qui s'exécute sur node.js (http://nodejs.org/, disponible pour toutes les principales plates-formes) pour exécuter des tâches depuis le ligne de commande. Il existe de nombreux plugins pour cet outil, comme un compilateur de modèles, https://npmjs.org/package/grunt-contrib-jst . Consultez la documentation sur GitHub, https://github.com/gruntjs/grunt-contrib-jst . (Vous devrez également comprendre comment exécuter le gestionnaire de packages de nœuds, https://npmjs.org/ . Ne ​​vous inquiétez pas, c'est incroyablement facile et polyvalent.)

Vous pouvez ensuite conserver tous vos modèles dans des fichiers html séparés, exécuter l'outil pour les précompiler tous en utilisant un trait de soulignement (ce qui, je crois, est une dépendance du plugin JST, mais ne vous inquiétez pas, le gestionnaire de paquets de nœuds installera automatiquement les dépendances pour vous).

Cela compile tous vos modèles en un seul script, par exemple

templates.js

Le chargement du script définira un global - "JST" par défaut - qui est un tableau de fonctions, et peut être consulté comme ceci:

JST['templates/listView.html']()

qui serait similaire à

_.template( $('#selector-to-your-script-template'))

si vous mettez le contenu de cette balise de script dans (templates /) listView.html

Cependant, le vrai kicker est le suivant: Grunt est livré avec cette tâche appelée `` watch '', qui surveillera essentiellement les modifications apportées aux fichiers que vous avez définis dans votre fichier local grunt.js (qui est essentiellement un fichier de configuration pour votre projet Grunt, en javascript ). Si vous avez grogné, lancez cette tâche pour vous, en tapant:

grunt watch

à partir de la ligne de commande, Grunt surveillera toutes les modifications que vous apportez aux fichiers et exécutera automatiquement toutes les tâches que vous avez configurées pour cela dans ce fichier grunt.js s'il détecte des changements - comme la tâche jst décrite ci-dessus. Modifiez puis enregistrez vos fichiers, et tous vos modèles se recompilent dans un seul fichier js, même s'ils sont répartis sur un certain nombre de répertoires et de sous-répertoires.

Des tâches similaires peuvent être configurées pour linter votre javascript, exécuter des tests, concaténer et minimiser / uglifier vos fichiers de script. Et tout peut être lié à la tâche de surveillance, de sorte que les modifications apportées à vos fichiers déclencheront automatiquement une nouvelle «construction» de votre projet.

Il faut un certain temps pour configurer les choses et comprendre comment configurer le fichier grunt.js, mais cela vaut bien le temps investi, et je ne pense pas que vous reviendrez jamais à une façon de travailler pré-grognement

Mansiemans
la source
Réponse préférée. Cela devrait être la réponse acceptée. (pas le mien)
Brian Genisio
Bon point d'entrée pour grogner. Cela fonctionne bien pour du HTML simple, mais si j'ai <% = price%> ou similaire, j'obtiens: un jeton inattendu =, échec de la compilation à partir de grunt
mcktimo
J'aime cette approche (en utilisant JST), sauf que j'ai des problèmes pour faire ceci:, template: JST['test.html']()il ne semble pas charger les données depuis JST :( (voir ma question ici: stackoverflow.com/questions/29723392/… )
timhc22
15

Je pense que c'est ce qui pourrait vous aider. Tout dans la solution tourne autour de la require.jsbibliothèque qui est un fichier JavaScript et un chargeur de module.

Le tutoriel sur le lien ci-dessus montre très bien comment un projet de base pourrait être organisé. Un exemple d'implémentation est également fourni. J'espère que cela t'aides.

nayaab
la source
3
Merci pour la référence à mon site, pour tous ceux qui cherchent, j'ai commencé un projet qui tente de mettre en œuvre les meilleures pratiques backboneboilerplate.com
Thomas Davis
4

Je me suis intéressé à la création de modèles javascript et maintenant je fais les premiers pas avec le backbone. C'est ce que j'ai trouvé et semble fonctionner plutôt bien.

window.App = {

    get : function(url) {
        var data = "<h1> failed to load url : " + url + "</h1>";
        $.ajax({
            async: false,
            url: url,
            success: function(response) {
                data = response;
            }
        });
        return data;
    }
}

App.ChromeView = Backbone.View.extend({
    template: _.template( App.get("tpl/chrome.html") ),
    render: function () {
        $(this.el).html(this.template());
        return this;
    },
});

App.chromeView = new App.ChromeView({ el : document.body });
App.chromeView.render();
j040p3d20
la source
Sur votre getfonction, je retournerais probablement le $.ajaxlui - même afin qu'il renvoie un objet de promesse au cas où votre modèle ne répondrait pas tout de suite.
Dennis Rongo
4

J'ai dû définir le type de données sur "texte" pour que cela fonctionne pour moi:

get : function(url) {
    var data = "<h1> failed to load url : " + url + "</h1>";
    $.ajax({
        async: false,
        dataType: "text",
        url: url,
        success: function(response) {
            data = response;
        }
    });
    return data;
}
user1828189
la source
2

J'ai trouvé une solution qui fonctionne pour moi en utilisant jQuery.

J'ajoute le code du modèle de soulignement, avec la méthode jQuery.load (), au fichier html principal.

Une fois qu'il est là, je l'utilise pour générer les modèles. Tout doit se produire de manière synchrone!

Le concept est:

J'ai un code de modèle de carte de soulignement:

<!-- MAP TEMPLATE-->
<script type="text/template" id="game-map-template">
    <% _.each(rc, function(rowItem, index){ %>
      <ul class="map-row" data-row="<%- index %>">
        <li class="map-col <%- colItem.areaType ? 'active-area' : '' %>"></li>
        ...
</script>

Et j'ai mis ce code dans un fichier appelé map-template.html

Après cela, je crée un wrapper pour les fichiers de modèle.

<div id="templatesPool"></div>

Ensuite, j'inclus ce fichier dans mon fichier html principal comme ça.

En tête:

<!-- Template Loader -->
<script> 
    $(function(){
      $("#templatesPool").append($('<div>').load("map-template.html")); 
    });
</script> 

À votre santé.

Kaloyan Stamatov
la source
1

Je sais que cette question est vraiment ancienne, mais elle est apparue comme le premier résultat d'une recherche Google pour les modèles de soulignement ajax.

J'étais fatigué de ne pas trouver une bonne solution pour cela alors j'ai créé la mienne:

https://github.com/ziad-saab/underscore-async-templates

En plus de charger des modèles de soulignement à l'aide d'AJAX, il ajoute la fonctionnalité <% include%>. J'espère que cela pourra être utile à quelqu'un.

ziad-saab
la source
0

J'étais un peu inquiet de forcer jQuery à fonctionner de manière synchrone, j'ai donc modifié l'exemple synchrone précédent en utilisant des promesses. C'est à peu près la même chose, mais fonctionne de manière asynchrone. J'utilise des modèles hbs dans cet exemple:

var asyncRenderHbs= function(template_name, template_data) {
    if (!asyncRenderHbs.template_cache) { 
        asyncRenderHbs.template_cache= {};
    }

    var promise= undefined;

    if (!asyncRenderHbs.template_cache[template_name]) {
        promise= new Promise(function(resolve, reject) {
            var template_url= '/templates/' + template_name;
            $.ajax({
                url: template_url,
                method: 'GET',
                success: function(data) {
                    asyncRenderHbs.template_cache[template_name]= Handlebars.compile(data);
                    resolve(asyncRenderHbs.template_cache[template_name](template_data));
                },
                error: function(err, message) {
                    reject(err);
                }           
            });
        });
    } else {
        promise= Promise.resolve(asyncRenderHbs.template_cache[template_name](template_data));
    }

    return promise;
};

Ensuite, pour utiliser le html rendu:

asyncRenderHbs('some_template.hbs', context)
    .then(function(html) {
        applicationMain.append(html);
        // Do other stuff here after html is rendered...
    })
    .catch(function(err) {
        // Handle errors
    });

REMARQUE: comme indiqué par d'autres, il serait préférable de compiler tous les modèles dans un seul fichier templates.js et de le charger au début plutôt que d'avoir de nombreux petits appels AJAX synchrones pour obtenir des modèles lorsque la page Web se charge.

Megatron
la source
0

Avertissement avant - Voici les dragons:

Je mentionne l'approche présentée ci-dessous simplement pour aider ceux qui ont du mal à faire en sorte que les piles ASP.NET (et les frameworks similaires) fonctionnent harmonieusement avec l'écosystème de js-libs. Il va sans dire que ce n'est pas une solution générique. Ayant dit cela ...

/ endforwardwarning

Si vous utilisez ASP.NET, vous pouvez externaliser vos modèles simplement en les plaçant dans une ou plusieurs vues partielles qui leur sont propres. Aka dans votre .cshtml:

  @Html.Partial("path/to/template")

Dans votre template.cshtml:

   // this is razorview and thusly if you ever need to use the @ character in here  
   // you will have to either escape it as @@ or use the html codepoint which is &#64
   // http://stackoverflow.com/questions/3626250/escape-character-in-razor-view-engine
   <script type="text/x-template" id="someId">
        <span class="foo"><%= name %></span>
   </script>

Et maintenant, vous pouvez utiliser le modèle comme d'habitude:

  _.template($("#someId").html())({ name: "Foobar" });

J'espère que cette approche insaisissable aidera quelqu'un à économiser une heure de se gratter la tête.

XDS
la source