Accueil Nos publications Blog doT.js le templating Javascript a la vitesse de la lumière

doT.js le templating Javascript a la vitesse de la lumière

doT.js  est un moteur de templating javascript à la fois simple, concis et très rapide. A l’origine développé pour node.js, il est également disponible sous forme de librairie à utiliser sur vos sites web.

Dans cet article, après une brève présentation du templating de manière globale, je vous présenterai les avantages de doT.js et comment l’utiliser dans votre projet.

 Le templating

Souvent, lorsque l’on développe un site web, nous sommes amenés à construire des éléments du DOM avant de les insérer dans notre page. Et souvent, cela se traduit dans les faits par une boucle dans laquelle des chaines de caractères sont concaténées selon un ensemble de conditions.


var html = '<div><div>' + name + '</div><div>' + address.street + ' ';
html += address.zipcode + ' ' + address.city + ' ' + address.country + '</div>';
html += '<div>le site : ' + website + '</div>';
html += '<div>' + creationDate + '</div>';
html += '<div>Capital : ' + fonds + '</div></div>';

Cette approche a le défaut d’être peu maintenable et fortement sensible aux erreurs, notamment sur les fermetures de balises ou les problèmes de guillemets.

Pour faire face à ce type de problèmes, le templating vient à notre secours. Hors, en JavaScript, il existe de nombreux frameworks de templating.

Pourquoi doT.js ?

Dans ma mission actuelle, je travaille sur un site web destiné aux mobiles. Et qui dit mobile, dit deux problématiques importantes et très contraignantes : la rapidité et la compatibilité.

En effet, la puissance de calcul des smartphones est bien moindre que celle d’un ordinateur de bureau. C’est particulièrement frappant dans le développement web mobile, car à ce facteur se rajoute la relative médiocrité des moteurs JavaScript des navigateurs par défaut si on les compare aux versions récentes de Chrome ou Opera par exemple.

Il était donc important de choisir le moteur de templating le plus rapide possible tout en étant compatible avec un maximum de téléphones.

S’en est suivi un combat à mort entre différents moteurs ( mustache, handlebars, jqote2, underscore, etc…). Le tout comparé à la concaténation de chaines et à l’utilisation d’un tableau qui sont les solutions habituellement mises en places ou proposées dans les forums d’entraide.

Vous pouvez voir les benchmarks qui portent sur le cas d’utilisation de base du projet sur jsPerf .

Étonnamment, le résultat a été sans appel avec doT.js gagnant haut la main y compris devant la concaténation de chaînes, et ce sur tous les périphériques de test que j’avais sous la main.

L’autre gros avantage est sa concision, seulement 130 lignes de codes commentaires inclus. Et 2.8kB pour la version “minifiée”.

Mais comment ça marche

La première chose à savoir sur doT.js, est que c’est très basique surtout si on le compare aux ténors du genre en JavaScript.

Mais l’essentiel y est : afficher des valeurs, itérer sur des tableaux, branchements conditionnels et insertion de code JavaScript directement dans les templates.

Il est également possible de créer des fragments réutilisables même si je n’ai pas suffisamment testé cette fonctionnalité.

Mais avant toute chose, il va vous falloir télécharger le fichier javascript sur gitHub et l’inclure dans votre page :


<script type="text/javascript" src="doT.min.js"></script>

Votre premier template

Tout d’abord il vous faut définir votre template. La syntaxe est celle assez classique des doubles accolades (moustaches) {{}} pour définir les éléments à insérer. Le premier caractère (appelé préfixe dans le reste de l’article) définit à quoi correspond le bloc entre moustaches. Nous verrons progressivement les différentes possibilités. Pour le moment, nous débutons et nous allons nous contenter d’afficher la valeur d’une variable. Pour cela le préfixe est le signe “=“.


Hello {{=maValeur}}

Sauf que doT.js a une particularité : les données que l’on va injecter dans le template sont contenues dans une variable qui, par défaut, s’appelle it (il est possible de changer le nom). Notre template doit donc devenir :


hello {{=it.maValeur}}

Compilation du template

doT.js nécessite que vous précompiliez votre template. L’opération transforme celui-ci en une fonction JavaScript qui prendra vos données en paramètre. De base doT.js, ne gère pas de mécanisme pour définir les templates dans des balises ou dans un fichier externe. Il prend simplement une chaine de caractères, à vous de la récupérer où vous voulez. Nous verrons comment faire avec jQuery plus loin dans l’article. Pour compiler votre template c’est très simple, nous allons utiliser la variable doT  qui est exposée dans le scope global par la librairie :


