Vous êtes sur la page 1sur 73

Travail de Bachelor - Mémoire

Scada - Interface Web dynamique en HTML5

Etudiant
Sébastien Schoepfer

Professeur
MM. Didier Mettler et François Birling

Mandants
MM. Christopher Bouzas et Antoine Jeanrichard - Objectis SA

Période : 10.07.2017 - 29.09.2017


Centre Formation de Base

Travail de Bachelor

Préambule
Ce travail de Bachelor est réalisé en vue de l'obtention du titre de Bachelor of
Sciences en Ingénierie.

Son contenu, sans préjuger de sa valeur, n'engage ni la responsabilité de


l'auteur, ni celles du jury du travail de Bachelor et de l'Ecole.

Aucune utilisation, même partielle, de ce travail ne peut être faite sans


l'autorisation écrite préalable de la Direction. Toutefois, l'entreprise ou
l'organisation qui a confié un sujet de travail de Bachelor peut utiliser les
résultats du travail pour ses propres besoins.

Doyenne du
Centre Formation de Base

L. Larghi

Yverdon-les-Bains, novembre 2016

_______________________________________________________________________________
HEIG-VD, av. Sport 20, 1401 Yverdon-les-Bains
Tél. 024/557 76 09 / 07 Fax : 024/557 76 01
Table des matières

1 Société mandante 6

2 Enoncé 7
2.1 Demande initiale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.2 Cahier des charges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

3 Démarche 8
3.1 Pré-étude . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.2 Réalisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

4 Concept.Web 10
4.1 Généralités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
4.2 Navigation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
4.3 Page d’accueil . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
4.4 Visualiseur de synoptique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
4.5 Editeur de synoptique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

5 Architecture globale 17
5.1 Généralités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
5.2 Organisation du projet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

6 Architecture du client 19
6.1 Généralités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
6.2 Fichiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
6.3 Application principale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
6.4 Page d’accueil . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
6.5 Objets métiers - Synoptique et composant . . . . . . . . . . . . . . . . . . . . . . . . 22
6.6 Objets métiers - Propriétés, variables et transformations . . . . . . . . . . . . . . . . . 31
6.7 Visualiseur de synoptique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
6.8 Concepteur de synoptique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40

7 Architecture du serveur 50
7.1 Généralités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
7.2 Schéma et base de données . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
7.3 Services web REST . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52

Sébastien Schoepfer Page 4


8 Client Woopsa 54
8.1 Généralités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
8.2 Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
8.3 Fonctions principales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
8.4 Système d’abonnement côté serveur . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
8.5 Abonnement à une variable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56

9 Tests unitaires 59
9.1 Généralités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
9.2 Test du client . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
9.3 Test du serveur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59

10 Revue des exigences 60


10.1 Exigences globales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
10.2 Exigences de l’éditeur de synoptiques . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
10.3 Exigences ajoutées . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61

11 Dossier de gestion 62
11.1 Planning des réunions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
11.2 Planning des tâches . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62

12 Conclusion 65
12.1 Concept . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
12.2 Choix technologiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
12.3 Expérience personnelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66

13 Remerciements 67

Table des figures 68

Table des tableaux 70

Bibliographie 71

Authentification 72

Sébastien Schoepfer Page 5


Société mandante 1

Objectis SA est une société essentiellement active dans le milieu de l’industrie. Basée à Y-
parc (avenue des Découvertes 18, 1400 Yverdon-les-Bains), elle a été fondée par d’anciens
étudiants de la HEIG-VD. Actuellement composée d’une vingtaine d’ingénieurs, elle est admi-
nistrée par MM. François Birling et Christopher Bouzas.
Comme décrit sur le site web de la société[15]. Elle a orienté ses compétences dans les trois
domaines suivants :

Machines et Automatisation
Création de logiciels et frameworks performants et modulaires pour l’automatisation, le
contrôle de machine et la supervision de production.
Business Applications
Développement d’applications métiers avec interfaces modernes et ergonomiques pour
des applications professionnelles sur PC, web, smartphones et tablettes.
Instruments et Embarqué
Applications et frameworks flexibles et performants pour instruments de mesure et systèmes
embarqués temps-réel.

Objectis est un partenaire d’innovation offrant différents services, de la réalisation complète


d’un logiciel à la formation, elle accompagne ses clients dans leurs projets de développement.

Sébastien Schoepfer Page 6


Enoncé 2

Ce sujet a été traité en détail lors de la pré-étude, il s’agit ici de se remémorer, ce qui a été
défini lors de cette première étape.

2.1 Demande initiale

L’énoncé, tel qu’il a été proposé initialement par la société Objectis, est le suivant :
“ Les logiciels d’interface homme machine de supervision utilisés dans les systèmes automa-
tisés, appelés SCADA, pourraient tirer un très grand bénéfice des nouvelles technologies HTML5
pour offrir un affichage dynamique avec un visuel attractif et une architecture Web aisément
exploitable depuis tout type de périphérique, y compris tablettes mobiles.

Le but de ce projet est de développer une base de logiciel SCADA en HTML 5, permettant
de superviser une application tournant sur un PC connectés aux systèmes de production (PLC,
CNC). La liaison avec le processus physique sera entre autres réalisée par le protocole Woopsa.

Au stade actuel, un concepteur visuel est déjà disponible, il s’agira donc de développer un
module Runtime remplissant la fonction de serveur Web.

Ce travail de diplôme permettra de se former à des technologies porteuses et très en vogue


dans l’industrie suisse de machines. ”

2.2 Cahier des charges

Le sujet étant très vaste, une des principaux éléments de la pré-étude a été de déterminer
l’étendue du projet et d’établir un cahier des charges précis. Résumons ici les axes principaux
déterminé lors de la pré-étude :

— Fournir un outil permettant la création et la visualisation de vues synoptiques


— Etablir les fondations permettant la création de composant visuel utilisable dans les vues
précitées.
— Les composants devront permettre l’affichage de données constantes ou être liés avec
des variables fournies par le protocole Woopsa[14].
— Créer quelques composants de base.

D’autres éléments ont également été évoqués lors de la pré-étude. Cette dernière avait permis
de les écarter, car ils n’étaient pas prioritaires et n’entraient pas dans le planning (ex : édition
de composant, édition de page et gestion des utilisateurs).
Une liste des exigences a été rédigée lors de la définition du cahier des charges. Nous revien-
drons sur celle-ci à la fin du document afin de s’assurer que nous les avons bien toutes respec-
tées.

Sébastien Schoepfer Page 7


Démarche 3

3.1 Pré-étude

La pré-étude a permis d’établir un cahier des charges détaillés. En partant de cette base,
trois étapes ont pu être réalisées :

a. Choix technologiques
Lors de ce processus, un panel de technologies a été défini et évalué en fonction de
l’objectif à atteindre. Le choix final s’est rapidement imposé de lui-même. Il a ensuite fallu
s’assurer que toutes les fonctionnalités pouvaient être réalisé avec les technologies rete-
nues.

Divers prototypes de parties-clés de l’application ont été créés afin de s’assurer de la fai-
sabilité du produit.

Cette étape importante dans la réalisation d’un projet informatique avait permis de définir
le choix technologique suivant :

Figure 3.1 – Diagramme technologique

Il convient de rappeler que le langage Typescript a été utilisé pour créer l’ensemble du
produit.
b. Architecture
Lors de cette étape, un diagramme de classe a été défini. Ce diagramme a été utilisé tout
au long de la réalisation du projet. Il a beaucoup évolué au fur et à mesure du dévelop-
pement. Cependant, les grandes lignes spécifiées dans la pré-étude ont été respectées.
c. Planification
Sur la base du diagramme de classe, le projet a été divisé en tâches qui ont été évaluées
individuellement. Ainsi, les parties du produit pouvant être réalisées ont pu être détermi-
nées.

Sébastien Schoepfer Page 8


3.2 Réalisation

3.2.1 Semaine 1 à 8

Aucune phase d’analyse n’a été nécessaire au début du projet. étant donné que cette
partie avait été effectuée lors de la pré-étude. Le projet a pu commencer directement par le
développement de l’application à proprement parler.
Globalement, les tâches ont été effectuées en suivant l’ordre défini dans le planning initial.
Dans certain cas, il a tout de même été nécessaire d’en modifier la chronologie pour une plus
grande efficacité.
Certains éléments-clés ont également nécessité de développer des tests unitaires. Ceux-ci ont
été réalisés directement dans le cadre de la tâche.

3.2.2 Semaine 9 à 12

A partir du 3ème tiers, la réalisation de ce dossier a débuté, tout en finalisant la fin du déve-
loppement. Il était important d’atteindre un certain niveau de maturité avant de commencer
à rédiger le mémoire. De cette manière, il était plus évident d’avoir une vue d’ensemble du
projet et ainsi sélectionner le contenu plus efficacement.

Sébastien Schoepfer Page 9


Concept.Web 4

4.1 Généralités

Dans ce chapitre, nous allons présenter le produit tel que livré avec ce dossier. Nous allons
passer en revue les fonctionnalités.
A la demande de la société mandante, toute l’application a été développée en anglais. De
ce fait, toutes les captures d’écran et images sont réalisées dans cette langue.
La procédure complète pour installer et démarrer l’application se trouve dans le fichier README.md
situé à la racine du projet. Ce dernier étant livré sur le CD-ROM annexé à ce document.

4.2 Navigation

Figure 4.1 – Barre de navigation

La barre supérieure est disponible sur toutes les pages. Elle permet de naviguer à travers
l’application, ceci grâce aux trois boutons situés sur la droite :
— Welcome
— Viewer
— Designer

4.3 Page d’accueil

La page d’accueil se présente comme sur la figure ci-dessous (4.2).

Sébastien Schoepfer Page 10


Figure 4.2 – Page d’accueil

Elle contient une liste des synoptiques existantes. Les icônes sur la droite de la liste per-
mettent de :
— Visualiser un synoptique (cf 4.4)
— Editer un synoptique (cf 4.5)
— Supprimer un synoptique

4.4 Visualiseur de synoptique

Cette partie permet d’afficher un synoptique. La liste sur la gauche permet de naviguer
d’une vue à l’autre. Le synoptique s’affichent sur la droite (comme vous pouvez le constater
sur l’image 4.3)

Figure 4.3 – Visualiseur de synoptique

4.5 Editeur de synoptique

Cette page permet à l’utilisateur de créer de nouvelles vues synoptiques mais également
de modifier les existantes. Elle est certainement la partie la plus intéressante de l’application.
Nous détaillerons dans une prochaine section chaque élément de cette page. Voici un aperçu
global :

Sébastien Schoepfer Page 11


Figure 4.4 – Editeur de synoptique

4.5.1 Barre supérieure

Figure 4.5 – Barre supérieure de l’éditeur de synoptique

Sur la gauche sont disposées diverses fonctionnalités telles que :


— Créer un nouveau synoptique
— Ouvrir un synoptique existant
— Enregistrer le synoptique en cours d’édition
Sur la droite vous trouverez les fonctions propres au synoptique :
— Edition du nom
— Définition de la taille en pixel (largeur x hauteur)

Sébastien Schoepfer Page 12


4.5.2 Liste des composants

La liste des composants est séparée en deux parties :


Composant par défaut
Il s’agit des composants fournis par le système. Pour rappel,
ces composants sont fournis avec l’application. Ils ne peuvent
pas être édités par l’utilisateur.
Composant utilisateur
Nous parlons ici des composants pouvant être ajoutés à
l’application directement par les utilisateurs. Bien qu’aucune
fonctionnalité d’édition ne soit encore présente, il s’agit
d’une des prochaines étapes dans le développement de ce
produit. Actuellement, pour ajouter des composants utilisa-
teur, il faut aller les ajouter à la main dans la base de données.
Pour ajouter un composant dans un synoptique, il suffit de double
cliquer sur le composant. Celui-ci apparaitra directement dans le
synoptique.

Figure 4.6 – Liste des compo-


sants

4.5.3 Partie centrale

La partie centrale contient le synoptique en cours d’édition. Chaque composant peut être
sélectionné et déplacé au moyen de la souris dans le synoptique. Il est également possible
de supprimer les composants en pressant sur la touche Effacer (Delete) ou Retour en arrière
(Backspace) du clavier.

Figure 4.7 – Synoptique en cours d’édition

Sébastien Schoepfer Page 13


4.5.4 Editeur de propriétés

Lorsqu’un composant est sélectionné, la liste des propriétés disponibles pour ce composant
s’affiche dans l’éditeur de propriétés sur la droite de l’écran.

A côté de chaque propriété, divers boutons sont disponibles. Ces


boutons changent en fonction du type de propriété. On distingue
deux grands groupes de propriétés :
Lecture Ces propriétés permettent d’afficher une valeur du
système au sein du composant. On pourrait par
exemple afficher une température ou un texte.
Ecriture Celles-ci permettent d’écrire une valeur sur le serveur.

Figure 4.8 – Liste des propriétés

Figure 4.9 – Différence entre propriétés en lecture (à gauche) et propriétés en écriture (à droite)

Chacun des boutons ci-dessus permet d’accéder aux éditeurs que nous décrirons dans les
sections suivantes.
Les propriétés en lecture peuvent être de l’un des types suivants :
— Texte
— Couleur
— Numérique
— Vrai/Faux
Ces propriétés interagissent directement avec des éléments de composant. Par exemple, don-
ner une valeur à une propriété de type couleur peux changer le fond d’un composant. Une
propriété de type vrai/faux peut donner la possibilité à l’utilisateur de choisir d’afficher ou non
un élément de composant. L’effet du type de propriété dépend entièrement de chaque com-
posant.

Editeur de constante

Cette fonctionnalité permet de définir une constante. En fonction du type de propriétés


que l’on veut modifier, l’application propose un éditeur différent pour guider l’utilisateur dans
le choix de sa valeur. Voici les quatre éditeurs de constantes :

