Utilisation de gabarit

J'ai étudié un peu knockout.js aujourd'hui suite à une question sur irc ##javascript.

Debug de 2.3.0: http://knockoutjs.com/downloads/knockout-2.3.0.debug.js

template

Il y a quatre partie pour utiliser un template (gabarit):

  1. Le gabarit (html)
  2. Le lieu d'insertion du résultat dans le document HTML
  3. Les données (javascript)
  4. La commande pour exécuter le tout (javascript).

Voici un exemple simple avec les 4 éléments (knockout-applybindings.htm)

<script type="text/html" id="template-id">
    <div>
        <span data-bind="text: title"></span> /
        <span data-bind="text: $parent.title"></span>
    </div>
</script>

<div id="insertdiv" data-bind="template: { name: 'template-id', foreach: items}"></div>
var data = {
    title: 'data title',
    items : [
        {id:1, title:'a'},
        {id:2, title:'b'},
        {id:3, title:'c'},
        {id:4, title:'d'}
    ]
};
ko.applyBindings(data);

Voici comment la commande procède:

  1. ko.applyBindings(data) : pour tous les éléments du document, trouver ceux avec un attribut data-bind="template" et appliquer les données "data".
  2. lorsqu'on trouve un attribut data-bind="template", appliquer le gabarit avec un id="template-id" pour chaque (foreach) éléments data.items.
  3. pour chaque éléments de data.items, créer un div avec une balise span qui contient le titre de l'item, un / et une balise span avec le titre de data ($parent.title).
  4. le code généré est donc:
        <div>
            <span data-bind="text: title">a</span> / <span data-bind="text: $parent.title">data title</span>
        </div>
    
        <div>
            <span data-bind="text: title">b</span> / <span data-bind="text: $parent.title">data title</span>
        </div>
    
        <div>
            <span data-bind="text: title">c</span> / <span data-bind="text: $parent.title">data title</span>
        </div>
    
        <div>
            <span data-bind="text: title">d</span> / <span data-bind="text: $parent.title">data title</span>
        </div>
    

Allons un peu plus loin...

Imaginons que l'on veulent aller un peu plus loin et n'afficher que les items avec un id > 2. dans notre lieu d'insertion, foreach: items signifie que le gabarit sera appliqué pour chaque items de "data". items est un object de type array() et possède donc la méthode filter. On peut donc écrire:

<div id="insertdiv" data-bind="template: { name: 'template-id', foreach: items.filter(function(el){return el.id &gt; 2})}" />

Maintenant, il serait peut-être meilleur d'ajouter notre nombre "2", dans un attribut HTML de la balise div, mais comment accéder à ce nombre? Et bien, knockout.js défini la balise div comme étant la variable $element. Si on ajouter data-greaterthan="2", alors, on peut utiliser $element.getAttribute('data-greaterthan') pour avoir le 2 (conversion automatique en nombre).

<div id="insertdiv" data-greaterthan="2" data-bind="template: { name: 'template-id', foreach: items.filter(function(el){return el.id &gt; $element.getAttribute('data-greaterthan'); })}" />

Allons encore plus loin

Nous voulons un filtre un peu plus complexe et il devient compliquer de l'écrire en HTML. Plaçons donc notre fonction dans le code javascript, et appelons la fonction dans le foreach:. La fonction doit être globale:

window.filter_my_items = function(items, $element) {
  return items.filter(function(el){
    if (el.id > $element.getAttribute('data-greaterthan') || el.id == 1) {
     return true;
    }
    return false;
  });
}
<div id="insertdiv" data-greaterthan="2" data-bind="template: { name: 'template-id', foreach: filter_my_items(items, $element) })}" />

Note que dans la déclaration du template, nous pouvons avoir accès à:

  • $context.$data :
  • $context.$parents :
  • $context.$root :
  • $context.ko :
  • $element : il s'agit de l'élément avec un data-bind.

ko.applyBindingsToNode()

Pour éviter de parcourir le document en entier avec ko.applyBindings, il peut être plus efficace d'exécuter le bindings sur un seul élément. Il faut utiliser la fonction ko.applyBindingsToNode().

var node = document.getElementById('insertdiv');
ko.applyBindingsToNode(node, null, data);

Enlever le data-bind du code HTML

Il pourrait être plus élégant d'enlever le data-bind du code HTML, on pourrait donc ajouter le data-bind juste avant d'exécuter le binding et utiliser data-bind-template pour spécifier le gabarit. (knockout-applybindingstonode.htm)

<div id="insertdiv" data-greaterthan="2" data-bind-template="template-id" />
var node = document.getElementById('insertdiv');
insertdiv.setAttribute('data-bind', "template: { name: $element.getAttribute('data-bind-template'), foreach: filter_my_items(items, $element) }");
ko.applyBindingsToNode(node, null, data);

Enlever le data-bind

On peut aussi enlever complètement le data-bind en utilisant le second paramètre de la fonction applyBindingsToNode. (knockout-applybindingstonode2.htm)

<div id="insertdiv" data-greaterthan="2" />
var node = document.getElementById('insertdiv');
ko.applyBindingsToNode(node, { template: { name: 'template-id', foreach: filter_my_items(data.items, node) } }, data);