var compiledTemplate = doT.template('hello {{= it.maValeur}}');

La variable compiledTemplate contiendra votre fonction de templating. Si on regarde le code compilé on obtient :


function anonymous(it) {
   var out='hello '+( item.maValeur);
   return out;
}

On constate qu’il s’agit d’une simple concaténation de chaînes.

Exécuter notre template

Nous avons donc un template, nous l’avons compilé, il nous faut maintenant l’exécuter en lui passant des données. Pour cela, il suffit d’appeler la fonction renvoyée par la méthode template en lui passant un objet JavaScript.


var result = compiledTemplate ( { maValeur : 'world'});

La variable result contiendra la chaine “hello world”. Ici on encapsule notre chaine dans un objet JavaScript pour qu’elle soit accessible dans notre template sous la forme it.maValeur. Ça peut paraître contraignant mais dans la vraie vie, si on utilise du templating c’est que l’on a des données plus complexes qu’une simple chaîne  Il aurait été possible d’injecter directement la chaîne de caractères :


compiledTemplate('world');

Dans ce cas notre template aurait été :


Hello {{=it}}

Passons aux choses sérieuses

Maintenant que je vous ai présenté les concepts de base, nous pouvons attaquer le véritable templating. Nous allons afficher l’objet suivant :


var entreprise = {
   name:'So@t',
   address:{
      street:'104, bis rue de Reuilly',
      zipcode:'75012',
      city:'Paris',
      country:''
   },
   website:'www.soat.fr',
   funds:100000,
   creationDate:new date(2000,01,01),
   directors:[
      {name:'Azria',firstname:'Michel'},
      {name:'Levy',firstname:'David-Eric'}
   ]
}

Nous avons un peu de tout, des chaines, des sous objets, des nombres, des dates et un tableau d’objets. Le pays est volontairement laissé vide, nous verrons pourquoi un peu plus tard.

Afficher des valeurs provenant de sous objets

Nous avons vu au début que pour afficher notre valeur nous avons utilisé une notation en point qui correspond à la syntaxe objet classique du javascript. Vu le code généré, c’est logique puisque doT.js se contente de concaténer ce qu’on lui a donné entre moustaches dans la chaîne de sortie. Du coup, fort logiquement, pour accéder à nos sous objets, il suffit de continuer avec cette notation :


<div>
<div>{{=it.name}}</div>
<div>{{=it.address.street}}
{{=it.address.zipcode}}
{{=it.address.city}}
{{=it.address.country}}</div>
<div>le site : {{=it.website}}</div>
<div>fondée en {{=it.creationDate}}</div>
<div>Capital : {{=it.fonds}}</div>
</div>

Nous pouvons constater qu’il n’y a rien de particulier à faire pour gérer les nombres, ils seront concaténés tels quels dans notre fonction finale. De même, la date utilisera la fonction d’affichage par défaut du JavaScript. Nous allons donc devoir traiter ce cas. Au passage, il s’agit du même fragment HTML que celui utilisé dans la concaténation de chaînes au début de l’article pour ceux qui ont encore des doutes sur l’utilité du templating.

 Gérer la date

Afin d’afficher la date formatée comme nous le souhaitons nous allons créer une fonction très simple :


function formatDate ( date){
   return date.getDay() + '/' + (date.getMonth()+1) + '/' + date.getYear();
}

Nous pouvons maintenant appeler notre fonction directement dans notre template tant que celle-ci est accessible dans le scope global.


<div>{{= formatDate(it.creationDate) }}</div>

L’appel à la fonction sera simplement ajouté dans la construction du résultat. En conséquence, il faut que votre fonction retourne une chaîne de caractères.


function anonymous(it) {
   var out='<div>'+( formatDate(it.creationDate) )+'</div>';
   return out;
}

 Ajouter des conditions

Nous allons maintenant gérer le cas de notre pays vide pour ne pas afficher de ligne vide. Pour cela nous allons devoir utiliser le préfixe ?  permettant de définir une condition dans notre template.


<div>{{=it.address.street}}
{{=it.address.zipcode}}
{{=it.address.city}} {{? it.address.country != ''}}
{{=it.address.country}} {{?}}</div>

Le {{?}} se trouvant à la fin de l’exemple marque la fin de notre condition. Il est également possible de gérer des conditions plus complexes en utilisant le préfixe ??  qui représente l’instruction sinon.


<div>{{=it.address.street}}
{{=it.address.zipcode}}
{{=it.address.city}} {{? it.address.country == 'france'}}
Cocorico! {{?? it.address.country != ''}}
{{=it.address.country}} {{?}}</div>

