Polymer et les Web Components

Quand on démarre le développement d’un site web, on se pose toujours les mêmes questions en ce qui concerne la couche de présentation : quel type de solution je mets en oeuvre ? Aujourd’hui, le framework Javascript est bien souvent la réponse, et pas seulement par effet de mode. En effet, ce type de solution offre beaucoup d’avantages : des interfaces riches et réactives, un découplage complet entre l’UI et le backend, un meilleur support des terminaux mobiles, et bien souvent des communautés très actives. De plus, certains sont orientés « composants graphiques », et leur catalogue est très fourni : menus déroulants, layout, grilles, tooltip, champs de formulaires spécialisés, boutons, cartographie, …

Mais le framework Javascript, c’est surtout :

  • Du « vendor lock-in », et donc un risque sur la pérennité (voire même sur les montées de version si les API changent);
  • Des solutions qui ne sont pas des standards du Web;
  • Pas de réutilisabilité des composants que l’on développe, en dehors d’une solution basée sur la même technologie.

Et sur ce dernier point tous les développeurs seront d’accord : développer l’UI d’un site web, c’est souvent réinventer la roue…

La réponse à toutes ces problématiques, c’est un standard W3C appelé Web Components.

Présentation

Les Web Components sont basés sur 4 évolutions majeures, qui adressent chacune un problème récurrent du développeur Web :

Capture

Vous l’aurez compris, le standard Web Components va nous permettre de créer nos propres composants HTML, de les réutiliser et les distribuer, tout en garantissant la cohérence globale du DOM.

Un nouveau standard : quel niveau de support ?

En toute objectivité, le support natif est limité, et comme souvent, c’est Chrome et Opera qui mènent la danse.

Heureusement, les librairies polyfill sont là pour combler l’absence de support, et vont se charger de gérer la bascule automatique entre le natif et le support spécifique. Elles sont disponibles sur le portail http://webcomponents.org/polyfills/

Capture2

Le framework Polymer

Polymer est l’implémentation de Google des Web Components (parmi les concurrents on pourra citer Xtags, de Mozilla).

Désormais disponible en version 1.1, et estampillée production ready, son API est stabilisée.

