Introduction
Intérêt
Enrichir le Web avec de nouveaux tags
- Le standard HTML compte une centaine de balises seulement
Aider à la réutilisation de code – éviter le copier-coller
Principes
Créer de nouvelles balises
Encapsuler le code afin de masquer et isoler sa complexité
Pouvoir importer et déclarer les balises dans d’autres projets/pages
2
Introduction - Historique
Initié par Google depuis 2010 avec le projet Polymer,
relayé par Mozilla et d’autres acteurs du web
Basé sur des standards en cours au W3C
Chrome 36 premier navigateur compatible
La technologie « Polyfill » pour les navigateurs plus
anciens
Librairie webcomponents.js remplaçant platform.js (depuis fin 2014,
avec le transfert de la librairie de Polymer vers WebComponents.org)
3
4 normes à la base de WebComponent
4
Ecosystème HTML5
Custom element
HTML Template
Shadow DOM
HTML import
Polyfill
importer webcomponents.js
Framework facilitant la création de
Web Component
Polymer
X-Tag
Bosonic
Introduction
Qu’est-ce qu’un Web Component ?
Identifier les technologies permettant leur création
Il faut pouvoir
définir de nouvelles balises
ajouter du contenu
encapsuler sa définition
réutiliser les composants
6
7
Source http://webcomponents.org/
• Chrome: implémenté
• Opera: implémenté
• Firefox: en cours de dev.
• Safari: non implémenté
• IE: en cours de considération
Spécification Custom Elements
Custom Element - principes
Permet de définir nos propres balises HTML
Peut avoir un rendu ou non
Contient du code et/ou du contenu HTML
Pour le créer:
tag-name est le nom de la balise. Doit contenir au moins un ‘-’
options sont principalement le prototype et extend
retourne le constructeur qui permet d’instancier la balise
Et <element></element> ?
La spécification n’a jamais aboutie et semble être délaissée depuis août
2013
8
var constructor = document.registerElement(tag-name, options);
Custom Element – Déclaration
Instancier un Custom Element
De manière déclarative – la plus élégante
Dans le code via le constructeur
Dans le code via document.createElement() – la plus classique en JS
9
var MyComponent = document.registerElement(‘mon-composant');
var dom = new MyComponent();
document.body.appendChild(dom);
<script> document.registerElement(‘mon-composant'); </script>
<mon-composant></mon-composant>
document.registerElement(‘mon-composant');
var dom = document.createElement(‘mon-composant');
document.body.appendChild(dom);
Custom Element – Comportement
Ajouter des comportements
Utilisation de l’argument options
Créer un prototype dérivant de HTMLElement
Définir les nouveaux comportements à partir de ce prototype
- Attributs
- fonctions
10
document.registerElement(tag-name, options);
Custom Element – Comportement
Exemple de code:
11
var proto = Object.create(HTMLElement.prototype);
proto.nom = ‘Mon Composant';
proto.afficheNom = function() {
console.log(‘Nom de la balise : ' + this.nom);
};
document.registerElement(‘mon-composant', {prototype: proto });
var dom = document.createElement(‘mon-composant’);
Custom Element – Héritage
Hériter d’une balise HTML existante
déclarer dans options de registerElement() avec mot clé extends
dériver du prototype de l’élément HTML hérité
Utilisation
12
document.registerElement(‘mon-champs-saisie', {
extends: 'input',
prototype: Object.create(HTMLInputElement.prototype)
});
Doivent correspondre
<input is=“mon-champs-saisie”></input>
Custom Element – Cycle de vie
Des callbacks sur prototype pour nous aider à définir
notre Custom Element
createdCallback() appelé après la creation de l’élément
attachedCallback() appelé lors de l’attachement au DOM
detachedCallback() appelé lors du détachement au DOM
attributeChangedCallback() appelé lors d’un changement attribut
Exemple
13
var proto = Object.create(HTMLElement.prototype);
proto.createdCallback = function() {
var div = document.createElement('div');
div.textContent = ‘Le contenu de mon composant';
this.appendChild(div);
};
document.registerElement(‘mon-composant', {prototype: proto)});
<mon-composant>
<div>Le contenu de mon composant</div>
</mon-composant>
Custom Element – Exemple
14
var proto = Object.create(HTMLElement.prototype);
proto.createdCallback = function() {
this.innerHTML = "<style>.outer { border: 2px solid #347fac; border-radius: 1em; background: #347fac; font-size: 20pt;
width: 12em; height: 7em; text-align: center;}.title {color: white; font-family: sans-serif; padding: 0.5em;}.name {color:
black; background: white; font-size: 45pt; padding-top: 0.2em;} </style><b>Je suis un badge DNG</b><div
class='outer'><div class='title'></div><div class='name'></div></div>";
};
proto.attachedCallback = function(){
var self = this;
var b= document.querySelector("b");
b.addEventListener('click', function(ev) {
var event = new CustomEvent(‘click-title', { 'detail': ev });
self.dispatchEvent(event);
});
var titleDiv= document.querySelector(".title");
titleDiv.innerHTML = this.getAttribute("label");
var nameDiv= document.querySelector(".name");
nameDiv.innerHTML = this.getAttribute("nom");
};
document.registerElement(‘dng-badge', {prototype: proto});
<dng-badge label="Badge de:" nom="ZOE"/>
<script>
var dom = document.querySelector('dng-badge');
dom.addEventListener(‘click-title', function(e) {
alert('Clique sur le titre');
});
</script>
Custom Element – Conclusion
Custom Element permet de créer ses propres balises
HTML
Mixer des balises HTML dans des chaînes de caractères
(innerHTML) n’est pas une solution très viable pour des
interfaces complexes
15
HTML Template pour aider à la
création d’interface
16
Source http://webcomponents.org/
• Chrome: implémenté
• Opera: implémenté
• Firefox: implémenté
• Safari: implémenté
• IE: en cours de considération
HTML Template - Principe
Avoir un format prédéfini réutilisable
Ne pas avoir à recréer le même cadre à chaque fois
Concept existant dans le web, mais côté serveur comme
Apache Velocity en Java
Django en Python
Smarty en PHP
Et bien d’autres…
Peu de solutions côté client (exécuté dans le navigateur)
17
HTML Template - Principe
Une déclaration simple en utilisant les balises
<template>…</template>
Caractéristiques de cet élément
Son contenu n’est pas visible par le moteur de rendu du navigateur
Les scripts ne s’exécutent pas, les images ne sont pas chargées, …
Le contenu n’est pas considéré comme attaché au DOM
(document.getElementById() ou querySelector() ne fonctionnent pas)
Il peut être placé n’importe où dans la page HTML
Activer un template
18
var t = document.querySelector(‘template’);
var clone = document.importNode(t.content, true);
document.body.appendChild(clone);
HTML Template – exemple
19
<template id="badgeTemplate">
<link href="templateBadge.css" rel="stylesheet">
<b>Je suis un badge DNG</b>
<div class='outer'>
<div class='title'></div>
<div class='name'></div>
</div>
<script>
var b= document.querySelector("b");
b.addEventListener('click', function(ev) {
alert("clique sur le titre !");
});
alert("Template actif");
</script>
</template>
<script>
var content = document.querySelector('#badgeTemplate').content;
content.querySelector(".title").innerHTML = "Badge de:";
content.querySelector(".name").innerHTML = "ZOE";
document.body.appendChild(document.importNode(content, true));
</script>
templateBadge.css
.outer {
border: 2px solid #347fac;
border-radius: 1em;
background: #347fac;
font-size: 20pt;
width: 12em;
height: 7em;
text-align: center;
}
.title {
color: white;
font-family: sans-serif;
padding: 0.5em;
}
.name {
color: black;
background: white;
font-size: 45pt;
padding-top: 0.2em;
}
Exemple de script en utilisant la spécification Template
20
<script>
var proto = Object.create(HTMLElement.prototype);
proto.createdCallback = function() {
var content = document.querySelector('#badgeTemplate').content;
this.appendChild(document.importNode(content, true));
};
proto.attachedCallback = function(){
var self = this;
var b= document.querySelector("b");
b.addEventListener('click', function(ev) {
var event = new CustomEvent(‘click-title', { 'detail': ev });
self.dispatchEvent(event);
});
var titleDiv= document.querySelector(".title");
titleDiv.innerHTML = this.getAttribute("label");
var nameDiv= document.querySelector(".name");
nameDiv.innerHTML = this.getAttribute("nom");
};
document.registerElement(‘dng-badge', {prototype: proto});
<script>
<dng-badge label="Badge de:" nom="ZOE"/>
<script>
var dom = document.querySelector('dng-badge');
dom.addEventListener(‘click-title', function(e) {
alert('Click sur le titre');
});
</script>
HTML Template – Custom Element
Script modifié pour
une utilisation via les
Web Component
HTML Template – Conclusion
HTMLTemplate facilite la création graphique des Custom
Element
Déclaration à l’aide de balise simple à lire et à maintenir
les ressources déclarées dans le template sont inactives tant qu’on ne
l’instancie pas.
Comment s’assurer que ce que l’on déclare dans le
template s’affichera comme il a été défini?
Concurrence des styles CSS, …
Peut-on aller plus loin dans la séparation présentation et
données ?
Peut-on imaginer du databinding ?
21
Object.observe() pour le databinding
Principe: être notifié nativement lors d’un changement
sur un objet JavaScript
Standard prévu pour ECMAScript 7
20 à 40 fois plus rapide qu’une bibliothèque tierce
Benchmark AngularJS en 2012, 40 ms pour une mise à jour alors que
Object.observe() met 1-2 ms pour la même.
Intérêts:
Séparation des couches Modèle et Vue
Les notifications sont asynchrones
23
Object.observe() – Principe
Ajouter un handler pour tous les changements d’un objet
Considérons l’exemple suivant
On a un modèle, on définit un handler et on lie le handler aux
changements du modèle avec Object.observe()
Pour arrêter l’écoute:
24
var model = {name: ‘ZOE’, sex: ‘Female’, color: ‘blue’}
function handler(changes){
changes.forEach(function(change, i) {
console.log('propriété modifiée: ' + change.name);
console.log('nature du changement: ' + change.type);
console.log(‘nouvelle valeur: ' + change.object[change.name]); });
}
Object.observe(model, handler);
Object.observe(model_a_observer, observer);
Object.unobserve(model_a_observer, observer);
Object.observe() – Et encore plus…
Possibilité d’observer les Tableau
Obtenir les changements en cours tout de suite
Créer ses propres notifications groupées
Ne pas être notifié unitairement mais pour un ensemble défini.
Utile pour les modèles avec énormément de données (cas de grosses
applications)
- Type_creer: le nom du type de changement qui sera notifié (et à écouter)
- Action: une fonction contenant les actions de modifications faites avant la
notification. Peut renvoyer un objet dont les champs seront ajoutés à l’objet
change 26
Object.getNotifier(model).performChange(type_creer, action);
Array. observe(model_a_observer, observer);
Object.deliverChangeRecords(observer);
Shadow DOM pour
l’encapsulation
27
Source http://webcomponents.org/
• Chrome: implémenté
• Opera: implémenté
• Firefox: en cours de dev.
• Safari: non implémenté
• IE: en cours de considération
Shadow DOM - Principes
Shadow DOM permet de séparer le contenu de la
présentation tout en éliminant les conflits de noms
Permet de cacher toute la cuisine interne d’un composant
Permet d’encapsuler les styles naturellement
Principe
Créer un shadow DOM sur un élément HTML comme un Div
Ajouter du contenu
28
<style> p { color: Green; } </style>
<p>Du texte dans ma page html</p>
<div id="element"></div>
<script>
var foo = document.getElementById('element');
foo.createShadowRoot();
var p = document.createElement('p');
foo.shadowRoot.appendChild(p);
p.textContent = 'Du texte dans le shadow DOM';
</script>
Shadow DOM - Content
La présentation est masquée dans le Shadow DOM
Il faut pouvoir définir et insérer du contenu
La balise <content> permet d’identifier des points d’insertion
Le texte mis entre les balises sera inséré en lieu et place de <content>
29
<style> p { color: Green; } </style>
<p>Du texte dans ma page html</p>
<div id="element">ZOE</div>
<script>
var foo = document.getElementById('element');
foo.createShadowRoot();
var p = document.createElement('p');
foo.shadowRoot.appendChild(p);
p.innerHTML = ‘Mon nom est : <content/>';
</script>
Shadow DOM – Content – plus loin
Possibilité de différentier les contenus
Les balises content peuvent être différenciées avec l’attribut select
select correspond au nom d’une balise, un ID ou même la classe CSS
Pas d’attribut select correspond au texte par défaut (sans balise)
Si plusieurs content identiques, seule la première est utilisée
Plusieurs balises identiques, leurs contenus seront ajoutés l’un après
l’autre à la place du content correspondant
30
<style> p { color: Green; } </style>
<p>Du texte dans ma page html</p>
<div id="element">ZOE <nom>Hochedez</nom> Cindy</div>
<script>
var foo = document.getElementById('element');
foo.createShadowRoot();
var p = document.createElement('p');
foo.shadowRoot.appendChild(p);
p.innerHTML = ‘’Mon nom est : <content select=‘nom’/><content/>’’;
</script>
Shadow DOM pour les Web Components
Quel intérêt pour nos Web Components ?
Utilisé directement sur le Custom Element, il permet de masquer la
complexité du composant
Les styles de la nouvelle balise ne seront pas en conflit avec d’autres
utilisations de Template
En reprenant l’exemple vu dans le chapitre Template
Le code commenté est la version précédente
31
proto.createdCallback = function() {
//var content = document.querySelector('#badgeTemplate').content;
//this.appendChild(document.importNode(content, true));
var shadow = this.createShadowRoot();
var content = document.querySelector('#badgeTemplate').content;
shadow.appendChild(document.importNode(content, true));
};
HTML Import pour réutiliser ses
Web Components
32
Source http://webcomponents.org/
• Chrome: implémenté
• Opera: implémenté
• Firefox: en cours de dev.
• Safari: non implémenté
• IE: en cours de considération
HTML import - Introduction
Custom Element, HTML Template et Shadow DOM
permettent de créer des Web Components, comment les
réutiliser?
On peut actuellement charger les éléments JS, CSS et
HTML séparément
Imaginez la complexité si vous importez des Web
Components qui utilisent eux aussi d’autres Web
Components
Ou utiliser une iframe, ou encore du code JS… Pas très
élégant
33
HTML import - Principe
Nouvelle définition de la balise link
Permet d’importer le contenu de monComposant
href contient le chemin vers le fichier html
HTML import va charger le document HTML, résoudre le
chargement des sous-ressources et exécuter le code
JavaScript.
Le contenu n’est pas affiché automatiquement à l’endroit de l’import
Du code doit être écrit pour réaliser cet affichage
Les balises de rendu ne sont pas ajoutées au DOM, mais les balises
style, script, link sont bien exécutées.
34
<link rel=‘import’ href=‘monComposant.html’>
HTML import - règles
Le fichier HTML importé
peut charger des ressources comme des scripts, css, images…
peut ne pas déclarer de balises html, head, body, doctype.
Plusieurs import HTML faisant référence à la même URL
ne sera importé et exécuté qu’une seule fois
Les restrictions Cross-domain s’appliquent
voir CORS (Cross Origin Resource Sharing) si besoin
Possibilité de gérer les erreurs de chargements
35
<link rel=‘import’ href=‘monComposant.html’
onload=‘handleLoad(event)’
onerror=‘handleError(event)’>
Conclusion
Web Component s’appuie sur des standards HTML
Custom Element
HTML Template
Shadow DOM
HTML Import
Tous les navigateurs ne les implémentent pas, mais
webcomponent.js permet de palier en grande partie à ces
manques
Reste tout de même beaucoup de code à écrire pour
simplement déclarer nos Web Component
Des Frameworks peuvent nous aider à gagner en
efficacité 36
Framework existants
Polymer
Projet créé par Google en 2010, à l’origine du projet webcomponent.js
(polyfill)
Bibliothèque de composant Elements, la plus riche
Syntaxe déclarative, databinding, gesture
X-Tags : projet initié par la fondation Mozilla officialisé en
Janvier 2013
Aucune syntaxe déclarative
Propose une bibliothèque de composants (Brick)
Moins riche fonctionnellement que Element de Polymer
Bosonic
Publié en 2014, un seul contributeur, supporte IE 9, Approche
différente de Polymer et X-Tags, utilise un compilateur/traducteur
38