Figure 4.10 – Editeur de propriété de type texte

Figure 4.11 – Editeur de propriété de


type couleur

Sébastien Schoepfer Page 14


Figure 4.13 – Editeur de propriété de type vrai/faux
Figure 4.12 – Editeur de propriété de type numérique

Lien vers une variable Woopsa

Pour accéder à l’éditeur de lien vers une variable Woopsa, il faut cliquer sur la petite prise
située à côté d’une propriété. Comme son nom l’indique, cet outil permettra de lier une va-
riable à un composant. Il sera par exemple possible d’afficher la valeur d’un thermomètre en
temps réel, mais également de modifier sa couleur en fonction de cette valeur.
Il s’agit donc de faire correspondre une valeur provenant d’un serveur Woopsa avec une pro-
priété comme celle décrite précédemment. Dans certains cas, il est possible de reprendre la
valeur telle quelle et de l’afficher dans le composant (affichage de la température). Dans
d’autres, il faut appliquer une transformation à la valeur retournée par Woopsa pour l’utiliser
dans le composant (changement de la couleur en fonction de la température).
Regardons l’éditeur de lien sur la figure 4.14. A gauche la liste des variables disponibles sur le
serveur Woopsa. Lorsque l’on sélectionne une variable, la liste des transformations disponibles
s’affiche sur la droite :

Figure 4.14 – Editeur de lien vers des variables Woopsa

Cette liste se construit automatiquement en fonction de la variable que l’on veut observer et
de la propriété de destination (celle en cours d’édition). Voici un tableau qui récapitule la liste
des transformations disponibles :

— Variable booléenne vers propriété couleur


— Variable numérique vers propriété booléenne
— Variable numérique vers propriété couleur
— Variable numérique vers propriété texte

Dans certains cas, il est possible de définir plusieurs transformations pour la même propriété. Il
s’agit de pouvoir changer la valeur de la propriété en fonction de la valeur de la variable. Par

Sébastien Schoepfer Page 15


exemple, il est possible de définir des couleurs différentes en fonction des valeurs de tempéra-
tures.
Sur la figure 4.14 , est disponible une liste de transformations. Cette liste s’applique de haut
en bas. Aussitôt qu’une des conditions est vérifiée, la propriété prend la valeur définie par la
transformation. Dans le cas de cet exemple, si la température est de 25°, le texte affiché sera
”Plus petit que 30°”.

Modificateur de variables Woopsa

Cet éditeur est disponible grâce à l’icône représentant une flèche pointant vers la droite
depuis la boîte à outil des propriétés. Il permet de sélectionner une variable Woopsa ainsi
qu’une valeur. Depuis le visualisateur de synoptique, lorsque l’utilisateur clique sur une certaine
partie du composant, ce dernier modifie la variable sélectionnée avec la valeur définie. Cela
permet de modifier l’état du serveur.

Figure 4.15 – Définition d’une valeur à donner à une variable Woopsa

Sébastien Schoepfer Page 16


Architecture globale 5

5.1 Généralités

Dans ce chapitre et les trois suivants, nous allons présenter l’implémentation. L’accent sera
mis sur les éléments-clés du système. Les schémas de classe que l’on trouvera dans ce chapitre
correspondent à l’implémentation du code. Ils sont donc légèrement différents de ceux définis
lors de la pré-étude. Il est important de préciser que les schémas ont volontairement été épurés
afin de mieux cibler la problématique traitée. Il sera ainsi possible qu’une classe apparaisse sans
lien dans une section et que dans la suivante, elle se trouve munie d’un nouvel attribut qui la
lie à une autre entité.
Rappelons également que le code couleur utilisé pour les classes reste identique à celui de la
pré-étude. A noter qu’une couleur plus claire a été utilisée pour symboliser les classes abstraites
ou les interfaces.

Figure 5.1 – Code couleur des classes

Nous ne rentrerons pas dans le détail du fonctionnement du framework Angular. Le web re-
gorge d’articles de qualité à ce sujet. Cependant, quelques éléments seront tout de même
expliqués afin que le lecteur comprenne les raisons de certaines décisions prises.
L’architecture de l’application a été définie lors de la pré-étude. Le schéma suivant rappelle
ce qui a été déterminé.

Figure 5.2 – Architecture globalel

Pour comprendre à quel emplacement intervient quelle technologie, référez-vous au diagramme


technologique 3.1.

Sébastien Schoepfer Page 17


5.2 Organisation du projet

Nous allons dans cette section expliquer l’organisation au sein du projet. La figure suivante
présente le contenu de son dossier racine.

Figure 5.3 – Organisation des fichiers du projet

client Ce dossier contient toute la partie client, il s’agit du code Angular.


common Ce dossier contient le code qui est partagé entre le client et le serveur.
dist Ce dossier contient le projet compiler.
1
node_modules Répertoire d’installation des librairies externes, il est nécessaire au bon fonc-
tionnement de Node.js.
servers Ce dossier est divisé en deux parties, un dossier application-server dans le-
quel se trouve le serveur fournissant l’API REST 2 et le client. Le dossier woopsa-
server qui contient un server woopsa utilisé pour le développement. Ce der-
nier a permis de simuler un vrai ordinateur industriel et ainsi faciliter le déve-
loppement.
.editorconfig Ce fichier permet de définir le formatage du code source (p. ex : déterminer
la taille de l’indentation).
.gitignore Ce fichier précise au système de gestion de version quel fichier ignorer.
db-data.js Ce fichier contient un script pour créer des données sur la base MongoDB. Il
inclut un synoptique ainsi que divers composants utilisateur.
gulpfile.js Pour simplifier le développement, nous avons utilisé l’outil Gulp 3 . Il permet
d’automatiser certains processus. Nous retrouvons donc dans ce fichier des
tâches pour : compiler les projets, démarrer les serveurs, etc.
package.json 4 Ce fichier définit la configuration du projet Node.js. Il s’agit de la configu-
ration global de toutes l’applications. Elle contient notamment les librairies
nécessaire à l’exécution du système en production. Des fichiers similaires se
trouvent dans le client et le serveur pour leur propre configuration.
README.md Ce fichier contient la procédure d’installation et de compilation de l’appli-
cation, ainsi que quelques généralités sur le code source.

2. Une API compatible REST, ou « RESTful », est une interface de programmation d’application qui fait appel à
des requêtes HTTP pour obtenir (GET), placer (PUT), publier (POST) et supprimer (DELETE) des données. - http ://www.
lemagit.fr/definition/API-RESTful.
3. Outil d’automatisation de tâches, https://gulpjs.com/

Sébastien Schoepfer Page 18


Architecture du client 6

6.1 Généralités

Dans ce chapitre, nous allons traiter la partie client de l’application. Nous présenterons la
partie serveur dans le chapitre suivant. Il convient de rappeler que ces deux éléments commu-
niquent grâce au protocole HTTP et à l’API REST. Les données transitent au format JSON.
Nous allons régulièrement parler de composant. Il faut faire la distinction entre les composants
de l’application qui appartiennent à notre modèle objet et qui composent les synoptiques,
et les composants Angular qui sont omniprésents dans tout le frameworks. Ces derniers per-
mettent d’afficher des informations. D’ailleurs les composants de l’application sont eux-mêmes
des composants Angular.

Note sur Angular

Chaque composant Angular est formé de trois éléments : une classe Typescript, un tem-
plate HTML et des styles CSS. Il est important de noter qu’un composant peut-être com-
posé de plusieurs autres composants. Ce lien de composition n’est pas forcément visible
dans le code Typescript. Il peut-être matérialise par l’usage de la balise qui représente
le composant en question. Dans les schémas de classes aucune distinction ne sera faite
entre ces deux types de lien, car ils ont exactement la même signification.

Convention de codage Les conventions de codage d’Angular ont été suivies 1 . Cela signifie
que les noms de fichier s’écrivent en minuscule. Chaque mot est séparé par un tiret (”-”), le nom
est terminé par point (”.”) et suivi par l’extension du fichier. Si le fichier a une fonction spécifique
reconnue par Angular (composant, service, module, ...), le nom de la fonction précédé par un
point (”.”) est placé juste avant l’extension.
Pour nommer les classes, la convention fixée par l’équipe Angular est d’utiliser la notation Camel
Case, à savoir tous les mots de la classe sont joints et commencent par une majuscule. Pour
ce qui concerne les noms de méthodes et de propriétés, nous appliquons la même notation
excepté la première lettre qui commencera par une minuscule.

Diagramme de classes Tous les diagrammes de classes ont été conçus pour être les plus com-
préhensibles possible. De ce fait, certains éléments propres au framework Angular qui n’était
pas absolument nécessaires à la bonne compréhension du système ont volontairement été
exclus du schéma de classes.
1. https://angular.io/guide/styleguide#general-naming-guidelines

Sébastien Schoepfer Page 19


6.2 Fichiers

Le lecteur trouvera ci-dessous le contenu du dossier client :


client
src
app
business-object
data-services
factories
ng-components
woopsa
test
app
woopsa
Le dossier client est séparé en deux sous dossier : src et test.
Le dossier src contient le code du client. Il est lui-même composé de deux dossiers app et
woopsa. Ce dernier contient le client Woopsa. La décision a été prise de séparer le code im-
plémentant le protocole woopsa du reste de l’application. De cette manière, il sera aisé de
l’extraire afin de le mettre à disposition d’autres applications. Le dossier app contient le reste
du code de l’application. Voyons plus précisément le contenu de ce dossier :
business-objects Ce dossier contient les éléments propres au métier de l’application, à savoir
les synoptiques et les composants (utilisateurs et par défaut).
data-services Ce dossier contient les classes d’accès aux données. Elles permettent la
communication avec le serveur via l’API REST.
factories Ce dossier contient les classes qui permettent la création des différents élé-
ments du système (composant, synoptique, ...).
ng-components Ce dossier contient les éléments graphiques de l’application. A la racine,
nous retrouvons les éléments globaux tels que la barre de navigation supé-
rieure ou la page d’accueil. Le sous-dossier designer inclut tout ce qui com-
pose l’outil de création de synoptiques.
Le dossier test contient tous les tests unitaires. Son arborescence est la même que celle du
dossier src.

6.3 Application principale

Nous traiterons ici de l’application principale ainsi que de son point d’entrée. Angular im-
pose une structure de base. La classe AppModule est considérée comme le conteneur de toute
l’application. Elle en connait toutes les classes. L’usage imposé du décorateur NgModule per-
met d’indiquer au framework quelles classes sont des services (Singleton Angular) et lesquelles
des composants.
App Component est le point d’entrée graphique de l’application. C’est d’une certaine ma-
nière notre ”Index.html”.

Sébastien Schoepfer Page 20


Figure 6.1 – Point d’entrée de l’application

Comme constaté sur la figure 6.1, la classe TopMenuComponent est liée à AppComponent par
une relation de composition. Tandis que les trois autres pages le sont par une simple relation.
Voyons le code HTML d’AppComponent :

<ofact-top-menu></ofact-top-menu>
<router-outlet>
Listing 6.1 – HTML de AppComponent

La balise <ofact-top-menu> dit à Angular qu’il doit intégrer le composant TopMenuComponent


dans la page. Nous le voyons donc comme une relation de composition.
Pour les autres pages, nous avons utilisé le système de routage intégré à Angular. De ce fait,
la classe AppComponent ne sait pas quelle page elle va afficher, d’où la relation simple utili-
sée dans le schéma de classe. La balise <route-outlet> précise au framework que c’est à cet
emplacement qu’il doit afficher l’une de nos trois pages à savoir : WelcomePageComponent,
SynopticViewerPageComponent ou SynoptiqueDesignerPageComponent. Pour comprendre
comment Angular détermine quelle page afficher, il faut retourner dans notre AppModule. Le
code ci-dessous montre la configuration du router Angular. Cet élément fait le lien entre les
parties de l’url passée au navigateur et le composant à afficher à la place de la balise <route-
outlet> :
const routes: Routes = [
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: 'home', component: WelcomePageComponent},
{ path: 'viewer', component: SynopticViewerPageComponent},
{ path: 'viewer/:synoptic', component: SynopticViewerPageComponent},
{ path: 'designer', component: SynopticDesignerPageComponent},
{ path: 'designer/:synoptic', component: SynopticDesignerPageComponent},
];

@NgModule({
// ....
imports: [
RouterModule.forRoot(routes),
]
// ....
})
export class AppModule {}
Listing 6.2 – Déclaration des routes dans AppModule

Le listing suivant représente la partie navigation de TopMenuComponent. En utilisant la direc-


tive routerLink, nous définissons que lorsque l’utilisateur clique sur un bouton, il doit naviguer
vers la route ”home”. Tout cela se passe sans rafraichir la page. Ainsi, l’application n’est pas
entièrement rechargée seuls les éléments nécessaires sont construits et affichés.
<nav>

Sébastien Schoepfer Page 21


<button md-button class="nav-button" routerLink="home">Welcome</button>
<button md-button class="nav-button" routerLink="viewer">Viewer</button>
<button md-button class="nav-button" routerLink="designer">Designer</button>
</nav>
Listing 6.3 – Navigation dans TopMenuComponent

6.4 Page d’accueil

Figure 6.2 – Page d’accueil

Cette page est simplement composée de la liste des synoptiques existants. Pour afficher cette
liste, nous avons créé un composant SynopticListComponent. Ce dernier est utilisé à plusieurs
endroits et permet d’effectuer diverses opérations. En fonction du listType qu’on lui attribue, il
affiche à côté de chaque synoptique différents boutons permettant des opérations comme :
accéder à un synoptique en mode lecture (visualisateur de synoptique), accéder à un synop-
tique en mode écriture (créateur de synoptique), ou plus simplement supprimer un synoptique.