Polymer, c’est aussi un catalogue de composants sur étagère, regroupés par catégorie, avec notamment des composants material design, des champs de saisie de formulaires, des boutons, des onglets, … (https://elements.polymer-project.org/)

Un premier composant

Commençons par le setup : pour ma part j’utilise Bower pour la gestion des dépendances Javascript, et le module Python http.server pour lancer rapidement un serveur Web dans le répertoire courant.

Pour récupérer Polymer, j’ai donc initialisé le fichier bower.json comme suit :

1
2
3
4
5
6
7
8
{
   "name": "my-first-component",
   "version": "0.0.1",
   "description": "My first Polymer component",
   "dependencies": {
      "polymer": "Polymer/polymer#1.1.4"
   }
}

Et je lance un bower update pour récupérer les dépendances. Polymer déclarant lui-même une dépendance vers les polyfill, je peux donc initialiser ma page principale index.html comme suit :

1
2
3
4
5
6
7
8
9
10
11
12
13
<!doctype html>
<html>
   <head>
      <title>My first component</title>
      <script src="bower_components/webcomponentsjs/webcomponents.js"></script>
      <link rel="import" href="bower_components/polymer/polymer.html"
      <link rel="import" href="my-first-component.html"
   </head>
   <body unresolved>
      <span>Voici mon premier composant :</span>
      <my-first-component mytext="Hello, World !"></my-first-component>
   </body>
</html>

Première illustration : l’import de ressources HTML.
C’est simple, et ça permet de découper proprement son markup.

Maintenant, rentrons dans le dur : le composant my-first-component.html.
Il est basique, il ne fait qu’afficher un champ de saisie, et le résultat de la saisie :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<dom-module id="my-first-component">
   <template>
      <style>
         :host {
            display: block;
            border: 1px solid red;
         }
         span {
            font-weight: bold;
         }
         span.result {
            font-weight: normal;
         }
      </style>
      <div>
         <span>Please input value : </span>
         <input id="thevalue" type="text" value="{{mytext::input}}"/>
      </div>
      <div>
         <span>My input is : </span>
         <span class="result">{{mytext}}</span>
      </div>
   </template>
   <script>
      Polymer({
        is: 'my-first-component',
       properties: {
            mytext : {
           type : String,
           value: "default value"
          }
        }
      })
   </script>
</dom-module>

Le résultat :

cap3

Ceci mérite quelques explications !

Pour commencer, le découpage :

  • On commence par la définition du composant via la balise dom-module. L’id correspond à la balise HTML que vous utiliserez plus tard (Polymer impose que l’id contienne au moins un tiret);
  • Le template : celui-ci embarque à la fois les styles CSS et le markup HTML;
  • Le scripting, qui permet d’enregistrer le composant. Dans cette partie, on a défini une propriété du modèle, que l’on peut également passer en paramètre de l’appel du composant.  On peut également gérer des évènements liés au cycle de vie du composant, ajouter ses propres fonctions, …

Ensuite la gestion des styles CSS : la nouveauté, c’est les conventions liées au Shadow DOM, qui sont très clairement expliquées sur ce site : http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-201

On notera par exemple :

  • Que l’on peut styler l’élément qui  va contenir le Shadow DOM via le sélecteur :host. Ici, j’ai ajouté un bord rouge;
  • Que le local scoping CSS est respecté : imaginez le désastre si on définissait un style directement sur l’élément span dans une page complète… On voit aussi que ça va considérablement simplifier le CSS du site. Finies les feuilles de style à rallonge, impossibles à maintenir…

Notez que l’on peut également utiliser les sélecteurs suivants :

  • this.$ pour tous les nœuds statiques (sous-entendu définis avec un id dans le template);
  • this.$$(selector) pour les nœuds créés dynamiquement (par exemple avec les directives dom-repeat ou dom-if, non présentées ici).

Enfin, le databinding, sur lequel il faut s’étendre un peu, car c’est très puissant, mais pas forcément aussi intuitif que dans un framework comme AngularJS par exemple. En fait, tout est basé là aussi sur l’encapsulation. Des composants qui appellent des composants. Et là ça devient un peu plus clair.

Polymer sait gérer un binding :

  • One-way avec les crochets [[var]] : du composant père vers les fils uniquement;
  • Automatic avec les accollades {{var}} : one-way ou two-way selon la configuration de la propriété cible concernée.

Dans l’exemple, quand je change le contenu de l’input, le résultat en dessous change !

Notez que j’ai utilisé mytext:input, car l’input est un élément natif et qu’il faut dans ce cas-là préciser l’événement à gérer. Dans le catalogue d’éléments de Polymer, il existe des champs de saisie de formulaires qui gèrent déjà le binding.

Ajoutons maintenant un composant fils pour voir ce que ça donne : j’ai créé un deuxième composant my-second-component.html comme suit :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dom-module id="my-second-component">
   <template>
      <input id="thevalue" type="text" value="{{mytext::input}}"/>
   </template>
   <script>
    Polymer({
      is: 'my-second-component',
      properties: {
          mytext : {
        type : String,
        value: "default value",
        notify: true
      }
      }
    })
   </script>
</dom-module>

Ici, la propriété mytext est configurée pour potentiellement gérer du two-way binding (notify à true).
J’ai ensuite ajouté l’import dans index.html, et j’ai ajouté un div dans le template de my-first-component.html, comme suit :

1
2
3
<div>
   <my-second-component mytext="{{mytext}}"></my-second-component>
</div>

Avec les accolades, je suis donc dans une configuration two-way.

Si j’affiche le résultat, j’ai maintenant des champs de saisies et un résultat qui se synchronisent, que je saisisse dans le premier ou le deuxième input.

cap4

Si je modifie le div appelant comme ceci :

1
2
3
<div>
   <my-second-component mytext="[[mytext]]"></my-second-component>
</div>

La saisie dans le premier input met à jour tout le reste. La saisie dans le deuxième input ne met pas à jour le reste.

Notez au passage qu’entre les deux composants, j’ai les mêmes noms de propriété, et ça fonctionne quand même : l’isolation fonctionne aussi à ce niveau-là.

En synthèse

Les Web Components sont relativement simples à mettre en oeuvre avec Polymer.

Il y a cependant encore beaucoup de choses à présenter sur ce framework :

  • La gestion des événements;
  • La gestion du cycle de vie d’un composant;
  • Les directives de layout basées sur FlexBox, pour mettre en page rapidement vos composants;
  • Les behavior : pour définir des comportements Javascript réutilisables et extensibles.

Et des questions :

  • Comment faire communiquer des éléments entre eux sans encapsulation ?
  • Comment communiquer avec quelques chose qui n’est pas un composant ?
  • Quel type de backend ?
  • Peut-on mélanger un Angular/ReactJS/ExtJS/ …. avec Polymer ?.

L’adoption ?

Pas encore tout à fait… Enfin si… Chez Google bien sûr. Et les équipes Polymer travaillent en proximité avec les équipes Chrome. A noter également que la version 2 d’Angular (réécrite intégralement, encore en alpha pour le moment) fait la part belle aux Web Components.

C’est un signal fort envoyé par un géant du Web…….

Références utiles

Leave a Reply

Your email address will not be published. Required fields are marked *