Notez que le sinon se trouve à l’intérieur de la condition. De plus, notre sous condition n’est pas terminée par un  {{??}} final contrairement à la condition principale. Pour faire un sinon englobant toutes les possibilités non prises en compte par la condition, il suffit de ne pas mettre de conditions : {{??}} . Il est ainsi possible d’enchaîner plusieurs conditions de suite.


<div>{{=it.address.street}}
{{=it.address.zipcode}}
{{=it.address.city}} {{? it.address.country == 'france'}}
Cocorico! {{?? it.address.country != ''}}
{{=it.address.country}} {{??}}
Pays Inconnu {{?}}</div>

Ce qui au final nous génère le code JavaScript suivant :


function anonymous(it) {
   var out='<div>'+(it.address.street)+' '+(it.address.zipcode)+' '+(it.address.city)+' ';
   if(it.address.country == 'france') { out+=' Cocorico! '; }
   else if(it.address.country != '') { out+=' '+(it.address.country)+' '; }
   else { out+=' Pays Inconnu '; }
   out+='</div>';
   return out;
}

Itérations et gestion des tableaux

Nous allons maintenant nous attaquer à l’affichage du nom des dirigeants de l’entreprise. Et comme il se trouve que Soat possède deux dirigeants, nous allons devoir faire une boucle. Pour cela, il faut utiliser le préfixe ~ puis donner les informations permettant d’itérer au format tableau:valeurCourante:index . Ce qui nous donne :


{{~it.directors:director:i}}
<div> {{=director.firstame}} {{=director.name}}</div>
{{~}}

Comme pour les conditions nous avons une instruction {{~}} finale permettant de délimiter notre boucle. A partir de ce template, doT.js va gérer le code suivant :


function anonymous(it) {
   var out='';
   var arr1=it.directors;
   if(arr1) {
      var director,i=-1,l1=arr1.length-1;
       while(i<l1) { director=arr1[i+=1];
                     out+='<div> '+(director.firstame)+' '+(director.name)+'</div>'; }
      }
   }
   return out;
}

On voit que doT.js se charge de générer une boucle optimisée selon les règles de l’art du JavaScript sans que l’on ait à subir la perte de lisibilité dans notre code.

Utilisation avancée

Maintenant que nous avons vu les bases de ce que l’on peut faire avec doT.js, je vais présenter des concepts un peu plus avancés.

Inclure du JavaScript dans nos templates

Nous avons vu 3 types de préfixes différents en fonction de ce que l’on veut faire. Mais je n’ai pas évoqué ce qu’il se passe si l’on ne met pas de préfixe dans nos moustaches. Dans ce cas de figure, doT.js inclura le texte entre moustaches en tant que JavaScript dans la fonction qu’il génère. Ce code sera inclus en dehors de la concaténation contrairement à ce que nous avons fait pour formater la date. Il est ainsi possible de déclarer des variables, de faire vos propres boucles ou tests si jamais ceux fournis via les préfixes ne vous conviennent pas. Bref tout ce que vous pouvez faire en JavaScript tant que c’est syntaxiquement correct lorsque la fonction finale est générée.


{{var toto = 'hello';}}
{{toto += item.maValeur;}}
{{=toto}}

Attention : n’oubliez pas les points-virgules en fin de ligne.


function anonymous(it) {
   var out='';
   var toto = 'hello';
   toto += it.maValeur;
   out+=(toto);
   return out;
}

Un cas d’utilisation pratique est pour déboguer ce qui se place dans votre template par exemple en ajoutant un :


{{console.log(it);}}

Assez pratique dans certains templates manipulant des objets particulièrement complexes.

Echapper une valeur

Si vous manipulez des liens, doT.js se charge de traiter vos chaines en utilisant le préfixe !


var templateLien = dot.template('<a href="{{!it.url}}">mon lien</a>');

templateLien({url : 'http//www.Soat.fr?test=une valeur&test2=c\'est risqué'});

Va donner le code javascript suivant :


function anonymous(it) {
   var out='<a href="'+encodeHTML(it.url)+'">mon lien</a>';
   return out;
}

Et au final le code HTML suivant:


<a href="http//www.soat.fr?test=une valeur&test2=c'est risqué">mon lien</a>

Ce n’est donc pas parfait et il faut être vigilant sur ce qu’on échappe vu qu’il s’agit d’un simple appel à encodeHTML, mais c’est déjà bien.

Passer des tableaux en paramètre