Figure 6.3 – Un élément de la liste de synoptique

Il aurait été plus judicieux de donner à l’attribut listType un type énuméré. Cependant, il n’est
pas possible d’utiliser ce genre d’artefact dans les templates HTML. Pour cette raison, le type
string a été utilisé. Voilà un exemple d’appel à ce composant dans un template :
<ofact-synoptic-list [listType]="'all'"></ofact-synoptic-list>
Listing 6.4 – Appel de SynopticListComponent

Remarque : Toutes les balises de composant propre à l’application sont préfixées avec le terme
ofact. Ce terme provient du nom initial du produit qui était oFactory.
Il est également possible d’écouter cette liste afin d’être notifié lorsque l’utilisateur sélectionne
un élément de la liste. L’attribut onSelectItemEvent a été créé à cet effet.
La classe SynopticDataService de la figure 6.2 est le service d’accès aux données des synop-
tiques. Les services d’accès aux données seront détaillés dans une prochaine section.

6.5 Objets métiers - Synoptique et composant

Avant d’aller plus loin dans le détail des pages de l’application et dans les éléments qui les
composent, il est nécessaire d’aborder le cœur de l’application. Il est important de comprendre
le processus de création d’un synoptique.

Sébastien Schoepfer Page 22


Figure 6.4 – Synoptiques et composants

Le serveur est composé de deux collections : une collection de synoptiques et une de définition
de composants. Cette dernière regroupe toutes les définitions de composants utilisateurs. Lors
de la pré-étude, nous avons mis en évidence deux types de composants. Les composants par
défaut qui sont compilés avec l’application, et les composants utilisateurs qui proviennent de
la base de données et qui pourraient être édités à l’aide d’un outil prévu à cet effet.
La collection de synoptiques, contient toutes les informations des synoptiques. Chaque synop-
tique étant lui-même constitué de composants qui se trouvent être des instances de compo-
sants utilisateurs ou de composants par défaut.
Dans la figure 6.4, le terme Factory désigne plusieurs classes qui permettent d’instancier un
synoptique provenant du serveur. Le principe est simple : en fonction du type de chaque com-
posant, nous définissons s’il faut aller chercher dans les définitions de composants utilisateurs ou
dans la liste des classes de composants par défaut. Ensuite nous pouvons créer des instances
des composants et leur attribué les données qui figurent dans le synoptique. Nous reviendrons
sur ce processus plus tard dans le chapitre.
Regardons de plus près l’implémentation d’un synoptique et d’un composant :

Figure 6.5 – Synoptique et composant - les classes

La classe SynopticComponent est suffixée par le terme Component, car il s’agit d’un compo-
sant Angular. Elle est donc directement affichée dans le navigateur. Les diverses propriétés des
classes permettent de dimensionner les éléments et de les placer à l’écran.

Sébastien Schoepfer Page 23


La propriété name du composant permet d’identifier lequel doit être affiché. Elle fait le lien
entre l’instance du composant dans le synoptique et la définition du composant.
Le schéma précédent introduit différents concepts que nous allons détailler dans les sections
suivantes.

6.5.1 Persistance et interface

Reprenons la figure 6.5 et expliquons la raison d’être des interfaces ISynoptic et ICom-
ponent. Nous avons choisi d’extraire tout ce qui doit persister en base de données dans des
interfaces. Celles-ci sont sauvegardées dans le dossier Common(voir figure 5.3) qui se situe à la
racine du projet. De cette manière, le client et le serveur utilise le même code. Cela présente
les avantages suivants :
— Réutilisation du code,
— Lorsque l’interface change, le client et le serveur seront obligés d’être corrigés pour l’im-
plémenter correctement (sinon cela entrainera erreur de compilation).
— Il est très facile de désérializer du JSON dans une interface.
Les interfaces sont des outils puissants fournis par Typescript. Cependant, JavaScript ne les sup-
portent pas nativement. De ce fait, leur usage est utilisé uniquement pour la phase de déve-
loppement (par les outils de développement pour fournir une complétion de code 2 ) et pour la
phase de transpilation (transformation du code Typescript en JavaScript).
Ce manque de support natif implique qu’il n’est pas réellement possible de transtyper une
classe implémentant une interface dans le type de l’interface. Si nous effectuons cette opéra-
tion, la compilation ne retournera pas d’erreur.
Cependant, à l’exécution, l’objet transtypé contiendra tout ce qu’il contenait à sa création et
pas seulement ce qui est décrit par l’interface. Cela posera problème lorsque nous voudrons
extraire le JSON correspondant à l’interface pour l’envoyer au serveur à des fins de persistance.
Tous les attributs n’appartenant pas à l’interface seront également sérialisés. Voici un exemple
illustrant ce point :
interface Ia{
valueFromA : string;
}

class myClass implements Ia{


valueFromA : string;
valueFromMyClass : string;
}

let a = new myClass();


a.valueFromA = 'Value From A';
a.valueFromMyClass = 'Value From My Class';
Listing 6.5 – Sérialisation d’interfaces

Nous pourrions penser qu’une sérialisation sous la forme : JSON.stringify(<Ia>a).toString(); re-


tourne la valeur {"valueFromA":"Value From A"}, mais ce n’est pas le cas. Elle nous renvoie la
valeur {"valueFromA":"Value From A","valueFromMyClass":"Value From My Class"}. Le résultat de la
sérialisation contient donc trop d’informations. De plus, les artifices de Typescript, tels que les
propriétés, ne sont pas sérialisées de manière adéquate.
Afin de contourner ce problème, la méthode toJson() a été ajoutée à chaque classe qui doit
persister. Dans la classe SynopticComponent, voici à quoi elle ressemble :

public toJson(): ISynoptic {

// Main synoptic implementation


const synoptic: ISynoptic = {
_id: this._id,
name: this.name,
width: this.width,
height: this.height,
components: new Array<IComponent>()

2. Capacité d’un outil à proposer des identifiants en inspectant le code source existant.

Sébastien Schoepfer Page 24


};

this.components.forEach((comp) => {
synoptic.components.push(comp.toJson());
});

return synoptic;
}
Listing 6.6 – Sérialisation d’un synoptique

Il s’agit de recréer un objet qui correspond parfaitement à l’interface implémentée. De cette


manière, il sera très aisé de sérialiser le synoptique en JSON.

6.5.2 Chargement des données du synoptique

L’API REST fournie par le serveur permet d’obtenir une représentation JSON de synoptique.
Il s’agit d’un objet JSON qui correspond à l’interface ISynoptic. Voici un exemple de cette re-
présentation :
"_id" : ObjectId("59bd0b3af9dbef1af10ff24b"),
"name" : "Synoptique de démonstration",
"width" : 1000,
"height" : 900,
"components" : [{}]
Listing 6.7 – JSON d’un synoptique vide

Il suffit de transtyper l’objet JSON avec l’interface ISnoptic pour manipuler directement les pro-
priétés du synoptique. Il est clair que l’on ne peut pas créer une instance de la classe Synoptic-
Component de cette manière. Cependant, en faisant ainsi nous profiterons d’un typage fort
sur les variables de types ISynoptic. Cela évitera de nombreuse erreur qui se verront lors de la
transpilation. De plus, nous tirerons parti de toute la puissance des compléteurs de code offerts
par les environnements de développement.
La propriété ”_id” du listing précédent correspond à l’identifiant unique du synoptique. Cette
information est gérée par la base de données.

6.5.3 Instanciation de synoptique

Le processus d’instanciation de la classe SynopticComponent est détaillé dans cette sec-


tion. Commençons par visualiser quelles sont les classes qui entrent en jeu dans ce processus.

Figure 6.6 – Instanciation de synoptique - Classes

Sébastien Schoepfer Page 25


Note sur Angular

Si vous n’êtes pas familier avec le framework Angular, vous vous demandez sûrement
pour quelles raisons toutes les classes du diagramme précédent sont suffixées par le terme
”Service”. Il s’agit d’une convention de codage du framework. Angular utilise l’injection
de dépendance pour fonctionner. En décorant une classe avec l’attribut @Injectable()
et en la déclarant au bon endroit dans AppModule, il est possible d’injecter une classe
dans d’autres classes de l’application. Le framework ne crée qu’une seule instance de
ces classes. Toutes celles qui se terminent par ”Service” dans le schéma précédent ont
été déclarées de cette manière. Nous délèguons ainsi la gestion de l’instance unique de
la classe au framework.

Le diagramme de séquences suivant représente les interactions entre les éléments de la figure
6.6. Le processus a été réparti entre plusieurs classes pour être le plus modulaire possible. Ceci
a également pour avantage de rendre l’application plus aisée à tester. Il est également plus
simple de la maintenir car chaque classe a une mission bien précise.

Figure 6.7 – Instanciation de synoptique - Séquence

Résumons les missions de chacune des entités :


SynopticFactoryService Crée une instance du synoptique et ajoute les composants en
fonction des données récupérées
SynopticServiceData Récupère les données du synoptique sur le serveur
SynopticComponent Attribue les différentes valeurs au composant
ComponentFactoryService Instancie le composant

6.5.4 Les classes SynopticFactoryService et SynopticServiceData

La classe SynopticFactoryService fait appel à SynopticServiceData pour récupérer les don-


