18 novembre 2008

Comment traiter du JSON pour produire du HTML en Javascript ?

Lors d'un développement de type AJAX, on se trouve rapidement confronté à la problématique suivante :

  • Je récupère des données au format JSON
  • Je les affiche en HTML

Question toute bête à priori : mais comment faire cela ?

Old School

On parcourt la structure Javascript et on produit le HTML avec des fonctions comme document.write ou element.appendChild

Exemple :


var data = [ "Meuse",   "Vosges",   "Moselle",   "Meurthe & Moselle" ];

document.write('<ul>');
for(i = 0; i < data.length; i++) {
    document.write('<li>'+data[i]+'</li>');
}
document.write('</ul>');

ou


<ol id="list">
</ol>
        
ol = document.getElementById("list");
for(i = 0; i < data.length; i++) {
    li = document.createElement("li");
    li_text = document.createTextNode(data[i]);
    li.appendChild(li_text);
    ol.appendChild(li);
}

Temps modernes : les frameworks

Bien sûr, maintenant, plus personne n'écrit du Javascript sans utiliser des frameworks comme Prototype ou JQuery.

Cependant, on écrira la même chose qu'auparavant de manière plus simple :

Avec Prototype :


<ol id="list3"> </ol>

data.each(function(item) {
    $('list3').insert(new Element('li').update(item));
});

Avec JQuery :


<ol id="list2"> </ol>


jQuery.each(data, function(i,item){
    jQuery("#list2").append(jQuery("<li>"+item+"</li>"));
});

On remarque ici la présence presque obligatoire d'une balise html d'ancrage. (ici, la balise &lt;ol id="listX"&gt; )

Le futur : les templates

A l'usage, les méthodes précédentes posent évidement le problème de la séparation du fond et de la forme : un problème bien connu en PHP et plus généralement pour la conception de site web traditionnel. (cf l'article Wikipedia sur les templates)

Alors nous voilà revenu au temps du choix d'un moteur de template !

Pour faire ma sélection dans cette liste, il y a plusieurs critères :

  • une preuve de vie (évolution des versions par exemple, activité sur le site)
  • une (bonne) documentation à jour
  • un fonctionnement multi- browser
  • une capacité à traiter des structures de données complexes

J'ai choisi de comparer :

  • JSrepeter
  • jstemplate
  • Pure

Pour chacun d'entre eux, j'essayerai de traiter le tableau de données suivant :


var data = {
   "items":[
      {
         "titre":"titre 1",
         "img":"http:\/\/localhost\/images\/01.jpg",
         "list1":[
            "row 1", 
            "row 2"
         ],
         "list2":[
            {
               "nom":"nom 1"
            },{
               "nom":"nom 2"
            }
         ]
      },
      {
         "titre":"titre 2",
         "img":"http:\/\/localhost\/images\/02.jpg",
         "list1":[
            "row A", 
            "row B"
         ],
         "list2":[
            {
               "nom":"nom A"
            },{
               "nom":"nom B"
            }
         ]
      }
   ]
}

Compiler un template

Avec jsrepeter :


$('#test').fillTemplate(data);

Avec jstemplate :


var input = new JsEvalContext(data);
var output = document.getElementById('test');
jstProcess(input, output);

Avec pure :


$('#test').autoRender(data);

Afficher une valeur dans une balise ou l'attribut d'une balise

Avec jsrepeter :


<ol id="test">
    <li context='items' >
        <img src="${img}" />
        <span>${titre}</span>
    </li>
</ol>

Avec jstemplate :


<ol id="test">
    <li jsselect="items" >
        <img jsselect="img" jsvalues="src:$this" />
        <span jscontent="titre">xxx</span>
    </li>
</ol>

Avec pure :


<ol id="test">
    <li class="items">
        <img class="img@src" src="xxx" />
        <span class="titre">XXX</span>
    </li>
</ol>        
                                                                                

Traiter les sous listes avec un index numérique

Avec jsrepeter :

Impossible à faire ?
En tout cas pour le moment, je n'ai pas réussi.

Avec jstemplate :


<ol id="test">
    <li context='items' >
        <ol>
        <li jsselect="list1" jscontent="$this">XXX</li>
        </ol>
    </li>
</ol>

Avec pure :

En théorie (cf .démo) cela semble possible, mais la syntaxe est loin d'être triviale, j'abandonne pour le moment.

Traiter les sous listes avec un index alphanumérique

Avec jsrepeter :


<ol id="test">
    <li context='items' >
        <ol>
        <li context='list2'>${nom}</li>
        </ol>
    </li>
</ol>

Avec jstemplate :


<ol id="test">
    <li context='items' >
        <ol>
        <li jsselect="list2"  jscontent="nom">XXX</li>
        </ol>
    </li>
</ol>

Avec pure :
En théorie (cf .démo) cela semble possible, mais la syntaxe est loin d'être triviale, j'abandonne pour le moment.

Conclusion

A ce jour, pour moi, le meilleur système de template est jstemplate, car il est capable de traiter n'importe quel format de données tout en restant très simple à l'usage.

Malgré une apparente facilité, Pure est compliqué à utiliser. De plus, l'usage de l'attribut class prête à confusion et rend le template au final moins lisible.

Jsreapter possède la syntaxe la plus simple et il est surement le plus facile à appréhender. Malheureusement, il semble incapable de traiter les index numériques...