Encapsulation des composants avec Riot
Ce projet est open source et accessible depuis GitHub GL-Browser.
Cet article est le 6ème d'une série d'articles concernant la création d'un navigateur internet incluant par défaut des fonctionnalités que l'on retrouve habituellement sous forme d'extensions dans Chrome ou Firefox.
Exemples de fonctionnalités :
Nous souhaitons :
Nous utilisons Electron qui mixe le navigateur open source Chromium avec la richesse du framework Node.js.
Nous souhaitons améliorer l'architecture du projet pour mieux assurer sa pérénnité.
Dans ce 6ème article, nous allons donc encapsuler les composants visuels à l'aide de Riot.
Article 1 : initialisation du projet avec Electron.
Article 2 : injection du CSS et du JavaScript dans la page de Google.
Article 3 : injection de scripts JavaScript et de styles CSS suivant l'URL.
Article 4 : bloquer les requêtes de suivi/tracking des utilisateurs.
Article 5 : amélioration de l'interface utilisateur avec Bootstrap.
Pour récupérer directement les sources du 5ème article :
git clone https://github.com/emmanuelroecker/GL-Browser
git checkout article5
Riot permet de structurer l'interface utilisateur d'une application Web monopage sous forme de composants réutilisables.
Chaque élément visuel encapsulé dans un composant/tag possède des états.
À chaque changement d'état, la portion HTML correspondant à l'élément est régénérée automatiquement.
Riot ressemble à React mais sa syntaxe est plus lisible et sa taille est plus petite.
Ajouter au fichier package.json la nouvelle dépendance Riot :
{
"devDependencies": {
"electron-prebuilt": "^0.36.0",
"js-yaml": "^3.5.0",
"match-pattern": "^0.0.1",
"bootstrap": "^3.3.0",
"jquery": "^1.9.1",
"riot":"^2.3.0"
}
}
Pour installer :
npm install
Les tags/composants Riot doivent être compilés en JavaScript avant qu'Electron puisse les exécuter.
Ajouter à la fin du fichier index.html :
<!-- utiliser Riot et son compilateur -->
<script src="./node_modules/riot/riot+compiler.min.js"></script>
<!-- monter/compiler tous les composants -->
<script>
riot.mount('*');
</script>
Jusqu'à présent, le code ressemblait à un plat de spaghettis :
Le contenu d'un onglet est encapsulé dans un composant Riot indépendant et détachable appelé page.riot.tag, il devient plus facilement maintenable.
page.riot.tag contient le JavaScript, le CSS et le code HTML du composant :
<page>
<!-- code HTML précédemment dans le fichier page.html -->
<div class="gl-header input-group">
<div class="input-group-btn">
<a class="gl-goback btn btn-default" role="button"><span class="glyphicon glyphicon-arrow-left"></span></a>
</div>
<input class="gl-urltext form-control" type="text" placeholder="URL">
<span class="gl-indicator input-group-addon"></span>
<div class="input-group-btn">
<a class="gl-refresh btn btn-default" role="button"><span class="glyphicon glyphicon-repeat"></span></a>
<a class="gl-dev btn btn-default" role="button"><span class="glyphicon glyphicon-wrench"></span></a>
</div>
</div>
<webview class="gl-webview">
</webview>
<!-- le style css ne sera appliqué qu'à ce composant -->
<style scoped>
.gl-indicator {
top: 0px;
width: 40px;
}
.gl-webview {
display: block;
border: none;
}
</style>
<!-- code javascript associé au composant -->
<script>
'use strict'; //autoriser l'utilisation de ES6
this.on('mount', function() { //appelé au moment du montage du composant
let $node = $(this.root); //utiliser ce composant comme racine de jQuery
let webview = $node.find('.gl-webview');
let indicator = $node.find('.gl-indicator');
$node.find('.gl-refresh').click(function () {
webview.get(0).reload();
});
$node.find('.gl-dev').click(function () {
webview.get(0).openDevTools();
});
$node.find('.gl-goback').click(function () {
webview.get(0).goBack();
});
$node.find('.gl-urltext').keypress(function (e) {
if (e.keyCode !== 13) {
return true;
}
webview.get(0).src = this.value;
return false;
});
webview.on('did-start-loading', () => {
indicator.toggleClass('glyphicon glyphicon-refresh');
});
webview.on('did-stop-loading', () => {
indicator.toggleClass('glyphicon glyphicon-refresh');
});
webview.on('load-commit', function (e) {
let url = e.originalEvent.url;
webview.on('did-finish-load', function () {
let inject = getToInject(url);
if (inject) {
webview.get(0).insertCSS(inject.css);
webview.get(0).executeJavaScript(inject.js);
}
$(this).off('did-finish-load');
});
});
});
</script>
</page>
Ajouter ce nouveau composant page à index.html :
<script src="components/page.riot.tag" type="riot/tag"></script>
De la même manière que pour le composant page ci-dessus, encapsuler la gestion des onglets dans un composant Riot appelé tabs.riot.tag.
Ce composant permet d'ajouter ou de supprimer des onglets.
Chaque onglet contient un composant page.
<tabs>
<ul class="nav nav-tabs">
<!-- parcourir tous les éléments -->
<li each={ item in items }>
<!-- utiliser item.id comme identifiant -->
<a href="#{item.id}" data-toggle="tab">
{item.id}
</a>
<!-- appeler la méthode remove du parent lors du clic sur le boutton de suppression de l'onglet -->
<span class="close" onclick={parent.remove}>
×
</span>
</li>
<li>
<!-- appeler la méthode add lors du clic sur le boutton d'ajout d'un nouvel onglet -->
<a role="button" class="add-url" data-toggle="tab" onclick={add}>
+
</a>
</li>
</ul>
<div class="tab-content">
<!-- parcourir tous les éléments -->
<div each={item in items} class="tab-pane fade" id={item.id}>
<!-- utiliser le composant page -->
<page name={item.id}></page>
</div>
</div>
<!-- le style css ne sera appliqué qu'à ce composant -->
<style scoped>
.nav-tabs > li {
position:relative;
}
.nav-tabs > li > a {
display:inline-block;
}
.nav-tabs > li > span {
display:none;
cursor:pointer;
position:absolute;
right: 6px;
top: 11px;
}
.nav-tabs > li:hover > span {
display: inline-block;
}
.tab-content > .tab-pane {
display: block;
height: 0;
overflow-y: hidden;
}
.tab-content > .active {
height: auto;
}
</style>
<!-- code javascript associé au composant -->
<script>
'use strict'; //autoriser l'utilisation de ES6
this.items = []; //tableau des onglets
this.incid = 0; //incrémenter les ids
//méthode appelée lors de l'ajout d'un nouvel onglet
add(e) {
this.currentid = 'url' + this.incid;
this.items.push({id:this.currentid});
this.incid++;
}
//initialiser le composant avec un premier onglet
this.add();
//méthode appelée lors de la suppression d'un onglet
remove (e) {
if (this.items.length <= 1)
return;
//supprimer l'onglet du tableau
let item = e.item.item;
let index = this.items.indexOf(item);
this.items.splice(index,1);
//activer l'onglet précédent
index--;
if (index < 0)
index = 0;
this.currentid = this.items[index].id;
}
//méthode appelée une fois le composant mis à jour
this.on('updated', function() {
let $node = $(this.root); //utiliser ce composant comme racine de jQuery
//redimensionner le webview à chaque affichage d'un onglet
$node.find('a[data-toggle="tab"]').on('shown.bs.tab', function () {
glRefreshWebComponentSize();
});
//afficher l'onglet courant
$node.find(`a[href="#${this.currentid}"]`).tab('show');
});
</script>
</tabs>
Ajouter ce nouveau composant tabs à index.html :
<script src="components/tabs.riot.tag" type="riot/tag"></script>
Le corps du fichier index.html se réduit sensiblement :
<body>
<div>
<!-- utiliser le composant tabs -->
<tabs>
</tabs>
</div>
</body>
En comparant le code source de l'article 5 avec celui-ci, l'architecture devient plus claire :
Avant - Article 5 | Après - Article 6 |
---|---|
index.html | index.html |
glbrowser.css | glbrowser.css |
glbrowser.js | glbrowser.js |
inclus dans glbrowser.js et glbrowser.css | tabs.riot.tag |
inclus dans glbrowser.js et glbrowser.css | page.riot.tag |
git add -A
git commit -m "article 6"
git push
Le prochain article traitera de l'intégration d'un gestionnaire de mots de passe pour se connecter de manière transparente à ses comptes.