nées d’un synoptique. Nous avons vu dans les sections précédentes comment les données
du synoptique étaient représentées au format JSON (listing 6.7). Nous allons approfondir cette
structure afin de comprendre comment est représentée une instance d’un composant au sein
du synoptique.
{
... // données du synoptique

Sébastien Schoepfer Page 26


"components" : [
{
"height" : null,
"width" : null,
"left" : 532,
"top" : 65,
"type" : 1,
"name" : "LabelComponent",
"_id" : ObjectId("59bd1d82f9dbef1af10ff2a0"),
},
}
Listing 6.8 – JSON d’une instance composant

Nous pouvons voir ici les différentes propriétés que nous retrouverons dans l’interface ICom-
ponent. La propriété type est un type énuméré (ComponentType) qui peut prendre une des
deux valeurs suivantes : 0 = Composant utilisateur, 1 = Composant par défaut. Elle sera utilisée
par la classe ComponentFactoryService pour savoir comment construire le composant.
Lors de l’instanciation d’un synoptique, SynopticFactoryService ajoutera tous les composants
au synoptique en appelant la méthode AddComponent de SynopticComponent avec en pa-
ramètre l’instance de l’interface IComponent correspondant au composant à ajouter.

6.5.5 La classe ComponentFactoryService

Nous allons exposer ici le fonctionnement de la classe ComponentFactoryService. Rap-


pelons tout d’abords, qu’il s’agit d’un service et que par conséquent il n’existe qu’une seule
instance de cette classe dans l’application.
Son objectif est d’instancier un composant en fonction du type et du nom du composant. Le
type énuméré ComponentType de la figure 6.5 permet de déterminer si le composant que l’on
doit instancier est de type utilisateur (provient de la base de données) ou par défaut (est une
classe de notre application). Le diagramme suivant explique le processus d’instanciation de
composant tel qu’il se déroule dans la classe ComponentFactoryService.

Sébastien Schoepfer Page 27


Figure 6.8 – Instanciation du composant

Lorsqu’il faut instancier un composant utilisateur, il est nécessaire d’aller chercher la définition
de ce composant dans la base de données. Cette opération est réalisée grâce à la classe
UserComponentDataService. Cette classe est chargée d’interroger l’API REST pour obtenir le
JSON représentant la définition d’un composant utilisateur. Comme pour le synoptique, une
interface partagée entre le client et le serveur a été créé pour représenter cet élément. Cette
interface n’est implémentée par aucune classe. Sa seule utilité est de fixer un contrat entre le
JSON attendu par le client et celui fourni par le serveur.

Figure 6.9 – Interface IComponentDefinition

Voyons maintenant un exemple de JSON correspondant à cet interface :


{
"_id" : ObjectId("59b933393c368851a7b267dc"),
"name" : "ComposantDemo",
"resizable" : true,
"template" : "<svg>...<svg>",
}
Listing 6.9 – JSON d’une définition de composant

L’identifiant ne figure pas dans l’interface car il n’est pas utilisé. Nous utilisons la propriété name
pour identifier une définition de composant. C’est d’ailleurs cette valeur qui fait le lien entre

Sébastien Schoepfer Page 28


l’implémentation d’un composant (dans un synoptique) et la définition telle qu’elle est décrite
ici.
Nous avons volontairement exclu tout ce qui concerne les propriétés dans cette section. Ce
thème sera traité dans le chapitre suivant.

6.5.6 La classe UserComponentCompilerService

Abordons la phase de compilation de composant utilisateur. Il s’agit d’une étape clé dans
cette application. C’est grâce à ce mécanisme qu’il est possible de créer des composants
dynamiquement à partir du contenu de la base de données.

Note sur Angular

Angular est nativement écrit en Typescript. Cependant ce langage n’est pas supporté
par les navigateurs. Pour cette raison, il est nécessaire de transpiler le code Typescript
vers du code JavaScript compatible. L’outil Angular-cli fourni par Google intègre cette
étape de transpilation dans le processus de construction de l’application.
Lors de l’exécution d’une application Angular, chaque composant doit être compilé.
Attention, on ne parle pas ici de la transpilation abordée précédemment. Il s’agit là d’une
interprétation du template HTML lié à chaque composant. La documentation Angular
utilise le terme de compilation pour parler de ce processus.

Just-in-time VS Ahead-of-time Il existe deux modes de compilations différents avec An-


gular. La compilation dite Just-in-time, JIT : l’application compile les composants à la vo-
lée lors de son exécution, plus précisément lorsqu’un composant est appelé. Cette étape
ralentit l’application. De plus, il est nécessaire d’embarquer le compilateur de compo-
sant dans l’application, ce qui l’alourdit grandement.
La compilation Ahead-of-time : AOT est arrivée avec les versions plus récentes du frame-
work. Lorsque l’application est construite, les composants sont précompilés, ils sont ainsi
beaucoup plus rapides à charger à l’exécution et les performances de l’application sont
meilleures.

Impact sur ce projet Nous compilons du template HTML à la volée, il est donc absolu-
ment nécessaire d’embarquer le compilateur de composants au sein de l’application.
Cependant, il serait intéressant de pouvoir utiliser la compilation AOT pour le projet et
d’embarquer le compilateur JIT et ne l’utiliser que pour les composants utilisateurs. Ceci
n’a pas pu être réalisé, car l’outil Angular-cli ne permet pas de le faire nativement. Au
vu des divers recherches effectuées sur le net, il semble possible d’utiliser ce mélange de
compilations. Cependant les exemples trouvés sont très compliqués. Il est fort probable
que les futurs versions d’Angular-cli permettront ceci.

La classe UserComponentCompilerService s’occupe de compiler les composants utilisateurs.


Le diagramme présente ce processus. Pour des raisons d’optimisation, un système de mise en
cache des composants compilé a été implémenté.

Sébastien Schoepfer Page 29


Figure 6.10 – Compilation du composant

Le listing qui suit permet de créer la classe représentant le composant utilisateur. Nous déclarons
la classe au sein d’une méthode, il faut également la décorer comme nous le faisons pour tout
autre composant Angular. Le type est ensuite retourné.
private createNewComponent(css: string[], tmpl: string): Type<UserComponent> {

@Component({
selector: 'ofact-user-component',
styles: css,
template: tmpl,
})
class CustomDynamicComponent extends UserComponent {
constructor(eRef: ElementRef) {
super(eRef);
}
}

return CustomDynamicComponent;
}
Listing 6.10 – Création du type du composant utilisateur

Dans la portion de code ci-dessous nous créons un module comme on l’a fait précédemment
pour le composant. Ce module ne contient qu’un seul composant, à savoir le composant utili-
sateur que nous désirons compiler et qui a été créé dans le listing 6.10.
private createComponentModule(componentType: Type<UserComponent>): any {

@NgModule({
imports: [],
declarations: [componentType]
})
class RuntimeComponentModule {}

return RuntimeComponentModule;
}
Listing 6.11 – Création d’un module

Sébastien Schoepfer Page 30


Voici comment les deux méthodes précédentes sont appelées :

const type = this.createNewComponent(styles, template);


const module = this.createComponentModule(type);

return new Promise((resolve) => {


this.compiler
.compileModuleAndAllComponentsAsync(module)
.then((moduleWithFactories) => {

const factory = moduleWithFactories.componentFactories.find((x) => x.


componentType == type);
this.cacheOfFactories.set(template, factory);
resolve(factory);

}).catch(error => {
console.log(error);
});
});
Listing 6.12 – Compilation d’un composant utilisateur

Le template passé en paramètre de la méthode createNewComponent provient de la base


de données. Une fois le type du module créé, il est compilé grâce à la méthode compileModu-
leAndAllComponentsAsync du compilateur JIT 3 . Pour être plus précis, nous ne compilons pas
directement le composant, mais une factory qui permettra d’instancier le composant.

Bien que relativement compliqué, ce procédé a un avantage non négligeable : il permet d’uti-
liser toute la puissance du système de modèle (templating) propre à Angular. Il est ainsi possible
de faire appel à des méthodes Typescript directement depuis le code HTML, mais également
d’effectuer des calculs ou utiliser le data-binding 4 . Chaque composant compilé hérite de la
classe UserComponent nous pouvons donc utiliser toutes les méthodes implémentées par cette
classe directement dans le template.

Note sur Angular

Vous l’aurez remarqué, Angular utilise beaucoup le terme Factory dans ses identifiants
lorsqu’il s’agit de création dynamique de composant. Dès que nous voulons dynami-
quement ajouter un composant à un autre, il faut obtenir la factory de ce composant
pour l’instancier de manière dynamique. Ceci est rendu possible grâce au service Com-
ponentFactoryResolver du framework. Ce dernier permet d’obtenir la factory d’un com-
posant en fonction d’un type (pour autant qu’il n’ait pas été compilé dynamiquement
comme nous l’avons précédemment fait). Il ne faut pas le confondre avec les différentes
classes factory qui ont été implémentées par nos soins. Elles ont des missions similaires mais
ne font pas partie d’Angular.

6.6 Objets métiers - Propriétés, variables et transformations

La notion de ”propriété” est un concept clé de ce système. Grâce aux propriétés, il est
possible d’interagir sur certains attributs du composant. Nous pouvons par exemple définir une
chaine de caractères, ou la couleur d’un trait ou choisir d’afficher ou non un composant.
Chaque composant implémente ses propres propriétés, il n’y a pas de propriété que l’on re-
trouve sur tous les composants. Il revient au créateur du composant de choisir ce qu’il souhaite
afficher ou non. Les attributs de width, height, left et top de l’interface IComponent (implé-
mentée par la classe BaseComponent) ne sont pas des propriétés comme décrites dans ce
chapitre.
3. Tout ce processus a été repris de l’article suivant https://stackoverflow.com/questions/38888008/how-
can-i-use-create-dynamic-template-to-compile-dynamic-component-with-angular
4. Technique permettant d’afficher le contenu de variables à l’utilisateur et de les modifier

Sébastien Schoepfer Page 31


Figure 6.11 – Fonctionnement global des propriétés

Chaque définition de composant entraine l’implémentation d’une liste de définition de pro-


priétés. Chaque composant d’un synoptique possède une instance de la propriété (appelée
simplement ”propriété” sur la figure 6.11). En d’autres termes, la définition de la propriété permet
de la créer en lui donnant un type, un nom et une valeur par défaut.

Figure 6.12 – Définition de propriété - l’interface

Il convient de préciser que tout comme pour les synoptiques et les composants, l’usage des
interfaces à des fins de persistance a également été appliqué. Voici un exemple de JSON
représentant une définition de propriété au sein d’une définition de composant :
// ... Attributs de ComponentDefinition ...
"properties" : [
{
"type" : 0,
"name" : "Text",
"defaultValue" : "Saisissez du texte ici ..."
},
// ...
]
Listing 6.13 – JSON d’une définition de propriété

L’instance de la propriété permet de lui donner la valeur qui lui est attribuée au sein du synop-
tique. Dans l’interface IProperty l’attribut definition permet de faire le lien avec une définition de
propriété du composant. Il est ainsi possible de déterminer quelle instance de propriété rempli
quelle définition.

Sébastien Schoepfer Page 32


Figure 6.13 – Propriétés - l’interface

Le listing suivant est un exemple du JSON d’une propriété au sein d’un composant (lui-même
situé dans un synoptique).
// ... Attributs du Synoptique ...
"components" : [
{
// ... Attributs du Composant ...
"properties" : [
{
"variable" : null,
"value" : "J'ai saisi un texte !",
"definition" : "Text",
},
]
}
Listing 6.14 – JSON d’une propriété

En comparant les listing 6.13 et 6.14, nous constantons que l’attribut Name de la définition
de propriété correspond à l’attribut Definition de la propriété. Ces deux éléments permettent
d’établir le lien entre une propriété et sa définition.

6.6.1 Type de propriétés

Figure 6.14 – Les différents types de propriétés

Les propriétés peuvent être divisées en deux groupes : les propriétés en lecture et les propriétés
en écriture.

Propriété en lecture

Toutes ces propriétés héritent de BaseReadProperty. Leur but est de définir une valeur au sein
d’un composant. Elles peuvent contenir une valeur constante ou un lien sur une variable Woopsa.
Elles sont nommées propriétés en lecture car elle permettre d’utiliser (en lecture) la valeur d’une
variable Woopsa. Nous avons actuellement implémenté quatre types de propriétés :

ColorProperty Permet de choisir une couleur au sein d’un composant, p. ex : couleur de


fond d’un composant.

Sébastien Schoepfer Page 33


BooleanProperty Permet de définir une valeur booléenne au sein d’un composant, p. ex :
afficher ou non le composant.
TextProperty Permet de préciser une chaine de caractères au sein d’un composant, p.
ex : texte d’un libellé.
NumberProperty Permet de spécifier une valeur numérique (entière ou réelle) au sein d’un
composant, p. ex : hauteur d’un rectangle.

Une propriété en lecture peut avoir soit une valeur constante soit être liée à une variable. Il n’est
pas possible qu’elle dispose des deux éléments simultanément.
La valeur de la propriété est implémentée par la classe PropertyBase. Comme défini dans l’in-
terface IProperty, le champ value est de type any. Cela signifie que nous pouvons mettre n’im-
porte quel type de valeur dans cet attribut. Les quatre classes concrètes décrites juste au-dessus
imposent l’usage d’un constructeur obligeant l’utilisateur à passer une valeur du type requis par
la propriété. Le type any facilite l’usage de la valeur de la propriété dans le template HTML des
composants. Nous verrons un peu plus loin comment la valeur d’une propriété est remontée
dans le template du composant.

Propriété en écriture

Il existe qu’une seule propriété en écriture. Il s’agit de la classe WriteProperty. Elle a pour objectif
de modifier la valeur d’une variable Woopsa. Contrairement aux propriétés en lecture, elle est
obligée de posséder une valeur constante et d’être liée à une variable. La première étant la
valeur à donner à la seconde lorsqu’une action est effectuée.
Ce type de propriété a été conçu pour répondre à une action de l’utilisateur. Elle fonctionne
avec l’action onClick. Il est possible de l’associer au ”clique” sur un élément de composant (par
exemple un bouton). Ainsi lorsque l’action se produira, la propriété ira modifier la valeur sur le
serveur Woopsa.
Cette propriété n’était initialement pas prévue dans le planning. Aucune exigence fixée lors
de la pré-étude ne demandait ce développement. Cependant, sur l’impulsion des conseillers
et avec le consentement du mandant ce développement a pu été intégré au projet.

6.6.2 Les variables

Nous allons traiter des variables et de leurs liens avec les propriétés. Au sein de ce projet, les
variables désignent des valeurs provenant du protocole Woopsa. Comme vous l’aurez compris,
il est possible de s’abonner à une variable pour être notifié de ses changements de valeur.

Figure 6.15 – Variables et propriétés

Sébastien Schoepfer Page 34


Une variable est composée essentiellement de deux éléments :
propertyPath Le chemin vers la variable, par exemple : Floor1/Tank1/Content
oFactoryWoopsaValueType Type de la variable, par exemple : Logical, Integer, Real, ...
Ces deux informations permettent d’établir le lien avec la variable.
Le chemin de la propriété contient son url sans la partie nom du serveur, port et préfix Woopsa.
Ces informations sont définies dans le client Woopsa. Pour cette raison, il n’est pas possible de
se connecter à plusieurs serveur Woopsa. Les raisons de cette décision sont expliquées dans le
chapitre traitant du client Woopsa.
L’attribut oFactoryWoopsaValueType définit le type de variable que nous souhaitons écouter.
Ce type correspond au WoopsaValueType implémenté dans le client Woopsa. Nous avons
volontairement réimplémenté WoopsaValueType pour éviter de stocker directement en base
de donnée le WoopsaValueType. De cette manière si le type WoopsaValueType évolue, il sera
plus aisé de modifier la correspondance entre les deux types, plutôt que de modifier les données
dans la base.
Comme étudié précédemment, ces informations font partie d’une interface qui est utilisée pour
enregistrer les données.
Pour que la valeur d’une variable soit affectée à une propriété, il est nécessaire de s’abonner à
celle-ci. Voici comment cette opération est effectuée. La classe BasePropery implémente une
méthode qui est exécutée juste après qu’une variable soit attribuée à la propriété. Il s’agit de
la méthode afterVariableSet. Cette dernière est redéfinie comme détaillé ci-dessous dans la
classe BaseReadProperty.
protected afterVariableSet() {
this.value = null;

if (this.variable != null) {
// Getting the value of the variable
this.woopsaClient.read(this.variable.propertyPath).then((x) => {this.
variableValueChanged(x); });
// To be notify of changes
this.woopsaClient.onChange(this.variable.propertyPath, this.variableValueChanged,
this.variableValueChangedRegistered);
}
}
Listing 6.15 – BaseReadProperty - afterVariableSet

Dans un premier temps, nous allons lire le contenu de la variable pour pouvoir donner une
valeur à la propriété. Ensuite nous nous abonnons à la variable. A chaque fois que le serveur
notifiera le client Woopsa d’un changement, la méthode variableValueChanged est exécutée.
Elle s’occupe de mettre à jour la propriété avec la nouvelle valeur de la variable.

6.6.3 Les transformations

Dans certain cas, une valeur récupérée depuis une variable nécessite d’être transformée
avant d’être utilisée au sein d’un composant. Il est par exemple intéressant de faire varier une
couleur en fonction d’une variable numérique. Pour cette raison, un système de transformation
a été mis en place, selon le schéma suivant :

Sébastien Schoepfer Page 35


Figure 6.16 – Propriétés et transformations

Il y a actuellement quatre types de transformations. Chacune est composée d’une classe et


d’une interface (à nouveau essentiellement utilisée pour la persistance). Chaque transforma-
tion doit implémenter la méthode transform. Cette dernière prend en paramètre la valeur à
transformer et retourne une valeur du type souhaité par la propriété.
Lorsqu’une propriété dispose de transformations, à chaque fois que la valeur d’une variable
change elle passe par la méthode transform des transformations avant d’être récupérée par le
composant. Pour la transformation d’une valeur booléenne avec la classe TransformationBoo-
leanToColor, une seule transformation peut être ajoutée. Voici le code de la méthode transform
évoquée précédemment :
public transform(value: any): string {
const booleanValue: boolean = Boolean(value);

if (booleanValue) {
return this.trueColor;
} else {
return this.falseColor;
}
}
Listing 6.16 – TransformationBooleanToColor - transform

Les transformations qui permettent de passer d’une valeur numérique vers d’autres valeurs
(couleur, booléen ou texte) dérivent du type BaseTransformationNumber. Dans ce cas, il est
possible, voir nécessaire, d’ajouter plusieurs transformations du même type. De cette manière,
nous pouvons couvrir une plage illimitée de valeurs numériques. Prenons un exemple : Ima-
ginons une variable qui représente la température d’un élément. On souhaite changer une
couleur en fonction de cette température. Admettons que nous souhaitons afficher du bleu
si nous sommes en dessous de 0, du vert entre 0 et 20 et du rouge au-delà. Voilà ce à quoi
ressemblerait la liste des transformations :

Sébastien Schoepfer Page 36


Figure 6.17 – Exemple de transformations numérique vers couleur

Pour effectuer la transformation, la valeur de la variable se place à gauche du symbole (<, <=,
>, >=, =).
L’ordre dans lequel apparait ces transformations est très important. L’ordre d’affichage permet
de définir la valeur de l’attribut order des transformations. La liste des propriétés est toujours triée
de manière croissante selon cet attribut. Voyons de plus près ce qu’il se passe dans la méthode
variableValueChanged de la classe BaseReadProperty.

Figure 6.18 – Algorithme d’application d’une transformation

Nous comprenons pourquoi l’ordre d’application des transformations a une grande impor-
tance. La première transformation qui retourne un résultat est appliquée, les autres sont igno-
rées.

Sébastien Schoepfer Page 37


6.6.4 Instanciation d’une propriété

Nous avons vu précédemment comment se déroulait l’instanciation d’un synoptique et


de ses composants. Nous allons compléter ce processus avec l’instanciation des propriétés et
des variables. L’instanciation d’une propriété ne se déroule pas de la même manière pour un
composant utilisateur que pour un composant par défaut. Dans le premier cas, les informations
proviennent de la base de données. Il est donc nécessaire de passer par une Factory. Dans
le second cas, le composant étant compilé l’appel de la méthode generateProperties des
composants par défaut suffit.

Figure 6.19 – Instanciation des propriétés

Pour bien comprendre cette différence, voici l’implémentation de la méthode createProperty


de la classe PropertyFactoryService :
public createProperty(propDef: IPropertyDefinition): BaseProperty {

switch (propDef.type) {
case PropertyType.TextProperty:
return new TextProperty(propDef.name, propDef.defaultValue, this.woopsaClient)
;
case PropertyType.BooleanProperty:
return new BooleanProperty(propDef.name, propDef.defaultValue, this.
woopsaClient);
case PropertyType.ColorProperty :
return new ColorProperty(propDef.name, propDef.defaultValue, this.woopsaClient
);
case PropertyType.NumberProperty :
return new NumberProperty(propDef.name, propDef.defaultValue, this.
woopsaClient);
case PropertyType.WriteProperty :
return new WriteProperty(propDef.name, this.woopsaClient);
default:
throw new TypeError('Unknown property type exception !');
}
}
Listing 6.17 – PropertyFactoryService - createProperty

Et voici l’implémentation de la méthode generateProperties de la classe LabelComponent qui


est un des composants par défaut livré avec l’application.
public generateProperties(): void {
this.properties.push(new TextProperty('Text', 'Caption', ServiceLocator.injector.get(
WoopsaClient)));

Sébastien Schoepfer Page 38


this.properties.push(new BooleanProperty('Visible', true, ServiceLocator.injector.get(
WoopsaClient)));
this.properties.push(new NumberProperty('FontSize', 15, ServiceLocator.injector.get(
WoopsaClient)));
this.properties.push(new ColorProperty('FontColor', '#000000', ServiceLocator.injector
.get(WoopsaClient)));
}
Listing 6.18 – LabelComponent - generateProperties

Nous avons expliqué comment les propriétés sont instanciées, nous allons détailler comment
leurs sont attribués des valeurs, des variables et des transformations.

Figure 6.20 – Instanciation des variables et transformations

Ce diagramme de séquence correspond à la suite du diagramme 6.19. Une fois que le com-
posant est créé avec ses propriétés, il est nécessaire d’affecter à chacune d’elles la valeur, la
variable et les transformations qui lui est attribuée au sein du synoptique.

6.7 Visualiseur de synoptique

Etant donné que le cœur de l’application a été détaillé, nous allons aborder le visualisateur
de synoptique.

Sébastien Schoepfer Page 39


Figure 6.21 – Visualisateur de synoptique

Cette page est composée de deux éléments principaux : La liste de synoptique et le synoptique
affiché.
Le synoptique à afficher est passé en paramètre dans l’URL de l’application. La dernière partie
de cette adresse représente le synoptique à afficher.

Figure 6.22 – Chemin vers un synoptique à visualiser

La méthode suivante permet de récupérer l’identifiant du synoptique à partir de l’url de la page


et de le charger.
ngOnInit() {
this.activatedRoute.params.subscribe(params => {
this.synopticPlaceHolder.clear();
this.synopticLoader.loadSynopticFromID(this.synopticPlaceHolder, params['synoptic'
]);
});
}
Listing 6.19 – Obtention de l’identifiant du synoptique

Note sur Angular

La méthode ngOnInit est fournie par le framework et plus particulièrement par l’interface
OnInit. Il suffit à un composant d’implémenter cette interface. Ainsi lorsqu’un composant
est initialisé (construit et affiché), ladite méthode est exécutée.

6.8 Concepteur de synoptique

Cet outil représenté par la classe SynoptiqueDesignerPageComponent est séparé en trois


éléments principaux :

ComponentToolBox la liste des composants qu’il est possible d’ajouter sur un


synoptique.
SynopticDesignedComponent le synoptique en cours d’édition.
PropertyToolBox la liste des propriétés du composant sélectionné.

Sébastien Schoepfer Page 40


Figure 6.23 – Concepteur de synoptique

6.8.1 La page SynoptiqueDesignerPageComponent

Cette classe fait le lien entre les trois éléments cités précédemment. Elle permet notamment
de créer, d’ouvrir et de sauvegarder un synoptique.
Le chargement d’un synoptique utilise le même principe que le visualiseur. L’identifiant de la vue
à charger est passé en paramètre de l’url. La création d’un nouveau synoptique, fonctionne
de la manière suivante :
if (result != null && result != '') {
const newSynoptic: ISynoptic = {
name: (result as string),
width: 1000,
height: 900,
components: null
};

this.synopticPlaceHolder.clear();
this.currentSynoptic = <SynopticDesignedComponent>this.synopticFactory.
loadSynopticFromData(
this.synopticPlaceHolder, newSynoptic,true).instance;
}
Listing 6.20 – Création d’un nouveau synoptique

Une instance d’un synoptique vide est créée. Elle est ensuite passée à la méthode loadSynop-
ticFromData de la classe SynopticFactoryService. Comme nous l’avons vu dans les chapitres
précédents, cette classe va se charger de créer l’instance du synoptique. Attention, dans ce
contexte, nous utilisons le paramètre designedMode pour demander à la factory de créer
une instance SynopticDesignedComponent plutôt qu’une instance de SynopticComponent.
La classe SynopticDesignedComponent dérive de SynopticComponent, elle ajoute simplement
tout une série de fonction permettant l’édition du synoptique.
Pour la sauvegarde, nous faisons appel à la méthode saveSynoptic de la classe SynopticData-
Service. Nous lui passons en paramètre la représentation JSON du synoptique en cours d’édition
obtenu grâce à la méthode toJson de la SynopticComponent.

6.8.2 Le composant ComponentToolBox

Figure 6.24 – Boîte à outil de composants

Sébastien Schoepfer Page 41


Cette classe fournit la liste des composants disponibles pour
l’édition des synoptiques.

Les listes de composants par défaut et de composants utilisateurs


sont fournies par la classe ComponentFactoryService. Pour les
composants par défaut, elle fournit une liste de Tuple 5 dont le
premier élément représente le nom du composant et le second le
nom d’une image à afficher.

Pour les composants utilisateurs, seul le nom est retourné. Par


manque de temps, aucune gestion d’icône n’a pu être implémen-
tée pour les composants utilisateurs.
Figure 6.25 – Liste des composants

Les événements defaultComponentAdd et userComponentAdd permettent d’être notifié lorsque


l’utilisateur désire ajouter un composant (par un double clique). La classe SynopticDesignerPa-
geComposant s’abonne à ces deux événements. C’est elle qui ensuite va lancer la création
des composants.

Figure 6.26 – Ajouter un composant

La méthode addNewComponent du SynopticDesignedComponent va procéder de la même


manière que la méthode addComponent du parent. A la différence près que le composant
n’a pas besoin d’être rempli avec des données.

6.8.3 Le composant SynopticDesignedComponent

Figure 6.27 – Synoptic en mode édition

Comme énoncé précédemment, cette classe hérite directement de SynopticComponent. Il


s’agit donc d’un synoptique. Cette classe apporte toutes les fonctionnalités qui permettent de
5. Liste de N éléments dans laquelle chaque élément peut être de différents types.

Sébastien Schoepfer Page 42


sélectionner un composant avec la souris, de le déplacer et le redimensionner. Il est possible
de s’abonner à son événement componentSelect afin d’être notifié d’une sélection de com-
posant. Elle est composée d’une instance de ComponentSelectorComponent. Ce composant
représente le carré de sélection d’un composant, il s’agit de la partie violette de la figure ci-
dessous.

Figure 6.28 – Sélecteur de composant

Sélection d’un composant

Cet outil est piloté par la classe SynopticDesignedComponent. Lorsque l’utilisateur clique sur le
synoptique (événement onMouseDown), il va détecter si cette opération est effectuée sur un
composant. Si c’est le cas, nous conservons la référence du composant dans l’attribut current-
SelectedComponent de la classe SynopticDesignedComponent. Voici comment cette opéra-
tion est effectuée :
for (const comp of this.components) {
comp.isSelected = comp.eRef.nativeElement.contains(event.target);
if (comp.isSelected) {
this.currentSelectedComponent = comp;
}
}
Listing 6.21 – Sélection d’un composant

La liste de composants est parcourue et ainsi, le composant visé par l’événement onMouse-
Down sera sélectionné, grâce à l’instruction {comp.eRef.nativeElement.contains(event.target);}.
Cette instruction détecte si l’événement a eu lieu au sein du DOM 6 lié au composant.
Cette étape va également entrainer le déclenchement de l’événement componentSelect.
Cela permettra à la page SynopticDesignedComponent de savoir quel composant est sélec-
tionné.

Déplacement d’un composant

L’utilisateur peut déplacer un composant grâce à une opération de cliquer-déplacer. Les évé-
nements onMouseDown et onMouseUp de la classe SynopticDesignerPageComponent vont
modifier l’état de sa propriété isDragging. Le premier la mettra à vrai alors que le second à
faux. Lorsqu’elle est vraie, l’événement onMouseMove va automatiquement modifier la posi-
tion du composant sélectionné en fonction du mouvement de la souris. Lors du relâchement
du clique (onMouseUp), le composant n’est plus déplacé par le mouvement de la souris.

Sélecteur de composant

Lorsqu’un composant est sélectionné, ComponentSelectorComponent se positionne au-dessus


de celui-ci. Lors du déplacement du composant, le sélecteur va également suivre son dépla-
cement.
<div class="border">
<div *ngIf="resizable" #handleTL class="handle topLeft"></div>
<div *ngIf="resizable" #handleTR class="handle topRight"></div>
<div *ngIf="resizable" #handleBL class="handle bottomLeft"></div>
<div *ngIf="resizable" #handleBR class="handle bottomRight"></div>
</div>
Listing 6.22 – Code HTML sélecteur de composant

6. DOM, signifie Document Object Model, structure de données arborescente représentant les éléments d’une
page web.

Sébastien Schoepfer Page 43


Il s’agit d’un simple élément DIV. Aux quatre angles de cet élément, nous plaçons quatre autres
DIV qui représentent les poignées de dimensionnement du composant.
Ces poignées ne sont visibles que si le composant sélectionné est redimensionnable. En effet,
certains composants comme les lignes ou les rectangles peuvent être redimensionnés grâce
à ces poignées. D’autres comme les textes, ne le sont pas car leur taille est déterminée en
fonction de leur contenu. Dans ce cas, les poignées ne sont pas affichées. Ceci est défini par la
personne qui implémente le composant, en donnant la valeur vrai ou faux à l’attribut resizable.
Le redimensionnement du composant à proprement parler est géré par la classe SynopticDe-
signedComponent. Lorsque l’utilisateur clique sur un composant sélectionné, elle interroge le
sélecteur de composant pour savoir si une des poignées est actionnée. Si c’est le cas, le com-
posant sera redimensionné plutôt que d’être déplacé.

Suppression d’un composant

La classe SynopticDesignedComponent permet également de supprimer un composant sélec-


tionné grâce aux touches Delete et Backspace. Il faut cependant faire attention à un point.
Lorsque javascript écoute les événements clavier, il le fait pour toute la page. De ce fait, si
nous utilisons les touches précitées lors de l’édition d’un texte, le composant sélectionné était
initialement tout de même supprimé.
Pour résoudre ce problème, nous avons rajouté un attribut privé nommé isSynopticActivated. Il
permet de savoir si l’utilisateur a sélectionné le synoptique.
@HostListener('document:mousedown', ['$event'])
private onGlobalMouseDown(event) {
this.isSynopticActivated = this.eRef.nativeElement.contains(event.target);
}
Listing 6.23 – Activation du synoptique

Grâce à cette méthode, chaque fois que l’utilisateur effectue un clique dans le navigateur
nous pouvons savoir s’il a été effectué dans le synoptique ou pas. Nous considèrons que le
synoptique n’est plus actif si un clique a été effectué en dehors de celui-ci. Dans ce cas, aucun
composant ne pourra être supprimé.

6.8.4 Boîte à outils de propriétés

Cette boîte à outil permet d’éditer les propriétés. Lorsque l’utilisateur va sélectionner un
élément dans le synoptique, la boîte à outils est informée et elle affiche les propriétés du com-
posant sélectionné. Il est possible d’éditer deux types de propriétés : les propriétés en lecture
(à gauche sur l’image) et les propriétés en écriture (à droite sur l’image).

Figure 6.29 – Propriétés en lecture et en écriture

Les propriétés en lecture peuvent être remplies par des constantes ou des liens sur des variables,
ceci implique deux types d’éditeur différents. Les propriétés en écriture sont éditées par un seul
type d’éditeur.

Sébastien Schoepfer Page 44


Figure 6.30 – Boîte à outil de propriétés

Les éditeurs de propriétés sont tous créés depuis la classe PropertyValueSelectorDialogCom-


ponent. Cette classe intermédiaire permet d’afficher les composants d’édition dans une boîte
de dialogue. Son contenu est généré de manière différente en fonction du type d’éditeur né-
cessaire : éditeur de constante, éditeur de variable ou éditeur de propriété en écriture.

Note sur Angular

Pour la gestion de la boîte de dialogue, nous avons utilisé le composant Dialog de


la librairie de composant Angular Material. Pour plus d’informations sur ce composant
https://material.angular.io/components/dialog/overview

6.8.5 Propriété en lecture - Editeur de constantes

A noter, nous ne reviendrons pas sur l’aspect visuel des éditeurs. Il a déjà été abordé pré-
cédemment.

Figure 6.31 – Editeur de constante

Création de l’éditeur

Pour générer les éditeurs de constante, nous utilisons une factory, à savoir la classe ConstantPro-
pertyEditorFactoryService. Celle-ci va créer le bon éditeur en fonction du type de la propriété
qui lui est passé. Elle contient un dictionnaire de correspondance entre le type de propriété et
la classe de l’éditeur lui correspondant.

Sébastien Schoepfer Page 45


Modification de la valeur choisie

Chaque éditeur de constante implémente la méthode getValue() qui est appelée pour définir
la nouvelle valeur de la propriété.

6.8.6 Explorateur de variable Woopsa

Avant d’aborder les deux types d’éditeur suivant, il est nécessaire d’aborder l’explorateur
de variables Woopsa. Il s’agit d’un composant créé pour parcourir un serveur Woopsa et sé-
lectionner une variable.
Ce composant se nomme WoopsaVariableViewerComponent. Voici un rappel de son aspect
visuel :

Figure 6.32 – Explorateur de variable Woopsa

Pour récupérer la variable sélectionnée, il suffit de s’abonner à l’événement onVariableSelec-


ted. Cet événement est déclenché à chaque fois que la sélection d’une variable change.
L’arbre est rempli grâce à la méthode meta du client Woopsa. En utilisant la récursivité, nous
parcourons l’entier du serveur pour récupérer toutes les variables dont il dispose.

Note sur Angular

Ce composant est basé sur le composant Tree de la librairie open source PrimeNG. Il
permet d’afficher une arborescence de donnée. Pour plus d’informations sur cet élément
https://www.primefaces.org/primeng/#/tree.

Sébastien Schoepfer Page 46


6.8.7 Propriété en lecture - Editeur de variables

Figure 6.33 – Editeur de lien vers une variable Woopsa

Nous allons commencer par expliquer comment la liste des transformations est affichée. Sur la
figure 6.34 nous pouvons voir qu’il est possible de sélectionner deux types de transformation. La
première ”No transformation” signifie que la valeur de la variable devra être reprise telle quelle.
La seconde ”Numerical value to text” permet d’afficher un texte en fonction d’un chiffre.

Figure 6.34 – Liste des transformations disponibles

Cette liste est remplie grâce à la classe TransformationEditorFactoryService. En fonction du type


de la variable sélectionnée et du type de la propriété que nous éditons, la méthode getAvai-
lableTransformation retourne une liste de transformations disponibles.
Cette classe permet également de créer le bon éditeur de transformation. En effet, lorsque
l’utilisateur sélectionne une des transformations disponibles dans la liste, la méthode create-
TransformationEditor de cette même entité est appelée pour instancier l’éditeur.
Pour fonctionner, la classe TransformationEditorFactoryService contient une liste de triplet ren-
dant possible les opérations décrites précédemment.
this.transformationEditorList.push([TextProperty.name, WoopsaValueType.Integer,[
noTransformation, numberToTextTransformation]]);
Listing 6.24 – Elément de la liste des transformations

Les éditeurs de transformations numériques

Les éditeurs NoTransformationEditor et BooleanToColorTransformationEditor sont de simples com-


posants Angular sans grande complexité. Analysons de plus près les éditeur de transformation

Sébastien Schoepfer Page 47


dérivant de la classe BaseNumberTransformationEditor.
Les trois enfants de cette classe sont très ressemblants. Il s’agit des classes : NumberToText-
TransformationEditorComponent, NumberToColorTransformationEditorComponent et Number-
ToBooleanTransformationEditorComponent. Elles ont toutes été décorées comme n’importe
quel composant Angular. A la différence près qu’elles pointent vers le même template HTML.
De cette manière, il est possible de faire varier la partie Typescript du composant sans toucher
à la vue.
Chacune de ces classes implémente sa propre méthode createTransformation qui permet
d’instancier une transformation qui lui correspond.
Sur la figure suivante, la partie en rouge foncé désigne la zone occupée par les composants
de transformation. Les zones en rouge clair désignent les zones communes à tous les dérivés de
la classe BaseNumberTransformationEditor, la zone en blanc est celle qui varie. da

Figure 6.35 – Transformations numériques

Chacune des transformations est représentée par le composant NumberTransformationElement-


Component et ce peu importe le type de transformation. Dans ce composant, nous faisons
varier le contenu de la zone blanche de la figure 6.35. Voici le code l’ayant permis :
<span *ngIf="isTransformationNumberToText()">
<md-input-container>
<input mdInput [(ngModel)]="getTransformationNumberToText().textOutput"
placeholder="Text" type="text">
</md-input-container>
</span>

<span *ngIf="isTransformationNumberToColor()">
<p-colorPicker [(ngModel)]="getTransformationNumberToColor().color"></p-colorPicker>
</span>

<span *ngIf="isTransformationNumberToBoolean()">
<md-slide-toggle [color]="primary"
[checked]="getTransformationNumberToBoolean().value"
(change)="onSliderChange($event)">

</md-slide-toggle>
</span>
Listing 6.25 – Template HTML des transformations numériques

Comme vous le constatez, nous utilisons la fonction ngif qui permet d’afficher des éléments
HTML seulement si la condition entre guillemets est vraie.
Chaque instance NumberTransformationElementComponent contient un pointeur sur la trans-
formation qu’elle représente. Il est ainsi possible d’effectuer un lien entre le template du com-
posant et la transformation.
La liste de la figure 6.35 est triée selon le champ order de la classe BaseTransformation. Chaque
élément de cette liste peut être déplacé grâce à la souris, ceci affecte la valeur de l’attribut
order .

Sébastien Schoepfer Page 48


Note sur Angular

Cette liste provient également de la librairie PrimeNG. Pour plus d’informations sur ce
composant : https://www.primefaces.org/primeng/#/orderlist

6.8.8 Propriété en écriture - Editeur

Le but de ce type d’éditeur est de sélectionner une variable et une valeur à lui attribuer
lorsque l’utilisateur effectue une opération.

Figure 6.36 – Editeur de propriété en lecture

Lorsque la variable est sélectionnée, un simple champ de saisie est affiché. La variable sélec-
tionnée et le texte saisi permettent de remplir les champs variable et value de la propriété.

Figure 6.37 – Editeur de propriété en écriture

Les propriétés en écriture n’étaient pas prévues dans le cahier des charges. Elles ont été ajou-
tées afin de montrer l’étendue des possibilités. L’éditeur que nous venons de présenter n’est
pas sécurisé. Il faudrait limiter la saisie aux valeurs compatibles avec la variable sélectionnée.

Sébastien Schoepfer Page 49


Architecture du serveur 7

7.1 Généralités

Dans ce chapitre, nous allons traiter du serveur. Ses deux missions sont de servir l’application
et de fournir l’API REST qui permet d’interroger la base de données. Il est composé de deux
éléments centraux. Une application Node.js et une base MongoDB. L’application Node.js est
développée à l’aide du framework Express.js et du framework Mongoose.
Rappelons que tout le serveur est développé en Typescript puis transpilé en JavaScript.

Express.js Ce système offre les fonctionnalités de base pour créer une API REST. Il simplifie la
création du serveur web ainsi que la gestion des routes.

Mongoose Cette librairie fournit les fonctionnalités pour manipuler la base MongoDB. Elle per-
met de créer les schémas et de les interroger pour récupérer les données. Rappelons que Mon-
goDB est une base de données dites NoSQL (”Not only structured query langage”). Dans ce
système de gestion de base de données, les données sont stockées sous la forme de docu-
ment JSON.

7.2 Schéma et base de données

Toute l’initialisation de la base de données se fait depuis le code du server. Pour qu’il soit
fonctionnel, il suffit d’avoir installé et lancé MongoDB. L’adresse du serveur est configurée dans
des fichiers JSON situés dans le dossier config. Il existe trois configurations : default, dev et test. En
fonction de l’environnement défini pour lancer le serveur Node.js, l’une ou l’autre configuration
sera chargée. Ainsi, il est possible d’avoir une base de données pour les tests et une pour le
développement.
L’instance de la base MongoDB est créée à la volée lors du premier accès.
Le schéma suivant présente la structure de l’application qui permet de gérer la base de don-
nées

Figure 7.1 – Serveur - Modèle et schéma

Sébastien Schoepfer Page 50


La classe DB est un conteneur qui possède la connexion à la base de données ainsi que les
deux modèles qui donnent l’accès à nos schémas.

7.2.1 Les schémas

Nous présentons ci-dessous comment sont créés nos schémas. Chacun d’eux sera utilisé
pour créer une collection dans la base MongoDB.
export let ComponentDefinitionSchema: Schema = new Schema({
name: String,
resizable: Boolean,
template: String,
properties: [{
type: {type: Number, enum: Tools.fromEnumToNumberArray(PropertyType)},
name: String,
defaultValue: {}
}]
});
Listing 7.1 – Schéma du modèle ComponentDefinition

export let SynopticSchema: Schema = new Schema(


{
name: String,
width: Number,
height: Number,
components: [{
name : String,
type : {type: Number, enum: Tools.fromEnumToNumberArray(ComponentType)},
top: Number,
left: Number,
width: Number,
height: Number,
properties: [{
definition: String,
value: {},
variable: {
type: {
oFactoryWoopsaValueType: {type: Number, enum: Tools.
fromEnumToNumberArray(oFactoryWoopsaValueType)},
propertyPath: String
},
required: false
},
transformations: [{}]
}]
}]
},
{ strict: false }
);
Listing 7.2 – Schéma du modèle Synoptic

Les schémas ci-dessus sont des structures JSON représentant les données que les collections
devront stocker. Chaque champ correspond à un attribut des interfaces IComponentDefinition
et ISynoptic. Rappelons que ces interfaces sont utilisées sur le client également.
Le type : {} permet de stocker n’importe quel type d’informations. Cela permet une grande sou-
plesse, mais comporte certains risques. Nous pourrions nous retrouver dans une situation dans
laquelle les données sont si-différentes qu’il devient trop compliqué de les gérer. Il est donc
nécessaire d’être très rigoureux et d’éviter au maximum le type : {}. Il faut aussi prévoir un mé-
canisme pour être capable de retrouver le type des données stockées lors de leur extraction.
Pour le cas des transformations, l’interface ITransformations contient une valeur énumérée qui
définit le type de la transformation.

7.2.2 Les modèles

Les deux portions de code suivantes définissent le modèles objet appliqué à notre schéma.
Lorsque nous effectuerons des opérations sur nos schémas, ce modèle sera utilisé par Mongoose
pour structurer les données.

Sébastien Schoepfer Page 51


export type ISynopticModel = ISynoptic & Document;
Listing 7.3 – Modèle ISynopticModel

export interface IComponentDefinitionModel extends IComponentDefinition, Document {}


Listing 7.4 – Modèle IComponentDefinitionModel

Dans le cas du synoptique nous avons utilisé une union de types. Cet artifice fourni par Typescript
permet de fusionner deux types en un seul. Il a été nécessaire de procéder ainsi car ISynoptic
possède le même attribut _id que Document. Il n’était donc pas possible de créer une nouvelle
interface implémentant ISynoptic et Document comme nous l’avons fait pour IComponentDe-
finitionModel.
Il est encore nécessaire d’instancier la collection ou de s’y connecter. Pour se faire, nous pro-
cédons ainsi :
this.synoptic = this.connection.model<ISynopticModel>('Synoptic', SynopticSchema);

this.componentDefinition = this.connection.model<IComponentDefinitionModel>('
ComponentDefinition', ComponentDefinitionSchema);
Listing 7.5 – Création des modèles

Ces deux lignes instancient les objets synoptic et componentDefinition de la classe DB. C’est à
travers ces objets que nous effectuerons toutes les opérations sur la base de données.

7.3 Services web REST

Pour commencer rappelons la définition d’un service web REST (ou API REST) tel que pré-
sentée lors de la pré-étude : ”Une API compatible REST, ou « RESTful », est une interface de pro-
grammation d’application qui fait appel à des requêtes HTTP pour obtenir (GET), placer (PUT),
publier (POST) et supprimer (DELETE) des données.” 1 .
Voyons comment se structure l’application.

Figure 7.2 – Serveur - Routes

Il est important de préciser que ce schéma représente une vue fonctionnelle des classes. L’im-
plémentation réelle des méthodes au sein des classes ComponentDefinitionRoute et Synop-
ticRoute diffère légèrement. Ces méthodes étant utilisées lors d’appel HTTP par le framework
Express.js, elles ont des paramètres nécessaires à son bon fonctionnement. Nous retrouvons ce-
pendant au sein de ceux-ci les paramètres que l’on peut voir dans le diagramme 7.2. Ils sont
simplement encapsulés dans des objets représentant des requêtes et réponses HTTP.
1. source : http://www.lemagit.fr/definition/API-RESTful

Sébastien Schoepfer Page 52


Chacune de ces méthodes correspond à une route HTTP, voici les différentes routes accessibles
sur ce serveur :
/synoptics
/synopticsName/
/synoptic/:id
/saveSynoptic
/deleteSynoptic/:id
/componentDefinitions
/componentDefintionsName
/componentDefintion/:name
Le code caché derrière ces méthodes est relativement simple. Par exemple, voici la méthode
qui permet d’obtenir une définition de composant :
private getComponentDefinition = (req: Request, res: Response, next: NextFunction) => {

this.db.componentDefinition.findOne({name: req.params.name}).exec().then(s => {


res.json(s);
next();
});

}
Listing 7.6 – Méthode getComponentDefinition

Grâce au modèle que nous avons vu dans la section précédente, il est très aisé de récupérer
un composant en fonction de son nom. La méthode findOne fourni par Mongoose prend en
charge ce travail.
Pour ces méthodes, nous avons utilisé la notation fléchée fournie par Typescript. Elle a l’avan-
tage de préserver le pointeur this lorsque la méthode est utilisée dans un callback (méthode
passé en paramètre d’une fonction). Dans ce contexte, le pointeur this n’est plus l’instance de
classe dont la méthode est membre, il est nécessaire d’utiliser cette notation pour éviter tout
problème.

Sébastien Schoepfer Page 53


Client Woopsa 8

8.1 Généralités

Ce client a été développé en suivant les spécifications du protocole que l’on peut trouver
sur www.woopsa.com. Il est également inspiré du client JavaScript fournit sur ce site. La société
mandante a également fourni un exemple de client Woopsa développé en Typescript. Cepen-
dant cet exemple n’était pas complet. La gestion du publier/s’abonner n’étant pas implémen-
tée, décision a été prise de le développer depuis la base. Cette opération a permis de bien
comprendre le fonctionnement du protocole.
Le client Woopsa a été développé dans son propre module. L’idée derrière cette séparation est
de limiter au maximum le lien avec l’application. D’ailleurs, seule quelques classes sont exposée
et utilisable par l’application. Il sera plus simple d’extraire le client pour en faire une librairie.
Ce client ne peut se connecter qu’à un seul serveur. Lors de l’analyse (durant la pré-étude),
le mandant avait émis l’idée de développé un serveur intermédiaire qui ferait le tampon entre
différents serveurs Woopsa et le client. Ceci permettrait entre autres de suivre et d’enregistrer
l’évolution de certaine variable. La communication entre ce serveur et le client serait effectuée
avec le protocole Woopsa tel que développée dans ce projet. Pour cette raison, le client n’a
pas été prévu pour se connecter à plusieurs serveurs simultanément.

8.2 Configuration

Pour utiliser ce client, il faut importer le module WoopsaModule dans le module de l’ap-
plication. La classe principale, à savoir WoopsaClient est un service (une seule instance dans
toutes l’application). Elle utilise l’injection de dépendance pour connaître la configuration qu’elle
doit utiliser pour se connecter au serveur Woopsa. Ainsi en injectant correctement la configura-
tion dans notre module AppModule, il est possible d’utiliser la classe WoopsaClient dans toutes
l’applications sans se soucier du serveur qui est utilisé.
L’adresse du serveur Woopsa et le port sont stocké dans le fichier de configuration du client
Angular. Il s’agit du même fichier qui contient les informations concernant l’API REST nécessaire
au bon fonctionnement de l’application.
C’est pour les raisons évoquées ci-dessus que les variables (classe Variable) ne conservent que
le chemin vers l’élément Woopsa désiré sans se soucier des informations propres au serveur.

8.3 Fonctions principales

La classe exposée au public est WoopsaClient. C’est depuis cette dernière que les opéra-
tions en lien avec Woopsa sont effectuées.

Sébastien Schoepfer Page 54


Figure 8.1 – Client Woopsa, les classes principales

meta Permet de récupérer l’arborescence du serveur. Cette méthode est utilisée


par l’explorateur de variable Woopsa du client (voir figure 6.32). Pour par-
courir entièrement le serveur, il est nécessaire d’utiliser cette méthode de
manière récursive, en l’appelant sur chaque objet Woopsa qu’elle retourne.
read Permet de lire la valeur d’une variable.
write Permet d’écrire une valeur dans une variable.
invoke Permet d’exécuter une méthode du serveur.
onChange Permet de s’abonner à une variable du serveur.
unSubscribe Permet de se désabonner d’une variable du serveur.
unSubscribeAll Permet de se désabonner de toutes les variables auxquels nous sommes
abonnés.

Les classes SubscriptionChannel et Subscription sont utilisés de manière interne. Elle permette
de gérer le système d’abonnement à une variable, via la méthode onChange.

8.4 Système d’abonnement côté serveur

Pour utiliser le système d’abonnement, le serveur Woopsa doit être muni de l’extension na-
tive SubscriptionService. Le serveur Node.js fournit par www.woopsa.org contient cette dernière.
Pour bien comprendre ce principe, voyons comment le serveur Woopsa fonctionne.

Sébastien Schoepfer Page 55


Figure 8.2 – Subscription service - http://www.woopsa.org/specifications/

Tout d’abord, il est nécessaire de créer un Subscription Channel en invoquant la méthode Crea-
teSubscriptionChannel sur le serveur. Cette méthode retourne l’id du canal créé. A parti de là,
il est possible de souscrire à une variable avec la méthode RegisterSubscription du serveur. Les
méthode sont invoquée sur le serveur en appelant la fonction invoke de notre classe Woopsa-
Client qui elle-même effectue un appel grâce au client Http d’Angular.
Pour récupérer les notifications, le client utilise la méthode WaitNotification du serveur. Cet ap-
pel est bloquant tant qu’aucune notification n’est à récupérer. Dès que des notifications se
présentent, le serveur les envoie au client. Ce dernier peut ainsi les traiter, et effectuer un nouvel
appel à WaitNotification. Cette méthode retourne des lots de notifications qui doivent ensuite
être attribués aux abonnements. WaitNotification prend en paramètre le SubscriptionChannel
ainsi que l’identifiant de la dernière notification. Il a besoin de cette information pour supprimer
les éléments qui ont déjà été récupérées par le client. Pour plus d’informations, veuillez consulter
la spécification du protocole : http://www.woopsa.org/specifications/.

8.5 Abonnement à une variable

Pour s’abonner à une variable, il faut utiliser la méthode onChange de la classe Woopsa-
Client. Voici le prototype de cette dernière :
public async onChange(path: string, onChangeCallBack: (value: WoopsaValue) => void,
onRegisterCallBack: (subId: number) => void = null)
Listing 8.1 – Classe WoopsaClient méthode onChange

Le premier paramètre représente le chemin vers la variable à laquelle s’abonner. Le second est
la méthode à appeler lorsque le client est notifié d’un changement de valeur par le serveur.
Le dernier est également une méthode, celle-ci est appelée lorsque la variable a bien été
enregistrée. Ce paramètre est optionnel.
Voici le processus d’abonnement à une variable Woopsa, pour des raisons de compréhension
et de lisibilité seul les étapes principales sont représentées dans ce schéma.

Sébastien Schoepfer Page 56


Figure 8.3 – Méthode onChange de WoopsaClient

Chaque appel à une méthode register correspond à un appel REST décrit dans la section pré-
cédente. Voyons de plus près le fonctionnement de la méthode waitNotification :

Sébastien Schoepfer Page 57


Figure 8.4 – Méthode waitNotification de SubscriptionChannel

La méthode waitNotification s’appelle elle-même tant qu’il y a des abonnements (subscrip-


tion) actifs sur le serveur. Grâce au modèle non bloquant de javascript, il n’est pas nécessaire
d’effectuer cette opération dans une file d’exécution séparée. Lorsque la méthode WaitNo-
tification est appelée sur le serveur, l’application Angular continue de s’exécuter. Lorsque le
client reçoit la réponse du serveur, il reprend où il s’était arrêté. Dans ce cas précis, il effec-
tue les appels de méthode onChangeCallBack pour les notifications reçues. Rappelons que
onChangeCallBack a été passée en paramètre de la méthode onChange de la classe Woop-
saClient (listing 8.3), lors de l’abonnement à la variable.

Sébastien Schoepfer Page 58


Tests unitaires 9

9.1 Généralités

Des tests unitaires ont été écrits pour certains éléments. Des frameworks différents ont été
utilisés pour le client et pour le serveur. Pour le client nous avons utilisés les frameworks Karma[5]
et Jasmine[7]. Pour le serveur Mocha[11] et Chai[1]. Le but initial était d’utiliser la même tech-
nologie de test pour l’ensemble de l’application. Cependant, les différentes recherches effec-
tuées ont poussé à prendre des technologies précitées du fait de leur intégration avec Angular
pour Karma et Jasmine et Node.js pour Mocha et Chai.
Les outils Karma et Mocha sont des frameworks de tests. Ils ont pour mission de créer l’environ-
nement propice aux tests et de les exécuter. Chai et Jasmine sont des librairies d’assertions.

9.2 Test du client

Les tests de l’applications Angular n’ont pas pu être développé de manière aussi complète
que souhaitée au départ. Développer des tests pour la partie graphique d’une application est
relativement compliquée et chronophage. La prise en main du framework demande beau-
coup d’investissement. Des tests ont été développés pour le composant affichant la liste de
synoptique car il est utilisé à plusieurs endroits. C’est le seul élément graphique qui dispose de
tests unitaires. Il serait intéressant de tester d’autre éléments graphiques. Il faudrait cependant
déterminer une politique de test basé sur l’orientation donnée à l’application.
Des tests ont été implémentés pour les services d’accès aux données (SynopticDataService
et UserComponentDataService). Chaque transformation possède également son propre test
unitaire.

9.3 Test du serveur

Pour développer l’api REST, les tests ont été d’une grande utilité. Grâce à eux, il est possible
de se passer du client pour valider chaque route du serveur.
Le serveur est testé de deux manières différentes. La première consiste à tester les classes du
modèle. La seconde test l’API REST via un client HTTP. De cette manière, on peut tester l’api de
bout en bout.
L’usage de test unitaire pour développer le serveur a permis de gagner beaucoup de temps
de développement.

Sébastien Schoepfer Page 59


Revue des exigences 10

Nous allons passer en revue les exigences établies lors de la pré-étude. Il est important
de noter que seules les exigences dont le numéro est plus petit que 2000 sont considérées.
Les autres avaient été directement exclues en accord avec toutes les parties prenantes par
manque de temps disponible pour les réaliser.

10.1 Exigences globales

N° Libellé Validation Remarque


0010 L’application doit être de type web. OK -

0020 L’application doit être compatible avec la version du OK -


navigateur Chrome 58.

0030 L’application devrait être compatible avec la der- -


nière version des navigateurs Firefox, Safari et Edge.
0040 L’application doit être écrite en anglais. OK -

0050 L’application devrait permettre d’afficher une liste - Retirée du planning


des pages web disponibles.

0060 L’application doit permettre d’afficher une liste des OK -


synoptiques disponibles.

0070 L’application devrait permettre d’afficher une page - Retirée du planning


web.

0080 L’application doit permettre d’afficher un synop- OK -


tique.

0090 L’application devrait permettre de créer, modifier et - Retirée du planning


supprimer une page web.

0100 L’application doit permettre de créer, modifier et sup- OK -


primer un synoptique.

0110 L’application devrait permettre de créer, modifier et - Retirée du planning


supprimer un composant utilisateur.

0120 L’application devrait permettre la gestion des utilisa- - Retirée du planning


teurs.

Table 10.1 – Liste d’exigences globales

Nous constatons que toutes les exigences planifiées ont été réalisées. La numéro 0030 est notée
en orange car l’exigence très difficile à tester et valider.

Sébastien Schoepfer Page 60


10.2 Exigences de l’éditeur de synoptiques

N° Libellé Validation Remarque


1010 Il doit permettre de créer un nouveau synoptique. OK -
1020 Il doit permettre de modifier un synoptique existant. OK -
1030 Il doit permettre de nommer le synoptique. OK -

1040 Il doit permettre de définir la taille en pixel du synop- OK -


tique.
1050 Il doit afficher une liste de composant. OK -

1060 Il doit permettre d’ajouter des composants au synop- OK -


tique.

1070 Il doit permettre d’afficher une liste des propriétés dis- OK -


ponibles pour un composant.

1080 Il doit permettre de positionner les composants dans OK -


le synoptique.
1090 Il doit afficher une liste de variables disponibles. OK -

1100 Il doit permettre de lier des variables aux propriétés OK -


des composants.

1110 Il doit permettre d’affecter une valeur constante à OK -


une propriété.

1130 Il doit permettre l’ajout du composant par défaut OK -


trait.

1140 Il doit permettre l’ajout du composant par défaut OK -


Graphique en temps réel d’une variable
1150 Il doit permettre de supprimer les éléments ajoutés. OK -

1160 Il doit permettre la sauvegarde du synoptique en OK -


cours d’édition sur le serveur.

Table 10.2 – Liste d’exigences éditeur de synoptiques

Cette liste a également été entièrement validée.

10.3 Exigences ajoutées

N° Libellé Validation Remarque

1170 Il doit permettre la modification d’une variable depuis OK -


un composant.

Table 10.3 – Liste d’exigences ajoutées

Cette exigence a été ajoutée en cours de développement sur l’impulsion des conseillers et
avec la validation du mandant.

Sébastien Schoepfer Page 61


Dossier de gestion 11

11.1 Planning des réunions

Date Heure Conseiller Mandant


15.08.2017 15 :00 - 16 :30 X -
28.08.2017 16 :40 - 17 :40 X -
13.09.2017 12 :00 - 12 :30 - X
13.09.2017 13 :00 - 13 :30 X -

Table 11.1 – Planning des réunions

11.2 Planning des tâches

Sébastien Schoepfer Page 62


Sébastien Schoepfer
Figure 11.1 – Planing des tâches, partie 1

Page 63
Sébastien Schoepfer
Figure 11.2 – Planing des tâches, partie 2

Page 64
Conclusion 12

12.1 Concept

A ce stade, il est difficile d’évaluer la réussite du concept. Il faudra attendre que le système
soit présenté et mis en place pour évaluer le produit dans sa globalité.
Cependant, Objectis souhaitait poser les bases d’une plateforme Web permettant l’édition et
la visualisation de vues synoptiques pour superviser des installations techniques. Cet objectif est
rempli et démontre de la faisabilité du concept.

12.2 Choix technologiques

Le premier choix technologique a été celui du Web. Nous avions la possibilité de dévelop-
per l’éditeur de synoptique dans un client lourd. Cette piste avait rapidement été écartée au
profit des technologies web. Cela permettait de se démarquer des autres produits existants sur
le marché.
Nous sommes heureux de dire que cela reste bien une excellente décision. Premièrement, le
produit est très homogène à l’utilisation. Ce niveau, d’homogénéité aurait été plus difficile à
atteindre en développant un client lourd pour l’édition de synoptique. Deuxièmement, grand
nombre d’éléments développés pour le visualisateur de synoptique ont pu être réutiliser dans
l’outil de conception, nous avons ainsi pu être plus efficaces.

12.2.1 Angular comme client

Il est indéniable qu’Angular était un très bon choix pour réaliser le client. La panoplie d’outils
dont ils disposent à presque suffit à tout faire. Seules deux librairies de composants graphiques
ont dû être ajoutées pour développer la totalité du client. Ce côté ultra complet, lui donne une
réputation ”d’usine à gaz” au sein de la communauté. Ceci ne s’est absolument pas fait sentir
lors du développement.
Les équipes derrière cette technologie sont très actives. Diverses versions mineures ont été pu-
bliées durant le développement. C’est très agréable de savoir que l’outil que l’on utilise est
amélioré de jour en jour. Certaines mises à jour ayant un impact sur notre application peuvent
poser problème, mais autant les traiter tout de suite et ainsi rester à niveau. Cela garantit éga-
lement une meilleure sécurité.

12.2.2 Node.js, express et MongoDB pour le serveur

Couplé avec Angular, ces technologies forment ce que nous appelons communément la
pile MEAN (MongoDB ,Express, Angular et Node.js). Les choix technologiques côté serveur ont
également été positifs.
L’utilisation de Node.js et Express s’est révélée assez pratique grâce au nombre important d’ar-
ticles et de documents que nous trouvons sur le net à leurs sujets.

Sébastien Schoepfer Page 65


MongoDB est plus difficile à prendre en main. Il est moins facile de trouver des documents de
qualité sur ce système. Nous avons constaté le même problème avec le framework Mongoose
utilisé pour accéder à la base de données. Cependant, la nature même de cette technolo-
gie (stockage des données au format JSON) s’est révélée un atout très puissant pour gérer la
persistance des données et cela en fait un choix de qualité pour ce projet.

12.2.3 Typescript

Après plusieurs années passées comme développeur Delphi, puis .Net sur Winforms le tout
avec en tâche de fond de petits projets Java sur Swing puis JavaFX, il est assez effrayant de se
lancer dans un projet web, qui plus est, entièrement basé sur du JavaScript. L’utilisation du suren-
semble Typescript pour développer l’entier du projet s’est cependant révélée très concluante.
Ce langage allie le meilleur des deux mondes : la rigueur des langages objets tels que C# ou
Java et si nécessaire la souplesse de JavaScript et son typage dynamique.
La syntaxe élégante de Typescript rend son utilisation très agréable. Les fonctionnalités telles
que la déclaration de propriété directement au sein du constructeur allège le code (les para-
mètres du constructeur sont utilisés comme attributs de la classe, pas besoin de faire l’assigna-
tion à la main) .
Certes, il serait idéal que les navigateurs supportent nativement Typescript, mais le choix de
ce langage est une décision plus qu’appréciée et approuvée. Tout comme pour Angular de
nombreuses améliorations sont apportées fréquemment au langage et à son écosystème.

Il est fort probable que d’autres technologies Web auraient aussi pu être utilisées sur ce projet.
Cependant, la mise en œuvre de ces technologies s’est révélée un vrai succès et il a été très
plaisant de travailler avec.

12.3 Expérience personnelle

Cette expérience a accentué ma conviction de l’importance du travail d’analyse. En dé-


taillant la pré-étude jusqu’au diagramme de classe, il a été possible de définir une temporalité
assez précise qui s’est révélée relativement correcte. La seule déviation importante se réfère à
la rédaction de ce rapport, tâche qui a été sous-estimée.
Un autre enseignement que je tire de cette expérience est la nécessité de n’utiliser qu’un seul
outil de suivi des tâches. Initialement parti pour maintenir un ”kanban board” ainsi qu’une liste
Excel, le premier a rapidement été abandonné. Je ne pense pas que l’un soit meilleur que
l’autre, cependant le fait d’en utiliser qu’un seul simplifie grandement les opérations de saisie
et diminue également le risque d’erreur.
J’ai choisi ce sujet pour apprendre de nouvelles technologies et me sortir de ma zone confort
en me lançant un vrai défi. Bien que le travail de formation effectué depuis le début de l’année
ait été très conséquent, ce choix m’a apporté une très grande motivation et je suis très heureux
d’avoir pris cette direction. J’ai ainsi pu faire progresser mes compétences dans le monde du
web, mais aussi dans la gestion de projet.

Sébastien Schoepfer Page 66


Remerciements 13

Pour terminer, je tiens à remercier différentes personnes :


MM. Christopher Bouzas et Antoine Jeanrichard pour avoir proposé ce sujet.
MM. Didier Mettler et François Birling pour leurs conseils pertinents et leur accompagnement
tout au long de ce travail.
Les enseignants du soir pour leur présence à ces heures tardives.
Ma belle-soeur pour la relecture du mémoire et ses conseils orthographiques.
Mon fils pour sa compréhension d’avoir eu un père absent trois soirs par semaine dès sa nais-
sance.
Mon épouse pour son soutien et sa patience pendant ces quatre ans d’études.

Sébastien Schoepfer Page 67


Table des figures

3.1 Diagramme technologique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

4.1 Barre de navigation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10


4.2 Page d’accueil . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
4.3 Visualiseur de synoptique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
4.4 Editeur de synoptique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
4.5 Barre supérieure de l’éditeur de synoptique . . . . . . . . . . . . . . . . . . . . . . . . 12
4.6 Liste des composants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
4.7 Synoptique en cours d’édition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
4.8 Liste des propriétés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
4.9 Différence entre propriétés en lecture (à gauche) et propriétés en écriture (à droite) 14
4.10 Editeur de propriété de type texte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
4.11 Editeur de propriété de type couleur . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
4.12 Editeur de propriété de type numérique . . . . . . . . . . . . . . . . . . . . . . . . . . 15
4.13 Editeur de propriété de type vrai/faux . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
4.14 Editeur de lien vers des variables Woopsa . . . . . . . . . . . . . . . . . . . . . . . . . 15
4.15 Définition d’une valeur à donner à une variable Woopsa . . . . . . . . . . . . . . . 16

5.1 Code couleur des classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17


5.2 Architecture globalel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
5.3 Organisation des fichiers du projet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

6.1 Point d’entrée de l’application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21


6.2 Page d’accueil . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
6.3 Un élément de la liste de synoptique . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
6.4 Synoptiques et composants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
6.5 Synoptique et composant - les classes . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
6.6 Instanciation de synoptique - Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
6.7 Instanciation de synoptique - Séquence . . . . . . . . . . . . . . . . . . . . . . . . . . 26
6.8 Instanciation du composant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
6.9 Interface IComponentDefinition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
6.10 Compilation du composant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
6.11 Fonctionnement global des propriétés . . . . . . . . . . . . . . . . . . . . . . . . . . 32
6.12 Définition de propriété - l’interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
6.13 Propriétés - l’interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

Sébastien Schoepfer Page 68


6.14 Les différents types de propriétés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
6.15 Variables et propriétés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
6.16 Propriétés et transformations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
6.17 Exemple de transformations numérique vers couleur . . . . . . . . . . . . . . . . . . 37
6.18 Algorithme d’application d’une transformation . . . . . . . . . . . . . . . . . . . . . 37
6.19 Instanciation des propriétés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
6.20 Instanciation des variables et transformations . . . . . . . . . . . . . . . . . . . . . . . 39
6.21 Visualisateur de synoptique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
6.22 Chemin vers un synoptique à visualiser . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
6.23 Concepteur de synoptique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
6.24 Boîte à outil de composants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
6.25 Liste des composants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
6.26 Ajouter un composant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
6.27 Synoptic en mode édition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
6.28 Sélecteur de composant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
6.29 Propriétés en lecture et en écriture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
6.30 Boîte à outil de propriétés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
6.31 Editeur de constante . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
6.32 Explorateur de variable Woopsa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
6.33 Editeur de lien vers une variable Woopsa . . . . . . . . . . . . . . . . . . . . . . . . . 47
6.34 Liste des transformations disponibles . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
6.35 Transformations numériques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
6.36 Editeur de propriété en lecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
6.37 Editeur de propriété en écriture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

7.1 Serveur - Modèle et schéma . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50


7.2 Serveur - Routes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52

8.1 Client Woopsa, les classes principales . . . . . . . . . . . . . . . . . . . . . . . . . . . 55


8.2 Subscription service - http://www.woopsa.org/specifications/ . . . . . . . . . 56
8.3 Méthode onChange de WoopsaClient . . . . . . . . . . . . . . . . . . . . . . . . . . 57
8.4 Méthode waitNotification de SubscriptionChannel . . . . . . . . . . . . . . . . . . . 58

11.1 Planing des tâches, partie 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63


11.2 Planing des tâches, partie 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64

Sébastien Schoepfer Page 69


Liste des tableaux

10.1 Liste d’exigences globales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60


10.2 Liste d’exigences éditeur de synoptiques . . . . . . . . . . . . . . . . . . . . . . . . . 61
10.3 Liste d’exigences ajoutées . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61

11.1 Planning des réunions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62

Sébastien Schoepfer Page 70


Bibliographie

[1] ChaiJs. Chaijs. http://chaijs.com, 2017.


Accédé : 3ème trimestre 2017.
[2] fernandohu. Reading data before application startup in angular 2. https://gist.
github.com/fernandohu/122e88c3bcd210bbe41c608c36306db9, 2016.
Accédé : 3ème trimestre 2017.
[3] Google. Angular. https://angular.io, 2010-2017.
Accédé : 3ème trimestre 2017.
[4] Google. Angular material. https://material.angular.io, 2017.
Accédé : 3ème trimestre 2017.
[5] Karma. Karma. https://karma-runner.github.io, 2017.
Accédé : 3ème trimestre 2017.
[6] Radim Köhler. How can i use/create dynamic template to compile dynamic component
with angular 2.0 ? https://stackoverflow.com/questions/38888008/how-can-i-
use-create-dynamic-template-to-compile-dynamic-component-with-angular,
2016.
Accédé : 3ème trimestre 2017.
[7] Pivotal Labs. Jasmine. https://jasmine.github.io, 2017.
Accédé : 3ème trimestre 2017.
[8] LearnBoost. mongoose. http://mongoosejs.com, 2011.
Accédé : 3ème trimestre 2017.
[9] Kyle Ledbetter. Angular-material 2 theme tutorial. https://medium.com/covalent-ui/
angular-material-2-theme-tutorial-2f7e6c344006, 2016.
Accédé : 3ème trimestre 2017.
[10] Microsoft. Typescript. http://www.typescriptlang.org, 2012-2017.
Accédé : 3ème trimestre 2017.
[11] mochajs. Mochajs. https://mochajs.org, 2017.
Accédé : 3ème trimestre 2017.
[12] Pascal Precht. Custom themes with angular material. https://blog.thoughtram.io/
angular/2017/05/23/custom-themes-with-angular-material.html, 2017.
Accédé : 3ème trimestre 2017.
[13] PrimeTek. Prime ng. https://www.primefaces.org/primeng/#/, 2010-2017.
Accédé : 3ème trimestre 2017.
[14] Objectis SA. Woopsa - industry 4.0, internet of things protocol.
http://www.woopsa.org, 2012-2017.
Accédé : 3ème trimestre 2017.
[15] Objectis SA. Objectis.
https://www.objectis.com, 2017.
Accédé : 3ème trimestre 2017.
[16] IBM StrongLoop. Express. http://expressjs.com, 2017.
Accédé : 3ème trimestre 2017.

Sébastien Schoepfer Page 71


Authentification

Le soussigné, Sébastien Schoepfer, atteste par la présente avoir réalisé seul ce travail et
n’avoir utilisé aucune autre source que celles expressément mentionnées, si ce n’est les connais-
sances acquises durant ses études et son expérience acquise dans une activité professionnelle.

Epalinges, le 28.09.2017

S. Schoepfer

Sébastien Schoepfer Page 72

Vous aimerez peut-être aussi