Mustache, Templating library, jQuery, JSON – Utiliser Mustache comme système de template javascript pour une API sortant des flux JSON
Dans un précèdent billet, on avait vu comment utiliser le plugin de jQuery Template pour se faciliter le travail de génération de pages/écrans/IHM depuis des flux JSON. En effet, pour faire valider des écrans par le métier, mieux vaut présenter un aperçu intuitif dans le même esprit qu’une maquette Photoshop ou d’un wireframe fait avec des outils comme Axure, Balsamiq ou Omnigraffle. A la différence, que l’exercice peut se révéler périlleux car cette fois-ci les pages ne sont pas statiques comme sur une maquette HTML mais vont être alimentés par de véritable contenu émanant d’un API sous la forme des flux JSON. Il faut entendre par le métier : un client, un responsable marketing digital, un responsable éditorial, un DA…
C’est là où le recours à un framework javascript de génération de template peut-être d’une grande aide. C’est en ce sens que nous allons utiliser Mustache - Templating library
JSON vs IHM
On ne va pas faire valider des flux JSON quand bien même ces flux peuvent être mis en forme par une extension comme JSONview et sont le “coeur” de la génération des écrans. C’est tout le fossé presque culturelle qui existe entre le monde du développement et d’autres départements (marketing, éditorial, design) qui travaillent aussi à la création d’une application ou d’un site. Mieux vaut une représentation UX (des écrans) des règles fonctionnelles, compréhensibles par tous.
Pour se rendre compte du fossé entre les flux JSON et de véritables IHM, deux captures suffisent, l’une du flux JSON brut, l’autre de l’écran, rapidement maquetté à l’aide de Mustache de ce même flux.
Chargement non-UX du flux JSON à l’aide de JSONview
Chargement UX du même flux JSON à l’aide de Mustache
Dans notre article, on ne sert de Mustache que comme d’un outil véloce de maquettage pour des flux JSON mais cela va sans dire Mustache
peut-être aussi la solution choisie, en production, pour générer véritablement les écrans.
Comme dans l’article précèdent, une précision s’impose, vous pourriez rencontrer un problème lorsque vous essayez de lire le document JSON qui alimente vos écrans si vous ne mettez pas ce code sur un serveur. En effet, pour des raisons de sécurité un navigateur comme Google Chrome ne permet pas de charger des fichiers externes sur les dossiers locaux. Si vous devez charger un fichier .json en local, le mieux est donc d’utiliser firefox, safari ou tout simplement charger le fichier appelant la source de données au format .json sur un serveur en local type WAMP, MAMP ou EASYPHP.
Utiliser mustache pour rendre un flux JSON
Dans notre template, on se montre volontairement prolixe pour ce qui est des boucles pour être sûr de savoir ce qui est effectivement appeller depuis le JSON
OUTPUT – Le rendu du flux JSON à l’aide de Mustache
INPUT – La source du flux JSON, relativement complexe.
Détails du template de mustache
<script id="content-template" type="text/template"> <ol> {{#result}} {{#people}} {{#person}} <li> {{#image}} {{#code}} IMG ({{code}}) => <img src="{{url}}" /><br> {{/code}} {{^code}} no img {{/code}} {{/image}} TITLE => <a href="{{url}}/" title="{{title}}">{{title}} </a> <br><br> BODY escaped HTML => {{{body}}} <br><br> BODY unescaped HTML => {{body}} <br><br> TAXONOMY => {{#taxonomy}} {{#tags}} <span class="tag"><a class="no-decoration" href="{{tid}}">{{name}} ({{tid}})</a></span> {{/tags}} {{/taxonomy}} <br> GEOLOC => {{#geoloc}} LAT, LONG => ({{latitude}}, {{longitude}}) <br> {{#address}} GEOLOC => CITY, COUNTRY => <span class="tag">{{locality}} -[{{country}}]</a></span><br> {{/address}} {{/geoloc}} </li> {{/person}} {{/people}} {{/result}} </ol> </script> |
Pour comprendre la fonctionnement du template, beaucoup d’information sont disponibles sur le site de mustache de github.
Source : http://mustache.github.io/mustache.5.html
Par exemple BODY => {{{body}}}
fait que mustache va interpréter les balises html de la variable du JSON body
.
Tous les éléments pour faire fonctionner l’ensemble
Il y un fichier HTML, CSS, un fichier JS et des fichiers JSON qui sont la source de données. On a placé le tout dans différents répertoires : assets, css, js, json. On a intentionnellement mis deux fichiers JSON de source de donnés assez compliqués pour véritablement simuler le fonctionnement d’une API qui remonte des donnés complexes et pas seulement 3 données qui se battent en duel !
Tous les fichiers de cet article sont dans le fichier
.zip
: _mustache_example_4.zip
Le style avec good_all_style.css
/* #General ================================================== */ body { padding: 10px; margin: 20px; font-size: 14px; } body * { -webkit-font-smoothing: antialiased !important; text-rendering: optimizelegibility; } a { transition: all 0.2s ease-in-out; -moz-transition: all 0.2s ease-in-out; -webkit-transition: all 0.2s ease-in-out; -o-transition: all 0.2s ease-in-out; } body, h1, h2, h3, h4, h5, h6 { font-family: "Myriad Pro", Arial, Helvetica, Tahoma, sans-serif; font-weight: 300; } h1 { font-size: 30px; line-height: 42px; } h2 { font-size: 24px; line-height: 32px; } h3 { font-size: 18px; line-height: 24px; font-weight: normal; margin-bottom: 15px; } h4 { font-size: 16px; font-weight: normal; line-height: 20px; margin-bottom: 15px; } h5 { font-size: 14px; font-weight: bold; line-height: 18px; margin-bottom: 15px; } h6 { font-size: 12px; font-weight: bold; line-height: 16px; margin-bottom: 10px; text-transform: uppercase; } p { font-size: 14px; line-height: 170%; } p:empty { /*display: none; margin-bottom: 0;*/ } a.list { /*color: #F6177B;*/ color: #000; text-decoration: none; outline: none; } span.tag a { /*color: #F6177B;*/ color: #999999; text-decoration: none; outline: none; } |
Le code JS qui permet de charger les différents fichiers json main_mustache_complex.js
function sample_1 () { var product_name ='json/G_data_sample_utf8_complex_1.json'; $.getJSON(''+product_name+'', function(data) { var template = $('#content-template').html(); var info = Mustache.to_html(template, data); $('#PersonNewsFeed').html(info); }); console.log("product_name => "+product_name+"\n"); } function sample_2 () { var product_name ='json/G_data_sample_utf8_complex_2.json'; $.getJSON(''+product_name+'', function(data) { var template = $('#content-template').html(); var info = Mustache.to_html(template, data); $('#PersonNewsFeed').html(info); }); console.log("product_name => "+product_name+"\n"); } |
Le premier fichier de source de données G_data_sample_utf8_complex_1.json
chargé par la fonction sample_1
{ "encoding" : "UTF-8", "result": { "people": { "person": [ { "firstname": "Alikaou", "lastname": "Camara", "title": "The Soninke diaspora workers", "image": [ { "code": "256x256", "url": "assets/userphoto/alikaou_soninke.jpg" }], "body": "<p>Nulla facilisi. Etiam enim tellus, adipiscing et rutrum vitae, tempus nec orci. Aenean in consequat nunc. Pellentesque sagittis dolor non leo rutrum at suscipit quam adipiscing. Nulla faucibus faucibus neque id ultrices. Suspendisse potenti. Nulla sit amet justo vitae leo convallis facilisis. Maecenas condimentum urna quis tellus imperdiet sagittis. Nam quis metus sed erat feugiat sollicitudin pulvinar nec justo. Sed est quam, porta non elementum non, ullamcorper eu arcu. <b>Duis semper placerat enim non luctus. Vivamus tincidunt tempor erat, ac fringilla nulla aliquam quis. Suspendisse pretium enim risus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi id risus vel nibh dignissim dictum.</b></p>", "taxonomy": [ { "vid": "1_D890_3", "tags": [ { "tid": "1_D75_9", "name": "Africa" } ] }, { "vid": "1_D890_1", "tags": [ { "tid": "1_D75_639", "name": "Mali" }, { "tid": "1_D75_3949", "name": "Barack Obama" } ] } ], "geoloc": { "latitude": "12.650000", "longitude": "-8.000000", "bounds": { "northeast": { "latitude": "12.732974", "longitude": "-7.875945" }, "southwest": { "latitude": "12.490909", "longitude": "-8.115081" } }, "address": { "thoroughfare": "", "postalcode": "", "locality": "Bamako", "subadminarea": "Bamako", "adminarea": "Bamako", "country": "ML" } } }, { "firstname": "Robert Herbert", "lastname": "Appel", "title": "A Concise History of Modern Painting", "image": [ { "code": "256x256", "url": "assets/userphoto/Modern-Painting-Herbert.jpg" }], "body": "<p>Nulla facilisi. Etiam enim tellus, adipiscing et rutrum vitae, tempus nec orci. Aenean in consequat nunc. Pellentesque sagittis dolor non leo rutrum at suscipit quam adipiscing. Nulla faucibus faucibus neque id ultrices. Suspendisse potenti. Nulla sit amet justo vitae leo convallis facilisis. Maecenas condimentum urna quis tellus imperdiet sagittis. Nam quis metus sed erat feugiat sollicitudin pulvinar nec justo. Sed est quam, porta non elementum non, ullamcorper eu arcu. <b>Duis semper placerat enim non luctus. Vivamus tincidunt tempor erat, ac fringilla nulla aliquam quis. Suspendisse pretium enim risus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi id risus vel nibh dignissim dictum.</b></p>", "taxonomy": [ { "vid": "1_D430_3", "tags": [ { "tid": "1_D89_2", "name": "South America" } ] }, { "vid": "1_D430_1", "tags": [ { "tid": "1_D89_210", "name": "Brasil" }, { "tid": "1_D89_2902", "name": "Dilma Rousseff" }, { "tid": "1_D89_3794", "name": "Pope François" }, { "tid": "1_D89_790", "name": "Da Vinci" }, { "tid": "1_D89_1601", "name": "Religion" }, { "tid": "1_D89_731", "name": "Vatican" } ] } ], "geoloc": { "latitude": "-22.903539", "longitude": "-43.209587", "bounds": { "northeast": { "latitude": "-22.749045", "longitude": "-43.099382" }, "southwest": { "latitude": "-23.08302", "longitude": "-43.795449" } }, "address": { "thoroughfare": "", "postalcode": "", "locality": "Rio de Janeiro", "subadminarea": "", "adminarea": "Rio de Janeiro", "country": "BR" } } }, { "firstname": "Youssef", "lastname": "Touieb", "title": "History of the Muslim Brotherhood in Egypt", "image": [ { "code": "256x256", "url": "assets/userphoto/Youssef-Touieb-Selbstbildnis.jpg" }], "body": "<p>Nulla facilisi. Etiam enim tellus, adipiscing et rutrum vitae, tempus nec orci. Aenean in consequat nunc. Pellentesque sagittis dolor non leo rutrum at suscipit quam adipiscing. Nulla faucibus faucibus neque id ultrices. Suspendisse potenti. Nulla sit amet justo vitae leo convallis facilisis. Maecenas condimentum urna quis tellus imperdiet sagittis. Nam quis metus sed erat feugiat sollicitudin pulvinar nec justo. Sed est quam, porta non elementum non, ullamcorper eu arcu. <b>Duis semper placerat enim non luctus. Vivamus tincidunt tempor erat, ac fringilla nulla aliquam quis. Suspendisse pretium enim risus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi id risus vel nibh dignissim dictum.</b></p>", "taxonomy": [ { "vid": "1_D670_3", "tags": [ { "tid": "1_D64_3", "name": "Middle East" } ] }, { "vid": "1_D670_1", "tags": [ { "tid": "1_D94_579", "name": "Egypt" }, { "tid": "1_D94_3942", "name": "Moubarak" }, { "tid": "1_D94_3599", "name": "Mohamed Morsi" } ] } ], "geoloc": { "latitude": "30.044420", "longitude": "31.235712", "bounds": { "northeast": { "latitude": "30.110602", "longitude": "31.301973" }, "southwest": { "latitude": "30.009228", "longitude": "31.222067" } }, "address": { "thoroughfare": "", "postalcode": "", "locality": "Cairo", "subadminarea": "", "adminarea": "Cairo Governorate", "country": "EG" } } } ] } } } |
Le deuxième fichier de source de données G_data_sample_utf8_complex_1.json
chargé par le fonction sample_2
{ "encoding" : "UTF-8", "result": { "people": { "person": [ { "firstname": "Youssef", "lastname": "Wara", "title": "Ougao vs Disney", "image": [ { "code": "256x256", "url": "assets/userphoto/youssef_wara.jpg" }], "body": "<p>Nulla facilisi. Etiam enim tellus, adipiscing et rutrum vitae, tempus nec orci. Aenean in consequat nunc. Pellentesque sagittis dolor non leo rutrum at suscipit quam adipiscing. Nulla faucibus faucibus neque id ultrices. Suspendisse potenti. Nulla sit amet justo vitae leo convallis facilisis. Maecenas condimentum urna quis tellus imperdiet sagittis. Nam quis metus sed erat feugiat sollicitudin pulvinar nec justo. Sed est quam, porta non elementum non, ullamcorper eu arcu. <b>Duis semper placerat enim non luctus. Vivamus tincidunt tempor erat, ac fringilla nulla aliquam quis. Suspendisse pretium enim risus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi id risus vel nibh dignissim dictum.</b></p>", "taxonomy": [ { "vid": "1_D890_3", "tags": [ { "tid": "1_D75_9", "name": "Africa" } ] }, { "vid": "1_D890_1", "tags": [ { "tid": "1_D75_639", "name": "Kenya" }, { "tid": "1_D75_3949", "name": "Tanzanie" } ] } ], "geoloc": { "latitude": "12.650000", "longitude": "-8.000000", "bounds": { "northeast": { "latitude": "12.732974", "longitude": "-7.875945" }, "southwest": { "latitude": "12.490909", "longitude": "-8.115081" } }, "address": { "thoroughfare": "", "postalcode": "", "locality": "Bamako", "subadminarea": "Bamako", "adminarea": "Bamako", "country": "ML" } } }, { "firstname": "Moshe", "lastname": "Dunlop", "title": "Chess theory", "image": [ { "code": "256x256", "url": "assets/userphoto/Moshe-Dunlop.jpg" }], "body": "<p>Nulla facilisi. Etiam enim tellus, adipiscing et rutrum vitae, tempus nec orci. Aenean in consequat nunc. Pellentesque sagittis dolor non leo rutrum at suscipit quam adipiscing. Nulla faucibus faucibus neque id ultrices. Suspendisse potenti. Nulla sit amet justo vitae leo convallis facilisis. Maecenas condimentum urna quis tellus imperdiet sagittis. Nam quis metus sed erat feugiat sollicitudin pulvinar nec justo. Sed est quam, porta non elementum non, ullamcorper eu arcu. <b>Duis semper placerat enim non luctus. Vivamus tincidunt tempor erat, ac fringilla nulla aliquam quis. Suspendisse pretium enim risus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi id risus vel nibh dignissim dictum.</b></p>", "taxonomy": [ { "vid": "1_D430_3", "tags": [ { "tid": "1_D89_2", "name": "South America" } ] }, { "vid": "1_D430_1", "tags": [ { "tid": "1_D89_210", "name": "Brasil" }, { "tid": "1_D89_2902", "name": "Dilma Rousseff" }, { "tid": "1_D89_3794", "name": "Pope François" }, { "tid": "1_D89_790", "name": "Da Vinci" }, { "tid": "1_D89_1601", "name": "Religion" }, { "tid": "1_D89_731", "name": "Vatican" } ] } ], "geoloc": { "latitude": "-22.903539", "longitude": "-43.209587", "bounds": { "northeast": { "latitude": "-22.749045", "longitude": "-43.099382" }, "southwest": { "latitude": "-23.08302", "longitude": "-43.795449" } }, "address": { "thoroughfare": "", "postalcode": "", "locality": "Rio de Janeiro", "subadminarea": "", "adminarea": "Rio de Janeiro", "country": "BR" } } }, { "firstname": "Abdul", "lastname": "Fromager", "title": "Milk Recipes", "image": [ { "code": "256x256", "url": "assets/userphoto/Abdul-Fromager-Selbstbildnis.jpg" }], "body": "<p>Nulla facilisi. Etiam enim tellus, adipiscing et rutrum vitae, tempus nec orci. Aenean in consequat nunc. Pellentesque sagittis dolor non leo rutrum at suscipit quam adipiscing. Nulla faucibus faucibus neque id ultrices. Suspendisse potenti. Nulla sit amet justo vitae leo convallis facilisis. Maecenas condimentum urna quis tellus imperdiet sagittis. Nam quis metus sed erat feugiat sollicitudin pulvinar nec justo. Sed est quam, porta non elementum non, ullamcorper eu arcu. <b>Duis semper placerat enim non luctus. Vivamus tincidunt tempor erat, ac fringilla nulla aliquam quis. Suspendisse pretium enim risus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi id risus vel nibh dignissim dictum.</b></p>", "taxonomy": [ { "vid": "1_D670_3", "tags": [ { "tid": "1_D64_3", "name": "Middle East" } ] }, { "vid": "1_D670_1", "tags": [ { "tid": "1_D94_579", "name": "Egypt" }, { "tid": "1_D94_3942", "name": "Moubarak" }, { "tid": "1_D94_3599", "name": "Mohamed Morsi" } ] } ], "geoloc": { "latitude": "30.044420", "longitude": "31.235712", "bounds": { "northeast": { "latitude": "30.110602", "longitude": "31.301973" }, "southwest": { "latitude": "30.009228", "longitude": "31.222067" } }, "address": { "thoroughfare": "", "postalcode": "", "locality": "Cairo", "subadminarea": "", "adminarea": "Cairo Governorate", "country": "EG" } } } ] } } } |
L’ensemble du fichier qui charge le tout….
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <body> <head> <meta charset="utf-8" /> <title>Mustache complex</title> <link rel="STYLESHEET" type="text/css" href="css/good_all_style.css"> </head> <body> <h2>Check Out the list</h2> <ul> <li><a href="javascript:sample_1()" class="list">Load sample_1</a></li> <li><a href="javascript:sample_2()" class="list">Load sample_2</a></li> </ul> <h2>People</h2> <ul class="list" id="PersonNewsFeed"> </ul> <script src="js/jquery-1.7.1.min.js"></script> <script src="js/mustache.js"></script> <script src="js/main_mustache_complex.js"></script> <script id="content-template" type="text/template"> <ol> {{#result}} {{#people}} {{#person}} <li> {{#image}} {{#code}} IMG ({{code}}) => <img src="{{url}}" /><br> {{/code}} {{^code}} no img {{/code}} {{/image}} TITLE => <a href="{{url}}/" title="{{title}}">{{title}} </a> <br><br> BODY escaped HTML => {{{body}}} <br><br> BODY unescaped HTML => {{body}} <br><br> TAXONOMY => {{#taxonomy}} {{#tags}} <span class="tag"><a class="no-decoration" href="{{tid}}">{{name}} ({{tid}})</a></span> {{/tags}} {{/taxonomy}} <br> GEOLOC => {{#geoloc}} LAT, LONG => ({{latitude}}, {{longitude}}) <br> {{#address}} GEOLOC => CITY, COUNTRY => <span class="tag">{{locality}} -[{{country}}]</a></span><br> {{/address}} {{/geoloc}} </li> {{/person}} {{/people}} {{/result}} </ol> </script> </body> </html> |
Conclusion : Cela peut sembler assez bousinesque
et décevant
de se donner tant de mal pour un si piètre résultat en terme UX. Disons-le les templates sont assez moches. Toutefois, l’objectif était de valider rapidement des règles fonctionnelles, à l’aide de Mustache, en rendant des écrans plus “parlants” qu’un aride flux JSON, de ce point de vue la valeur ajoutée en terme fonctionnel est indéniable.
En savoir plus
- Le site officiel de
mustache
http://mustache.github.io/ - Tutorial: HTML Templates with Mustache.js (excellent)
http://coenraets.org/blog/2011/12/tutorial-html-templates-with-mustache-js/ - Add IF/ELSE function upon value dans mustache
https://github.com/janl/mustache.js/issues/311 - Installing Mustache.tmbundle dans textmate
https://gist.github.com/defunkt/323624 - Des explications sur les Mustache Tags
https://github.com/bobthecow/mustache.php/wiki/Mustache-Tags - La fonction jQuery.getJSON() dans jQuery
http://api.jquery.com/jQuery.getJSON/ - Semantic templates with Mustache.js and Handlebars.js par Martin Brennan
http://www.martin-brennan.com/semantic-templates-with-mustache-js-and-handlebars-js/ - How to Load Mustache.js Templates From an External File with jQuery
http://www.levihackwith.com/how-to-load-mustache-js-templates-from-an-external-file-with-jquery/ - Quick Tip: Using the Mustache Template Library
http://net.tutsplus.com/tutorials/javascript-ajax/quick-tip-using-the-mustache-template-library/ - Easy HTML Templates with Mustache
http://www.elated.com/articles/easy-html-templates-with-mustache/ - Introduction à Punch – Punch is a simple, intuitive web publishing framework that will delight both designers and developers.
http://laktek.github.io/punch/ - Une vidéo sur JavaScript and JSON tutorial: JavaScript templating with mustache.js | lynda.com
http://corel.tv/javascript-and-json-tutorial-javascript-templating-with-mustache-js-lynda-com/ - La même vidéo sur youtube – JavaScript and JSON tutorial: JavaScript templating with mustache.js
http://www.youtube.com/watch?feature=player_embedded&v=xIJuDcA6YBo#t=214 - A JSON Tutorial. Getting started with JSON using JavaScript and jQuery
http://iviewsource.com/codingtutorials/getting-started-with-javascript-object-notation-json-for-absolute-beginners/ - Introduction to JavaScript Templating with Mustache.js
http://iviewsource.com/codingtutorials/introduction-to-javascript-templating-with-mustache-js/ - L’extension JSONView pour Chrome, portage de l’excellent extension firefox original
https://chrome.google.com/webstore/detail/jsonview/chklaanhfefbnpoihckbnefhakgolnmc - L’extension firefox JSONView
http://benhollis.net/software/jsonview/