Un autre cas un peu particulier est le cas où l’on veut passer un tableau en paramètre à notre fonction de templating. En effet, celle-ci ne peut prendre qu’un objet. Rien de bien compliqué, il suffit d’encapsuler notre tableau dans un objet :


var tab = ['value1', 'value2', 'value3'];
templateFunction({values:tab});

Le tableau sera accessible dans votre template via it.values. Vous pouvez bien évidemment remplacer values par le nom de variable de votre choix.

Déclarer ses templates dans le fichier courant

Dans tous les exemples de l’article je n’ai pas évoqué où se trouvaient nos templates ou bien j’ai utilisé des chaînes de caractères passées en paramètre. De base, doT.js ne fournit aucun mécanisme pour gérer vos templates. Et, bien que pour des exemples simples, gérer tout ça dans votre JavaScript peut être pratique, ce n’est pas une solution pérenne dans un vrai projet. Il vous revient donc de gérer ça tout seul, ou si vous êtes fainéants comme moi, de vous aider de jQuery. La première solution est de stocker ses templates directement dans votre fichier HTML :

<script id="monTemplate" type="template/doT.js">// <![CDATA[
<div>mon super template! Hello {{=it.maValeur}} </div>
// ]]></script>

Nous créons une balise script qui va contenir notre template.

Nous lui donnons un type arbitraire mais explicite pour que tout le monde, et surtout le navigateur, sache qu’il ne s’agit pas de JavaScript mais d’un template doT.js.

Le navigateur ne saura pas reconnaître qu’il s’agit d’un template doT.js mais il n’essaiera pas de l’interpréter comme du JavaScript non plus, ce qui est le but.

Enfin, nous donnons un identifiant à notre balise script pour pouvoir en récupérer le contenu via les outils de manipulation de dom.


var template = document.getElementById('monTemplate').innerHTML;

Ou en jquery


var template = $('#monTemplate').text();

Externalisation du template

Pouvoir stocker son template est pratique, surtout pour le web mobile ou l’on inline beaucoup de choses, mais il peut également être pratique de stocker ses templates dans des fichiers spécifiques.

Là encore doT.js ne nous offre aucune solution, il va falloir s’aider d’autres outils. Le premier qui vient a l’esprit est encore une fois jQuery qui permet de charger un fichier très simplement.


var template;
$.get('chemin/de/votre/template.html', function(data){
   template = dot.template(data);
});

Ce code va charger le fichier de votre template et exécuter la méthode passée en paramètre une fois ce chargement terminé.

La variable data contient le contenu de notre fichier.

On compile notre template directement car c’est l’endroit le plus logique pour le faire mais vous pouvez le faire autre part.

Attention, cette méthode pose un gros problème : le chargement se fait en asynchrone, donc vous devrez vous assurer que votre template est prêt et bien chargé avant de l’utiliser.

Un autre problème peut survenir dans le cas où vous changez l’extension du fichier contenant votre template. Il faut que le fichier ait un type mime correspondant à un fichier textuel. En l’occurrence TXT ou HTML marchent parfaitement.

J’ai personnellement eu le problème en voulant récupérer un fichier ayant une extension .template depuis un serveur Weblogic.

Sous Apache, aucun problème, le type mime par défaut était celui d’un fichier texte. Cependant sous Weblogic, le type par défaut semble être celui des fichiers binaires.

Pour résoudre le problème lié au chargement asynchrone des données, il est possible de déléguer le chargement de votre template à une librairie chargée de charger les ressources de manière asynchrone telle que require.js.

Celui-ci vous garantira que tous vos fichiers, y compris vos templates, seront bien prêts avant d’exécuter votre code JavaScript.

Si vous n’utilisez pas de librairie pour ce genre de choses, il s’agit sans doute d’une bonne occasion de regarder les possibilités de require.js, labs.js ou même yepnope.

Conclusion

Pour conclure, ce qu’il faut retenir de doT.js est qu’il est simple, léger et rapide. Très rapide !

Il répond parfaitement à des besoins de templating basiques et, associé aux méthodes de chargement de jQuery ou à require.js, fait l’essentiel de ce que l’on peut demander à un système de templating côté client.

Cependant, si vous avez besoin d’un moteur plus évolué ou si vous ne vous sentez pas à l’aise en JavaScript vous risquez d’être fortement déçu.

En effet, le gros point noir de ce framework est la documentation qui est peu explicite et le manque de ressources sur le net.

Mais si vous avez des problèmes de performances sur votre site, il peut probablement vous aider à avoir un gain significatif à moindre coût.