Intermédiaire

Dynamisez vos pages web avec KnockoutJs : Un petit dans la cour des grands

 

koLorsqu’ils ont à créer une page web, de nombreux développeurs, utilisant des plateformes de développement telles que .NET ou Java, accusent des phrases comme “moins j’utilise de Javascript, mieux je me porte”. Le fait est qu’on ne peut pas y échapper, ne serait-ce que pour la manipulation, parfois complexe, des éléments du DOM. De plus, aujourd’hui, de nombreux frameworks JS existent pour faciliter la prise en main et le développement de pages web. L’avantage est qu’ils offrent une belle expérience utilisateur de navigation avec des éléments qui “bougent” dans la page au simple clic sur une option. KnockoutJs (que nous abrégerons KO par la suite) fait partie de ces frameworks, simple à prendre en main et fluide, c’est lui que je vais introduire à travers cet article.

A propos de KnockoutJs

KO est un framework Javascript open source qui a été créé par Steve Sanderson et publié pour la première fois en 2010. Son implémentation repose sur le pattern MVVM (Model-View-ViewModel).

 

MVVM en bref

Le patron de conception Model-View-ViewModel est le cousin du Model-View-Controller. Il facilite la séparation entre l’interface graphique et la couche logique d’une application web. Sa logique repose sur le data-binding entre la View et la ViewModel, ce qui permet d’avoir une application riche et “up-to-date”.

 

mvvm_diagram-png

 

La vue représente le dernier maillon de la chaîne. Elle est constituée d’éléments graphiques comme des listes déroulantes ou des boutons, bref tous les éléments affichés sur le navigateur.

Le modèle est la couche de données de votre application web. Il n’a aucune connaissance de la vue.

La vue-modèle joue le rôle de médiateur entre la vue et le modèle. Elle gère des objets qui se “lient” avec les données du modèle et qui interagissent avec les éléments HTML de votre page web.

 

Sa place dans la cour des grands

L’article n’a pas pour but de comparer l’efficacité de KO face à celle des autres frameworks existants, tels que Angular, BackBone ou Ember, mais de présenter une alternative simple et fluide. Leur but commun principal est d’offrir une architecture reposant sur des événements et des fonctions callbacks liées. En ce sens, KO n’a pas à rougir face aux autres puisqu’il offre le même panel de possibilités :

  • Mettre à jour la page entière ou juste un composant des Views (intrinsèquement des ViewModels) quand l’utilisateur change les valeurs
  • Manipuler la présentation (hide, show, effets visuels) à chaque action utilisateur
  • Afficher des éléments dont le DOM est complexe sous forme de widgets et bien d’autres

Le fonctionnement de KO repose sur 3 principes qui constituent les piliers du framework :

  • Observables & Dependancy Tracking
  • Declarative Bindings
  • Les Templates

Nous aborderons chacune de ces notions dans la suite de l’article. Mais commençons par la création d’une View Model avec KO.

 

Observables & Dependancy Tracking

Observable

“Observable” est un concept intrinsèque au pattern MVVM. Il est utilisé dans beaucoup de langages comme C#, Java ou Javascript, et permet de notifier des changements de la propriété et de mettre à jour de manière automatique toutes les zones exploitant la propriété, dans votre UI.

Dans l’exemple suivant, on déclare une propriété observable qui s’appelle “Nom”.

function ...(){
  ...
  this.Nom = ko.observable();
  ...
};

Basé sur ce principe, on peut créer des computed variables, qui sont des fonctions résultantes des propriétés observables.

function ViewUserModel(){
  ...
   //observables
   this.nom = ko.observable();
   this.prenom = ko.observable();

   //computed variable
   this.nomPrenom = ko.computed(function(){
   return this.Nom() + this.Prenom();
  ...
 });
});
}

var user = new ViewUserModel();

Dans le code précédent, je crée un objet ViewUserModel, qui possède deux variables observables “nom” et “prenom”, et une computed variable “nomPrenom” qui résulte de la concaténation des deux dernières. La lecture d’une propriété d’un observable se fait en rajoutant des parenthèses à la fin.

 

Fonctionnement du Dependancy Tracking

A la déclaration d’un observable ou d’une computed variable, KO invoque un objet statique interne qui assigne un nouvel identifiant à l’élément, c’est le traqueur de dépendances  (lors de votre implémentation, vous n’aurez probablement pas à y toucher). C’est lui qui se charge de la création ou de la lecture des différents objets au sein de la vue. Quand une dépendance est créée, un nouveau contexte est placé dans une pile de dépendances. C’est ce qui permet de créer les dépendances entre objets. Lors de la lecture d’un objet, une fonction de callback passe en paramètres les dépendances et leurs identifiants respectifs. Lors d’une écriture, la dépendance est réévaluée et mise à jour.

var object = {
A : ko.observable(true),
B : ko.computed(funtion(){
  if(this.A()){
   return "OK" ;
  }
}

Dans le code précédent, on instancie un objet observable A à true, et une computed variable B. Tant que A est évaluée à false, le traqueur de dépendances ne gère qu’un contexte dans la pile, mais quand il est à true, un nouveau contexte est ajouté et une dépendance entre B et A est créée.

Maintenant que nous savons créer ces objets nous allons voir comment les lier au DOM de notre page HTML.

 

Tout au long de l’article, nous allons suivre l’implémentation d’une page dynamique, afin d’illustrer les notions abordées. N’hésitez pas à jouer avec les valeurs pour observer les changements.

 

Declarative bindings

La syntaxe data-bind

Comme nous l’avons évoqué dans la présentation du pattern MVVM, le lien entre la ViewModel et la View est réalisé via le mot clé “data-bind“.

L’exemple suivant montre comment est liée la valeur d’une variable observable “nom” à un élément texte du dom :

<span data-bind="text: nom"> </span>

La syntaxe est composée du nom de la déclarative de binding, ici “text”, et de la variable observable qui lui est affectée. La démonstration est par .

Les déclaratives sont, au sens KO,  implémentées comme de simples computed variables. Ce qui veut dire qu’elles ont une dépendance à la variable observable affectée. A chaque ré-évaluation de la variable, la dépendance est elle aussi ré-évaluée par le traqueur de dépendances.

Contrôlez la valeur des éléments du DOM

Dans le code précédent, nous avons utilisé le biding text, qui permet d’afficher la valeur de la variable observable. Allons un peu plus loin et essayons de lier la déclarative text à une computed variable, qui détermine la valeur à afficher

var viewModel = function(){
    var self = this;
    self.nom = ko.observable('Nom');
    self.prenom = ko.observable('Prenom');

    self.fullname = ko.computed(function(){
        return self.prenom() + ' ' + self.nom(); 

    });

Dans l’exemple précédent, le nom complet est déterminé par la computed variable fullname, qui est évaluée à chaque fois qu’une modification est effectuée sur l’une des variables observables nom et prenom.

On utilise ensuite notre objet viewModel en spécifiant à KO que l’on applique les liaisons:


   ko.applyBindings(new viewModel());

Il y a deux bonnes pratiques à avoir lorsqu’on applique les liaisons :

  • Faire le ko.applyBinding() une fois que le dom est chargé
  • Spécifier à quelle section s’applique le binding de l’objet

 

$( document ).ready(function() {
   //binding sur la div d'identifiant container
   ko.applyBindings(new viewModel(), document.getElementById("container"));
});

 

Si on ne spécifie pas à quelle section on souhaite appliquer les bindings, ces derniers se feront sur le corps (body) de votre page.
Dans une application avec plusieurs vues au sein d’une même page, cela posera des problèmes car il n’est pas possible d’appliquer le binding plusieurs fois à une même section HTML.
Pensez donc à spécifiez l’identifiant de la section sur laquelle vous voulez faire les liaisons.

Voici la vue correspondante :

<div id="container">
    <p>
        <input type="text" data-bind="value : nom"/>
    </p>
    <p>
        <input type="text" data-bind="value : prenom"/>
    </p>
    <!-- binding de la computed variable à un élément span -->
    Hello <span data-bind="text: fullname"></span> !
</div>

Les valeurs de ces deux observables sont liées par la déclarative “value” à deux inputs de type texte. Nous pouvons aussi nous amuser à contrôler le css des éléments du DOM grâce aux computed variables. C’est par .

 

Controllez le worflow de votre page

KO vous permet d’interagir avec les éléments de votre DOM, via des directives de bindings. Vous pouvez conditionner l’affichage d’éléments HTML, lier un contexte à une div, itérer sur une collection pour dupliquer des markups ainsi que tout ce que vous avez toujours fait et plus.

 

Conditionnement (if/ifnot)

La directive If permet de gérer l’affichage de la section liée à la directive. Son fonctionnement ressemble à la propriété CSS display : si votre observable est évalué à false, les markups du bloc lié ne feront pas partie du DOM généré.

  Hello M<span data-bind="if: isFemale">lle</span>

Dans le code ci-dessus, on affiche l’élément  span uniquement si la variable observable est évaluée à true.
Petite démo par ici

La directive IfNot permet d’afficher un bloc quand la variable est évaluée à false :

  Hello M<span data-bind="ifnot: isMale">lle</span>

est équivalent à :

  Hello M<span data-bind="if: !isMale">lle</span>

 

Les itérations (foreach)

La directive foreach permet de dupliquer une section de markup autant de fois qu’il y a d’items présents dans la collection d’observable.

 

 <table>
   <tbody data-bind="foreach: users">
    <tr>
     <td data-bind="text: prenom"></td>
     <td data-bind="text: nom"></td>
     ...
   </tr>
  </tbody>
 </table>

Dans le code ci-dessus, un élément table est lié à une collection d’observable “users”. Une ligne est créée pour chaque utilisateur avec les propriétés de chacun des items. La démo c’est par .

 

Les syntaxes

Il est possible d’utiliser plusieurs syntaxes pour gérer le workflow de votre page avec KO :

  • La syntaxe “Native”
  • La syntaxe “ContainerLess”

La syntaxe Native

...
  <tbody data-bind="foreach: users">
...

La syntaxe native est celle que nous avons utilisée dans le code précédent pour créer notre tableau. On utilise cette syntaxe quand on crée une structure HTML basée sur des relations “parent-enfants”. C’est cette syntaxe qu’il faut utiliser pour lier un élément de type Nav/Ul/Li par exemple.

La syntaxe ContainerLess

...
  <!-- ko foreach: array -->
     ...
  <!-- /ko -->
...

A l’inverse, la syntaxe ContainerLess est utilisée quand il n’y a aucune structure HTML de type “parent-enfants”. Elle commence par une balise de commentaire, suivie du mot-clé ‘ko’, puis de la directive à appliquer (foreach dans le dernier exemple). Il faut ensuite indiquer à KO que la section est terminée en ajoutant le markup ‘/ko’, toujours en commentaire.

Dans cet exemple, vous remarquerez la présence de la propriété “$data” qui permet d’accéder à la valeur de chacun des items dans une collection. Il existe plusieurs propriétés spéciales comme celle-ci, dont vous aurez probablement besoin. Vous pourrez les trouver ici en temps voulu.

 

Les événements

Dans l’exemple précédent, nous avons utilisé sans le mentionner, le binding “checked“, qui est un événement Javascript. Sa présence ne vous a peut-être pas posé de soucis de lecture et c’est typiquement ce qui est appréciable dans Knockout.

La plupart des événements que vous avez l’habitude d’utiliser sont implémentés de la même manière.

Les plus courants sont implémentés nativement en tant que binding :

  • checked
  • enable
  • click
  • submit

On peut, par exemple, associer une fonction “showSomeThings”, qui afficherait des éléments jusqu’ici cachés dans le DOM.

  <button data-bind="click: showSomeThings">Cliquez</button>

Pour les autres types d’événements, il faut utiliser le binding “event” et spécifier le nom de l’événement et la fonction appelée.

  <button data-bind="event:{ mousedown : downClick, mouseup: upClick } ">Cliquez</button>

Faites le test.

 

Les templates

Très souvent dans nos développements web, il est nécessaire de créer des structures HTML complexes, par exemple pour afficher une famille d’objets avec une arborescence, créer un tableau ou créer un repeater pour une collection d’objets. Bref, c’est très fréquent. Le templating est une méthode permettant de créer des blocs de markups “réutilisables”. C’est une méthode “propre” qui favorise la compréhension et la maintenabilité du code. Ces blocs de markups sont appelés “Engines“. KO enverra les valeurs des observables aux engines et elles seront affichées dans le DOM de votre page.

L’exemple suivant illustre l’implémentation d’un template:


 <!-- utilisation du template user-template -->
 <div data-bind="template: { name: 'user-template', data: user}"></div>

<!-- définition du template -->
<script type="text/html" id="user-template">
    <figure data-bind="attr: { id: prenom }">
        <img data-bind="attr: {src : avatar}" class="avatar"></img>
        <figcaption data-bind="text: fullname"></figcaption>
    </figure>
</script>

var User = function(nom, prenom, avatar){
    var self = this;
    self.nom = ko.observable(nom);
    self.prenom = ko.observable(prenom);
    self.avatar = avatar;
     self.fullname = ko.computed(function(){
        return self.prenom() + ' ' + self.nom();
     });
}

var appViewModel = {
  user: ko.observable(new User("Martin", "Jean", "img"))
}

Les  mots clés:

  • name : Correspond à l’id du template que vous souhaitez lié.
  • data : Permet de fournir un objet au template, afin d’y afficher ses propriétés.

Il y a encore quelques autres mots-clés que l’on peut utiliser. On peut, par exemple, intégrer l’itération d’une collection directement dans la directive de templating.

Vous trouverez dans ce snippet, une implémentation complète synthétisant l’ensemble des principes évoqués dans l’article.

 

Pour finir…

Knockout est très facile à prendre en main et fluide à utiliser, ce qui fait sa force, et nous avons pu le voir tout au long de l’article. Il représente une alternative simple pour construire des “single page applications” (SPA), face aux grands comme Angular, Backbone ou Ember.

Je viens d’un monde .Net et j’ai commencé l’apprentissage des SPA avec Angular.  J’ai eu l’opportunité d’expérimenter Knockout pour la première fois dans une mission, nous l’avons couplé avec Asp.Net MVC 5 et je dois dire que ça marche plutôt très bien. Il est d’ailleurs utilisé par de nombreux sites comme Asos. Jetez un œil aux statistiques.

Bien sûr, ce n’est qu’une introduction au framework. Vous pouvez aller plus loin en consultant le site officiel ou le blog de Ryan Niemeyer, qui est très actif sur le framework.

 

 

 

Image Knockout: M.Scapolan

Nombre de vue : 1328

COMMENTAIRES 3 commentaires

  1. Amélie dit :

    Un article parfait pour connaitre les possibilités offertes par KO. En plus d’un contenu complet et abordable, j’ai beaucoup apprécié le dynamisme de l’article que créent les exemples jsfiddle préremplis.
    Le ton de l’auteur est indéniablement pédagogique et entrainant. Bravo !

  2. Arnaud JOLLY dit :

    Un des autres points sympa de KnockoutJS est de pouvoir déclarer ses propres “bindings”.
    Ainsi, on peut définir des bindings “date-picker”, “auto-complete”, etc. permettant d’exporter toute la mécanique Javascript de mise en place de ce type de composants en laissant Knockout faire pour nous. Vous les définissez tout de même une fois et ensuite on s’abstrait de ces contraintes qui en rebutent ou effrayent plus d’un.

    Prenez bien le temps de comprendre ce que vous manipulez sous peine de vous retrouver fréquemment avec des erreurs de type “mavariable is not a function”. 😉

    En effet, on assigne une valeur à un observable “mon_observable” par l’instruction “mon_observable( valeur )” sauf que les variables javascript ne sont pas fortement typées. Elles peuvent donc représenter des fonctions (les observables sont des fonctions) comme des types Javascript basiques tels des Number, des String ou autre.
    Donc si vous faites un “mon_observable = valeur” à un endroit de votre code, vous écrasez l’observable par une valeur simple. Cela a pour conséquence que les prochains bouts de code qui chercheront à utiliser l’observable vont planter car ce ne sera plus un observable mais bien une valeur simple (donc plus une fonction). Une autre conséquence sera que vos vues créées avec votre observable (i.e. le référencant via un data-bind) ne seront plus mises à jour automatiquement avec les changements de cette valeur car la vue est basée sur la “fonction” qui était référencée par la variable “mon_observable” et change que si cette fonction est appelée avec un argument, or votre changement n’a pas appelé la fonction (elle a juste écrasé la valeur de la variable).

    Gardez à l’esprit que KnockoutJS apportent une surcouche minimale pour qu’on puisse s’amuser avec des mises à jour automatiques, mais cela reste tout de même du Javascript 😉

  3. […] J’ai eu l’opportunité de travailler à la transition d’un site Web de JavaScript à TypeScript, site sur lequel j’intervenais depuis un an et demi en la qualité de développeur full stack. J’ai alors constaté le relatif manque d’informations concrètes pour réaliser sereinement cette transition. Cet article vise à combler en partie ce manque, en partant de mon retour d’expérience sur ce projet d’une durée d’un an et des techno utilisées : ASP.NET MVC avec les frameworks jQuery et Knockout-JS. […]

AJOUTER UN COMMENTAIRE