Vous êtes sur la page 1sur 325

Un exemple de client / serveur

AngularJS / Spring 4

serge.tahe at univ-angers.fr, juillet 2014

http://tahe.developpez.com

1/325

Table des matires


1 INTRODUCTION......................................................................................................................................................................6
1.1 L'ARCHITECTURE DE L'APPLICATION.........................................................................................................................................6
1.2 LES OUTILS UTILISS.................................................................................................................................................................7
1.3 LES FONCTIONNALITS DE L'APPLICATION................................................................................................................................7
1.3.1 CRATION DE LA BASE DE DONNES..........................................................................................................................................8
1.3.2 MISE EN OEUVRE DU SERVEUR WEB / JSON..............................................................................................................................9
1.3.3 MISE EN OEUVRE DU CLIENT ANGULAR...................................................................................................................................11
2 LE SERVEUR SPRING 4.......................................................................................................................................................17
2.1 LA BASE DE DONNES...............................................................................................................................................................17
2.1.1 LA TABLE [MEDECINS].......................................................................................................................................................18
2.1.2 LA TABLE [CLIENTS]...........................................................................................................................................................19
2.1.3 LA TABLE [CRENEAUX]......................................................................................................................................................19
2.1.4 LA TABLE [RV]......................................................................................................................................................................20
2.2 INTRODUCTION SPRING DATA...............................................................................................................................................20
2.2.1 LA CONFIGURATION MAVEN DU PROJET...................................................................................................................................21
2.2.2 LA COUCHE [JPA]..................................................................................................................................................................23
2.2.3 LA COUCHE [DAO]................................................................................................................................................................24
2.2.4 LA COUCHE [CONSOLE]...........................................................................................................................................................26
2.2.5 CONFIGURATION MANUELLE DU PROJET SPRING DATA..............................................................................................................29
2.2.6 CRATION D'UNE ARCHIVE EXCUTABLE..................................................................................................................................34
2.2.7 CRER UN NOUVEAU PROJET SPRING DATA.............................................................................................................................36
2.3 LE PROJET ECLIPSE DU SERVEUR............................................................................................................................................39
2.4 LA CONFIGURATION MAVEN....................................................................................................................................................39
2.5 LES ENTITS JPA....................................................................................................................................................................43
2.6 LA COUCHE [DAO].................................................................................................................................................................48
2.7 LA COUCHE [MTIER]..............................................................................................................................................................50
2.7.1 LES ENTITS...........................................................................................................................................................................51
2.7.2 LE SERVICE............................................................................................................................................................................52
2.8 LA CONFIGURATION DU PROJET...............................................................................................................................................55
2.9 LES TESTS DE LA COUCHE [MTIER]........................................................................................................................................57
2.10 LE PROGRAMME CONSOLE.....................................................................................................................................................59
2.11 INTRODUCTION SPRING MVC............................................................................................................................................61
2.11.1 LE PROJET DE DMONSTRATION.............................................................................................................................................61
2.11.2 CONFIGURATION MAVEN.......................................................................................................................................................62
2.11.3 L'ARCHITECTURE D'UN SERVICE SPRING REST......................................................................................................................64
2.11.4 LE CONTRLEUR C...............................................................................................................................................................64
2.11.5 LE MODLE M.....................................................................................................................................................................65
2.11.6 CONFIGURATION DU PROJET..................................................................................................................................................66
2.11.7 EXCUTION DU PROJET.........................................................................................................................................................67
2.11.8 CRATION D'UNE ARCHIVE EXCUTABLE................................................................................................................................69
2.11.9 DPLOYER L'APPLICATION SUR UN SERVEUR TOMCAT.............................................................................................................71
2.11.10 CRER UN NOUVEAU PROJET WEB.......................................................................................................................................73
2.12 LA COUCHE [WEB].................................................................................................................................................................74
2.12.1 CONFIGURATION MAVEN.......................................................................................................................................................74
2.12.2 L'INTERFACE DU SERVICE WEB...............................................................................................................................................75
2.12.3 LE SQUELETTE DU CONTRLEUR [RDVMEDECINSCONTROLLER].............................................................................................82
2.12.4 LES MODLES DU SERVICE WEB.............................................................................................................................................84
2.12.5 LA CLASSE STATIC................................................................................................................................................................86
2.12.6 LA MTHODE [INIT] DU CONTRLEUR....................................................................................................................................88
2.12.7 L'URL [/GETALLMEDECINS]................................................................................................................................................88
2.12.8 L'URL [/GETALLCLIENTS]...................................................................................................................................................89
2.12.9 L'URL [/GETALLCRENEAUX/{IDMEDECIN}].........................................................................................................................90
2.12.10 L'URL [/GETRVMEDECINJOUR/{IDMEDECIN}/{JOUR}].......................................................................................................92
2.12.11 L'URL [/GETAGENDAMEDECINJOUR/{IDMEDECIN}/{JOUR}]..............................................................................................95
2.12.12 L'URL [/GETMEDECINBYID/{ID}].....................................................................................................................................97
2.12.13 L'URL [/GETCLIENTBYID/{ID}].........................................................................................................................................98
2.12.14 L'URL [/GETCRENEAUBYID/{ID}].....................................................................................................................................99
2.12.15 L'URL [/GETRVBYID/{ID}]...............................................................................................................................................99
2.12.16 L'URL [/AJOUTERRV]......................................................................................................................................................101
2.12.17 L'URL [/SUPPRIMERRV]...................................................................................................................................................103
2.12.18 CONFIGURATION DU SERVICE WEB.....................................................................................................................................105

http://tahe.developpez.com

2/325

2.12.19 LA CLASSE EXCUTABLE DU SERVICE WEB.........................................................................................................................106


2.13 INTRODUCTION SPRING SECURITY...................................................................................................................................109
2.13.1 CONFIGURATION MAVEN.....................................................................................................................................................109
2.13.2 LES VUES THYMELEAF.......................................................................................................................................................110
2.13.3 CONFIGURATION SPRING MVC...........................................................................................................................................113
2.13.4 CONFIGURATION SPRING SECURITY.....................................................................................................................................114
2.13.5 CLASSE EXCUTABLE..........................................................................................................................................................115
2.13.6 TESTS DE L'APPLICATION.....................................................................................................................................................116
2.13.7 CONCLUSION......................................................................................................................................................................118
2.14 MISE EN PLACE DE LA SCURIT SUR LE SERVICE WEB DES RENDEZ-VOUS..........................................................................118
2.14.1 LA BASE DE DONNES.........................................................................................................................................................118
2.14.2 LE NOUVEAU PROJET ECLIPSE DU [MTIER, DAO, JPA]......................................................................................................120
2.14.3 LES NOUVELLES ENTITS [JPA]..........................................................................................................................................120
2.14.4 MODIFICATIONS DE LA COUCHE [DAO]...............................................................................................................................122
2.14.5 LES CLASSES DE GESTION DES UTILISATEURS ET DES RLES..................................................................................................123
2.14.6 TESTS DE LA COUCHE [DAO].............................................................................................................................................126
2.14.7 CONCLUSION INTERMDIAIRE..............................................................................................................................................130
2.14.8 LE PROJET ECLIPSE DE LA COUCHE [WEB]...........................................................................................................................130
2.14.9 TESTS DU SERVICE WEB......................................................................................................................................................133
2.15 CONCLUSION........................................................................................................................................................................137
3 LE CLIENT ANGULAR JS..................................................................................................................................................139
3.1 RFRENCES DU FRAMEWORK ANGULAR JS.........................................................................................................................139
3.2 ARCHITECTURE DU CLIENT ANGULAR....................................................................................................................................139
3.3 LES VUES DU CLIENT ANGULAR.............................................................................................................................................142
3.4 CONFIGURATION DU PROJET ANGULAR.................................................................................................................................146
3.5 LA PAGE INITIALE DU CLIENT ANGULAR................................................................................................................................150
3.6 DCOUVERTE DE BOOTSTRAP................................................................................................................................................153
3.6.1 EXEMPLE 1..........................................................................................................................................................................153
3.6.2 EXEMPLE 2..........................................................................................................................................................................153
3.6.3 EXEMPLE 3..........................................................................................................................................................................154
3.6.4 EXEMPLE 4..........................................................................................................................................................................155
3.6.5 EXEMPLE 5..........................................................................................................................................................................156
3.6.6 EXEMPLE 6..........................................................................................................................................................................157
3.6.7 EXEMPLE 7..........................................................................................................................................................................158
3.6.8 EXEMPLE 8..........................................................................................................................................................................160
3.6.9 EXEMPLE 9..........................................................................................................................................................................162
3.6.10 CONCLUSION......................................................................................................................................................................163
3.7 DCOUVERTE D'ANGULAR JS................................................................................................................................................163
3.7.1 EXEMPLE 1 : LE MODLE MVC D'ANGULAR.........................................................................................................................164
3.7.2 EXEMPLE 2 : LOCALISATION DES DATES.................................................................................................................................167
3.7.3 EXEMPLE 3 : INTERNATIONALISATION DES TEXTES..................................................................................................................173
3.7.4 EXEMPLE 4 : UN SERVICE DE CONFIGURATION........................................................................................................................180
3.7.5 EXEMPLE 5 : PROGRAMMATION ASYNCHRONE........................................................................................................................184
3.7.6 EXEMPLE 6 : LES SERVICES HTTP........................................................................................................................................191
3.7.6.1 La vue V..........................................................................................................................................................................192
3.7.6.2 Le contrleur C et le modle M......................................................................................................................................193
3.7.6.3 Le contrleur C...............................................................................................................................................................194
3.7.6.4 Le service [dao]..............................................................................................................................................................198
3.7.6.5 Tests de l'application - 1..................................................................................................................................................202
3.7.6.6 Modification du serveur web / JSON..............................................................................................................................205
3.7.6.7 Tests de l'application - 2..................................................................................................................................................208
3.7.7 EXEMPLE 7 : LISTE DES CLIENTS............................................................................................................................................211
3.7.7.1 La vue V..........................................................................................................................................................................211
3.7.7.2 Le contrleur C et le modle M......................................................................................................................................213
3.7.7.3 Modification du service web - 1.....................................................................................................................................214
3.7.7.4 Tests de l'application 1.................................................................................................................................................215
3.7.7.5 Modification du service web 2.....................................................................................................................................217
3.7.7.6 Tests de l'application 2.................................................................................................................................................218
3.7.7.7 Utilisation d'une directive...............................................................................................................................................221
3.7.7.8 Tests de l'application 3.................................................................................................................................................223
3.7.8 EXEMPLE 8 : L'AGENDA D'UN MDECIN..................................................................................................................................225
3.7.8.1 La vue V de l'application................................................................................................................................................225
3.7.8.2 Le formulaire...................................................................................................................................................................226

http://tahe.developpez.com

3/325

3.7.8.3 Le contrleur C...............................................................................................................................................................227


3.7.8.4 Affichage de l'agenda......................................................................................................................................................230
3.7.8.5 Modification du serveur web..........................................................................................................................................232
3.7.8.6 Utilisation de directives..................................................................................................................................................232
3.7.9 EXEMPLE 9 : CRER ET ANNULER DES RSERVATIONS.............................................................................................................234
3.7.9.1 La vue V de l'application................................................................................................................................................234
3.7.9.2 Le contrleur C...............................................................................................................................................................235
3.7.9.3 Initialisation du contrleur C..........................................................................................................................................235
3.7.9.4 Obtention de l'agenda......................................................................................................................................................237
3.7.9.5 Rservation d'un crneau horaire....................................................................................................................................238
3.7.9.6 Modification serveur.......................................................................................................................................................239
3.7.9.7 Tests................................................................................................................................................................................240
3.7.9.8 Suppression d'un rendez-vous.........................................................................................................................................242
3.7.9.9 Modification serveur.......................................................................................................................................................244
3.7.10 EXEMPLE 10 : CRER ET ANNULER DES RSERVATIONS - 2....................................................................................................244
3.7.10.1 La vue V de l'application..............................................................................................................................................244
3.7.10.2 Le constructeur C..........................................................................................................................................................246
3.7.11 EXEMPLE 11 : UNE DIRECTIVE [SELECTENABLE2]................................................................................................................247
3.7.11.1 La vue V........................................................................................................................................................................247
3.7.11.2 Le code HTML de la vue..............................................................................................................................................247
3.7.11.3 La directive [selectEnable2]..........................................................................................................................................248
3.7.11.4 Le contrleur C.............................................................................................................................................................249
3.7.11.5 Les tests.........................................................................................................................................................................249
3.7.12 EXEMPLE 12 : UNE DIRECTIVE [LIST]...................................................................................................................................249
3.7.12.1 La directive [list]...........................................................................................................................................................250
3.7.12.2 Le code HTML.............................................................................................................................................................252
3.7.12.3 Le contrleur C.............................................................................................................................................................252
3.7.12.4 Les tests.........................................................................................................................................................................252
3.7.13 EXEMPLE 13 : MISE JOUR DU MODLE D'UNE DIRECTIVE...................................................................................................252
3.7.13.1 Les vues V.....................................................................................................................................................................253
3.7.13.2 La page HTML.............................................................................................................................................................253
3.7.13.3 La directive [list2].........................................................................................................................................................254
3.7.13.4 Le contrleur C.............................................................................................................................................................255
3.7.14 EXEMPLE 14 : LES DIRECTIVES [WAITING] ET [ERRORS]........................................................................................................258
3.7.14.1 Le nouveau code HTML...............................................................................................................................................259
3.7.14.2 La directive [waiting]....................................................................................................................................................259
3.7.14.3 La directive [errors]......................................................................................................................................................260
3.7.15 EXEMPLE 15 : NAVIGATION.................................................................................................................................................261
3.7.15.1 Les vues V de l'application...........................................................................................................................................261
3.7.15.2 Organisation du code....................................................................................................................................................261
3.7.15.3 Le conteneur des vues...................................................................................................................................................262
3.7.15.4 Le module de l'application............................................................................................................................................263
3.7.15.5 Le contrleur du conteneur de vues..............................................................................................................................263
3.7.15.6 La barre de navigation..................................................................................................................................................264
3.7.15.7 La vue [/page1] et son contrleur.................................................................................................................................265
3.7.15.8 Contrle de la navigation..............................................................................................................................................266
3.7.16 CONCLUSION......................................................................................................................................................................267
3.8 LE CLIENT FINAL ANGULAR...................................................................................................................................................267
3.8.1 STRUCTURE DU PROJET.........................................................................................................................................................267
3.8.2 LES DPENDANCES DU PROJET..............................................................................................................................................268
3.8.3 LA PAGE MATRE [APP.HTML]................................................................................................................................................269
3.8.4 LES VUES DE L'APPLICATION..................................................................................................................................................271
3.8.5 FONCTIONNALITS DE L'APPLICATION.....................................................................................................................................273
3.8.6 LE MODULE [MAIN.JS]..........................................................................................................................................................278
3.8.7 LE CONTRLEUR DE LA PAGE MATRE....................................................................................................................................279
3.8.8 GESTION DE LA TCHE ASYNCHRONE.....................................................................................................................................281
3.8.9 CONTRLE DE LA NAVIGATION...............................................................................................................................................282
3.8.10 LES SERVICES.....................................................................................................................................................................285
3.8.11 LES DIRECTIVES..................................................................................................................................................................287
3.8.12 LE CONTRLEUR [LOGINCTRL]............................................................................................................................................290
3.8.13 LE CONTRLEUR [HOMECTRL]............................................................................................................................................293
3.8.14 LE CONTRLEUR [AGENDACTRL]........................................................................................................................................295
3.8.15 LE CONTRLEUR [RESACTRL].............................................................................................................................................297

http://tahe.developpez.com

4/325

3.8.16 LA GESTION DES LANGUES..................................................................................................................................................299


4 EXPLOITATION DE L'APPLICATION............................................................................................................................300
4.1 DPLOIEMENT DU SERVICE WEB SUR UN SERVEUR TOMCAT ..................................................................................................300
4.2 DPLOIEMENT DU CLIENT ANGULAR SUR LE SERVEUR TOMCAT...........................................................................................305
4.3 LES ENTTES CORS..............................................................................................................................................................309
4.4 DPLOIEMENT DU CLIENT ANGULAR SUR UNE TABLETTE ANDROID.......................................................................................309
4.5 DPLOIEMENT DU CLIENT ANGULAR SUR L'MULATEUR D'UN SMARTPHONE ANDROID........................................................313
5 CONCLUSION...................................................................................................................................................................... 316
6 ANNEXES...............................................................................................................................................................................318
6.1 INSTALLATION DE STS (SPRING TOOL SUITE).......................................................................................................................318
6.2 INSTALLATION DE [WAMPSERVER]........................................................................................................................................319
6.3 INSTALLATION DE [WEBSTORM]............................................................................................................................................320
6.3.1 INSTALLATION DE [NODE.JS]..................................................................................................................................................321
6.3.2 INSTALLATION DE L'OUTIL [BOWER].......................................................................................................................................321
6.3.3 INSTALLATION DE [GIT]........................................................................................................................................................321
6.3.4 CONFIGURATION DE [WEBSTORM].........................................................................................................................................322
6.4 INSTALLATION D'UN MULATEUR POUR ANDROID..................................................................................................................323
6.5 INSTALLATION DU PLUGIN CHROME [ADVANCED REST CLIENT]...........................................................................................324

http://tahe.developpez.com

5/325

1 Introduction
Nous nous proposons ici d'introduire deux frameworks l'aide d'un exemple de client / serveur :

AngularJS utilis pour le client. Pour simplifier, il sera not Angular par la suite ;

Spring 4 utilis pour le serveur. Pour simplifier, il sera not Spring par la suite ;
La comprhension de ce document ncessite certains pr-requis :

un niveau intermdiaire en Java EE ;

la connaissance de JPA (Java Persistence Api) qui sera utilis pour accder une base de donnes ;

la connaissance d'au moins une version prcdente de Spring pour connatre la philosophie de ce framework ;

l'utilisation de Maven pour configurer des projets Java ;

une connaissance de base des changes HTTP dans une application web ;

les balises courantes du langage HTML ;

une connaissance basique du langage Javascript ;


Les autres connaissances ncessaires seront introduites et expliques au fur et mesure de l'tude de cas.
Ce document n'est pas un cours et il est incomplet bien des gards. Pour approfondir les deux frameworks, on pourra
utiliser les rfrences suivantes :

[ref1] : le livre " Pro AngularJS " crit par Adam Freeman aux ditions Apress. C'est un excellent livre. Les codes
source des exemples de ce livre sont disponibles gratuitement l'URL
[http://www.apress.com/downloadable/download/sample/sample_id/1527/] ;
[ref2] : la documentation officielle d'Angular JS [https://docs.angularjs.org/guide]
[ref3] : le livre " Spring Data " chez o'Reilly [http://shop.oreilly.com/product/0636920024767.do] qui prsente
l'utilisation du framework [Spring Data] pour l'accs aux donnes que ce soit des bases de donnes relationnelles ou pas
(NoSQL) ;
[ref4] : le livre " Pro Spring3 " aux ditions Apress. C'est la version prcdente de Spring 4 mais les principaux concepts
sont dj l ;
[ref5] : la documentation de rfrence de Spring 4 [http://docs.spring.io/spring/docs/current/spring-frameworkreference/pdf/spring-framework-reference.pdf].

Les sources qui ont nourri ce document sont celles cites ci-dessus plus l'indispensable [ http://stackoverflow.com/] pour les trs
nombreuses sances de dbogage.

1.1

L'architecture de l'application

L'application tudie aura l'architecture suivante :

http://tahe.developpez.com

6/325

1.2

en [1], un serveur web dlivre des pages statiques un navigateur. Ces pages contiennent une application AngularJS
construite sur le modle MVC (Modle Vue Contrleur). Le modle ici est la fois celui des vues et celui du domaine
reprsent ici par la couche [Services] ;
l'utilisateur va interagir avec les vues qui lui sont prsentes dans le navigateur. Ses actions vont parfois ncessiter
l'interrogation du serveur Spring 4 [2]. Celui-ci traitera la demande et rendra une rponse JSON (JavaScript Object
Notation) [3]. Celle-ci sera utilise pour mettre jour la vue prsente l'utilisateur.

Les outils utiliss

Dans ce document les outils de dveloppement utiliss sont les suivants :

Spring Tool Suite pour le serveur Spring : tlchargeable gratuitement ;


Webstorm pour le client Angular : une version d'valuation pour un mois est tlchargeable gratuitement ;
Wampserver pour la gestion de la base de donnes MySQL 5 : tlchargeable gratuitement ;

L'installation de ces outils et d'autres est dcrite au paragraphe 6, page 318.

1.3

Les fonctionnalits de l'application

Le code de l'exemple est disponible l'URL [http://tahe.developpez.com/java/angularjs-spring4] sous la forme d'un fichier zip
tlcharger.

http://tahe.developpez.com

7/325

1.3.1

le serveur est contenu dans les dossiers [rdvmedecins-metier-dao-v2] et [rdvmedecins-webapi-v3] ;


le client est contenu dans le dossier [rdvmedecins-angular-v2] ;
le script SQL de gnration de la base de donnes MySQL5 est dans le dossier [database] ;

Cration de la base de donnes

Pour tester l'application, nous crons d'abord la base de donnes avec le script SQL [dbrdvmedecins.sql]. Nous utilisons l'outil
[PhpMyAdmin] de WampServer :

1
2

en [1], on slectionne l'outil [phpMyAdmin] de WampServer ;


en [2], on choisit l'option [Importer] ;

3
5
4

en [3], on slectionne le fichier [database/dbrdvmedecins.sql] ;


en [4], on l'excute ;
en [5], la base de donnes cre.

http://tahe.developpez.com

8/325

1.3.2

Mise en oeuvre du serveur web / JSON

Avec Spring Tool Suite (STS), on importe les deux projets Maven du serveur Spring 4 :
3
2

en [1] et [2], nous importons des projets Maven ;


en [3], nous dsignons le dossier parent des deux projets importer ;

3
5

4
6

en [3], les projets imports. Il se peut que les projets prsentent des erreurs. Il faut que chacun d'eux utilise un compilateur
>=1.7 :

Il faut donc une JVM >=1.7 :

http://tahe.developpez.com

9/325

Lorsqu'il n'y a plus d'erreurs de JVM, on peut excuter le projet [rdvmedecins-webapi-v3] :

en [4], [5] et [6] nous excutons le projet [rdvmedecins-webapi-v3] comme une application Spring Boot ;

Nous obtenons alors les logs suivants dans la console de STS :


1. .
____
_
__ _ _
2. /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
3. ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
4. \\/ ___)| |_)| | | | | || (_| | ) ) ) )
5.
' |____| .__|_| |_|_| |_\__, | / / / /
6. =========|_|==============|___/=/_/_/_/
7. :: Spring Boot ::
(v1.0.0.RELEASE)
8.
9. 2014-06-05 12:22:34.049 INFO 9296 --- [
main] rdvmedecins.web.boot.Boot
: Starting Boot on Gportpers3 with PID 9296 (D:\data\istia-1314\polys\istia\angularjsspring4\rdvmedecins-webapi\target\classes started by ST)
10. 2014-06-05 12:22:34.122 INFO 9296 --- [
main]
ationConfigEmbeddedWebApplicationContext : Refreshing
org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@4b4bee
22: startup date [Thu Jun 05 12:22:34 CEST 2014]; root of context hierarchy
11. 2014-06-05 12:22:35.083 INFO 9296 --- [
main] o.s.b.f.s.DefaultListableBeanFactory
: Overriding bean definition for bean
'org.springframework.boot.autoconfigure.AutoConfigurationPackages': replacing [Generic bean:
class [org.springframework.boot.autoconfigure.AutoConfigurationPackages$BasePackages]; scope=;
abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true;
primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null;
destroyMethodName=null] with [Generic bean: class
[org.springframework.boot.autoconfigure.AutoConfigurationPackages$BasePackages]; scope=;
abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true;
primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null;
destroyMethodName=null]
12. ...
13. s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080/http

http://tahe.developpez.com

10/325

14. 2014-06-05 12:22:41.630 INFO 9296 --- [


Boot in 8.0 seconds (JVM running for 8.944)

1.3.3

main] rdvmedecins.web.boot.Boot : Started

lignes 13-14 : l'application a dmarr sur un serveur Tomcat.

Mise en oeuvre du client Angular

Nous ouvrons le dossier [rdvmedecins-angular-v2] avec WebStorm :

en [1], on choisit l'option [Open Directory] ;


en [2], on slectionne le dossier [rdvmedecins-angular-v2] ;

en [3], l'arborescence du dossier ;


en [4], on slectionne la page principale [app.html] de l'application ;
en [5], on l'ouvre dans un navigateur rcent ;

http://tahe.developpez.com

11/325

9
10

11

12

13

14

en [6], la page d'entre de l'application. Il s'agit d'une application de prise de rendez-vous pour des mdecins. Cette
application a dj t traite dans le document Introduction aux frameworks JSF2, Primefaces et Primefaces mobile ;
en [7], une case cocher qui permet d'tre ou non en mode [debug]. Ce dernier se caractrise par la prsence du cadre [8]
qui affiche le modle de la vue courante ;
en [9], une dure d'attente artificielle en millisecondes. Elle vaut 0 par dfaut (pas d'attente). Si N est la valeur de ce temps
d'attente, toute action de l'utilisateur sera excute aprs un temps d'attente de N millisecondes. Cela permet de voir la
gestion de l'attente mise en place par l'application ;
en [10], l'URL du serveur Spring 4. Si on suit ce qui a prcd, c'est [http://localhost:8080];
en [11] et [12], l'identifiant et le mot de passe de celui qui veut utiliser l'application. Il y a deux utilisateurs : admin/admin
(login/password) avec un rle (ADMIN) et user/user avec un rle (USER). Seul le rle ADMIN a le droit d'utiliser
l'application. Le rle USER n'est l que pour montrer ce que rpond le serveur dans ce cas d'utilisation ;
en [13], le bouton qui permet de se connecter au serveur ;
en [14], la langue de l'application. Il y en a deux : le franais par dfaut et l'anglais.

en [1], on se connecte ;

http://tahe.developpez.com

12/325

une fois connect, on peut choisir le mdecin avec lequel on veut un rendez-vous [2] et le jour de celui-ci [3] ;
on demande en [4] voir l'agenda du mdecin choisi pour le jour choisi ;

http://tahe.developpez.com

13/325

une fois obtenu l'agenda du mdecin, on peut rserver un crneau [5] ;

en [6], on choisit le patient pour le rendez-vous et on valide ce choix en [7] ;

http://tahe.developpez.com

14/325

Une fois le rendez-vous valid, on est ramen automatiquement l'agenda o le nouveau rendez-vous est dsormais inscrit. Ce
rendez-vous pourra tre ultrieurement supprim [7].
Les principales fonctionnalits ont t dcrites. Elles sont simples. Celles qui n'ont pas t dcrites sont des fonctions de navigation
pour revenir une vue prcdente. Terminons par la gestion de la langue :

http://tahe.developpez.com

15/325

en [1], on passe du franais l'anglais ;

en [2], la vue est passe en anglais, y-compris le calendrier ;

http://tahe.developpez.com

16/325

2 Le serveur Spring 4

Dans l'architecture ci-dessus, nous abordons maintenant la construction du service web / JSON construit avec le framework Spring
4. Nous allons l'crire en plusieurs tapes :

d'abord les couches [mtier] et [DAO] (Data Access Object). Nous utiliserons ici Spring Data ;

puis le service web JSON sans authentification. Nous utiliserons ici Spring MVC ;

puis on ajoutera la partie authentification avec Spring Security.


Nous commenons par expliciter la structure de la base de donnes sous-tendant l'application.

2.1

La base de donnes

Couche
[web /
JSON]

Couche
[mtier]

Spring 4

Couche
[DAO]

SGBD

La base de donnes appele par la suite [dbrdvmedecins] est une base de donnes MySQL5 avec les tables suivantes :

http://tahe.developpez.com

17/325

Les rendez-vous sont grs par les tables suivantes :

[medecins] : contient la liste des mdecins du cabinet ;

[clients] : contient la liste des patienst du cabinet ;

[creneaux] : contient les crneaux horaires de chacun des mdecins ;

[rv] : contient la liste des rendez-vous des mdecins.


Les tables [roles], [users] et [users_roles] sont des tables lies l'authentification. Dans un premier temps, nous n'allons pas nous en
occuper.
Le relations entre les tables grant les rendez-vous sont les suivantes :

2.1.1

un crneau horaire appartient un mdecin un mdecin a 0 ou plusieurs crneaux horaires ;


un rendez-vous runit la fois un client et un mdecin via un crneau horaire de ce dernier ;
un client a 0 ou plusieurs rendez-vous ;
un crneau horaire est associ 0 ou plusieurs rendez-vous ( des jours diffrents).

La table [MEDECINS]

Elle contient des informations sur les mdecins grs par l'application [RdvMedecins].

http://tahe.developpez.com

18/325

ID : n identifiant le mdecin - cl primaire de la table


VERSION : n identifiant la version de la ligne dans la table. Ce nombre est incrment de 1 chaque fois qu'une
modification est apporte la ligne.
NOM : le nom du mdecin
PRENOM : son prnom
TITRE : son titre (Melle, Mme, Mr)

2.1.2

La table [CLIENTS]

Les clients des diffrents mdecins sont enregistrs dans la table [CLIENTS] :

ID : n identifiant le client - cl primaire de la table


VERSION : n identifiant la version de la ligne dans la table. Ce nombre est incrment de 1 chaque fois qu'une
modification est apporte la ligne.
NOM : le nom du client
PRENOM : son prnom
TITRE : son titre (Melle, Mme, Mr)

2.1.3

La table [CRENEAUX]

Elle liste les crneaux horaires o les RV sont possibles :

ID : n identifiant le crneau horaire - cl primaire de la table (ligne 8)


VERSION : n identifiant la version de la ligne dans la table. Ce nombre est incrment de 1 chaque fois qu'une
modification est apporte la ligne.
ID_MEDECIN : n identifiant le mdecin auquel appartient ce crneau cl trangre sur la colonne MEDECINS(ID).
HDEBUT : heure dbut crneau
MDEBUT : minutes dbut crneau

http://tahe.developpez.com

19/325

HFIN : heure fin crneau


MFIN : minutes fin crneau

La seconde ligne de la table [CRENEAUX] (cf [1] ci-dessus) indique, par exemple, que le crneau n 2 commence 8 h 20 et se
termine 8 h 40 et appartient au mdecin n 1 (Mme Marie PELISSIER).

2.1.4

La table [RV]

Elle liste les RV pris pour chaque mdecin :

ID : n identifiant le RV de faon unique cl primaire


JOUR : jour du RV
ID_CRENEAU : crneau horaire du RV - cl trangre sur le champ [ID] de la table [CRENEAUX] fixe la fois le
crneau horaire et le mdecin concern.
ID_CLIENT : n du client pour qui est faite la rservation cl trangre sur le champ [ID] de la table [CLIENTS]

Cette table a une contrainte d'unicit sur les valeurs des colonnes jointes (JOUR, ID_CRENEAU) :
ALTER TABLE RV ADD CONSTRAINT UNQ1_RV UNIQUE (JOUR, ID_CRENEAU);

Si une ligne de la table[RV] a la valeur (JOUR1, ID_CRENEAU1) pour les colonnes (JOUR, ID_CRENEAU), cette valeur ne peut
se retrouver nulle part ailleurs. Sinon, cela signifierait que deux RV ont t pris au mme moment pour le mme mdecin. D'un
point de vue programmation Java, le pilote JDBC de la base lance une SQLException lorsque ce cas se produit.
La ligne d'id gal 3 (cf [1] ci-dessus) signifie qu'un RV a t pris pour le crneau n 20 et le client n 4 le 23/08/2006. La table
[CRENEAUX] nous apprend que le crneau n 20 correspond au crneau horaire 16 h 20 - 16 h 40 et appartient au mdecin n 1
(Mme Marie PELISSIER). La table [CLIENTS] nous apprend que le client n 4 est Melle Brigitte BISTROU.

2.2

Introduction Spring Data

Nous allons implmenter la couche [DAO] du projet avec Spring Data, une branche de l'cosystme Spring.

Couche
[web /
JSON]

Couche
[mtier]

Couche
[DAO]

Couche
[JPA]

Pilote
[JDBC]

SGBD

7 4
Spring
Sur le site de Spring existent de nombreux tutoriels pour dmarrer avec Spring [ http://spring.io/guides]. Nous allons utiliser l'un
d'eux pour introduire Spring Data. Nous utilisons pour cela Spring Tool Suite (STS).

http://tahe.developpez.com

20/325

en [1], nous importons l'un des tutoriels de [spring.io/guides] ;

3
4
5

2.2.1

en [2], on choisit le tutoriel [Accessing Data Jpa] qui montre comment accder une base de donnes avec Spring Data ;
en [3], on choisit un projet configur par Maven ;
en [4], le tutoriel peut tre dlivr sous deux formes : [initial] qui est une version vide qu'on remplit en suivant le tutoriel
ou [complete] qui est la version finale du tutoriel. Nous choisissons cette dernire ;
en [5], on peut choisir de visualiser le tutoriel dans un navigateur ;
en [6], le projet final.

La configuration Maven du projet

Les dpendances Maven du projet sont configures dans le fichier [pom.xml] :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.

<groupId>org.springframework</groupId>
<artifactId>gs-accessing-data-jpa</artifactId>
<version>0.1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.0.2.RELEASE</version>
</parent>

http://tahe.developpez.com

21/325

11.
<dependencies>
12.
<dependency>
13.
<groupId>org.springframework.boot</groupId>
14.
<artifactId>spring-boot-starter-data-jpa</artifactId>
15.
</dependency>
16.
<dependency>
17.
<groupId>com.h2database</groupId>
18.
<artifactId>h2</artifactId>
19.
</dependency>
20.
</dependencies>
21.
22.
<properties>
23.
<!-- use UTF-8 for everything -->
24.
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
25.
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
26.
<start-class>hello.Application</start-class>
27. </properties>

lignes 5-9 : dfinissent un projet Maven parent. C'est lui qui dfinit l'essentiel des dpendances du projet. Elles peuvent
tre suffisantes, auquel cas on n'en rajoute pas, ou pas, auquel cas on rajoute les dpendances manquantes ;
lignes 12-15 : dfinissent une dpendance sur [spring-boot-starter-data-jpa]. Cet artifact contient les classes de Spring
Data ;
lignes 16-19 : dfinissent une dpendance sur le SGBD H2 qui permet de crer et grer des bases de donnes en mmoire.

Regardons les classes amenes par ces dpendances :

Elles sont trs nombreuses :

certaines appartiennent l'cosystme Spring (celles commenant par spring) ;

d'autres appartiennent l'cosystme Hibernate (hibernate, jboss) dont on utilise ici l'implmentation JPA ;

d'autres sont des bibilothques de tests (junit, hamcrest) ;

d'autres des bibliothques de logs (log4j, logback, slf4j) ;


Nous allons les garder toutes. Pour une application en production, il faudrait ne garder que celles qui sont ncessaires.
Ligne 26 du fichier [pom.xml] on trouve la ligne :
<start-class>hello.Application</start-class>

Cette ligne est lie aux lignes suivantes :


1. <build>
2.
<plugins>
3.
<plugin>
4.
<artifactId>maven-compiler-plugin</artifactId>
5.
</plugin>

http://tahe.developpez.com

22/325

6.
7.
8.
9.
10.
11.

<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

Lignes 6-9, le plugin [spring-boot-maven-plugin] permet de gnrer le jar excutable de l'application. La ligne 26 du fichier
[pom.xml] dsigne alors la classe excutable de ce jar.

2.2.2

La couche [JPA]

L'accs la base de donnes se fait au travers d'une couche [JPA], Java Persistence API :

Couche
[console]

Couche
[DAO]

Couche
[JPA]

Pilote
[JDBC]

SGBD

7
Spring
4

L'application est basique et gre des clients [Customer]. La classe [Customer] fait partie de la couche [JPA] et est la suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.

package hello;
import
import
import
import

javax.persistence.Entity;
javax.persistence.GeneratedValue;
javax.persistence.GenerationType;
javax.persistence.Id;

@Entity
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String firstName;
private String lastName;
protected Customer() {
}
public Customer(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
@Override
public String toString() {
return String.format("Customer[id=%d, firstName='%s', lastName='%s']", id, firstName,
lastName);

http://tahe.developpez.com

23/325

28.
29.
30. }

Un client a un identifiant [id], un prnom [firstName] et un nom [lastName]. Chaque instance [Customer] reprsente une ligne
d'une table de la base de donnes.

ligne 8 : annotation JPA qui fait que la persistence des instances [Customer] (Create, Read, Update, Delete) va tre gre
par une implmentation JPA. D'aprs les dpendances Maven, on voit que c'est l'implmentation JPA / Hibernate qui est
utilise ;

lignes 11-12 : annotations JPA qui associent le champ [id] la cl primaire de la table des [Customer]. La ligne 12, indique
que l'implmentation JPA utilisera la mthode de gnration de cl primaire propre au SGBD utilis, ici H2 ;
Il n'y a pas d'autres annotations JPA. Des valeurs par dfaut seront alors utilises :

la table des [Customer] portera le nom de la classe, --d [Customer] ;

les colonnes de cette table porteront le nom des champs de la classe : [id, firstName, lastName] sachant que la casse n'est
pas prise en compte dans le nom d'une colonne de table ;
On notera qu' aucun moment, l'implmentation JPA utilise n'est nomme.

2.2.3

La couche [DAO]
Couche
[console]

Couche
[DAO]

Couche
[JPA]

Pilote
[JDBC]

SGBD

7
Spring
4

La classe [CustomerRepository] implmente la couche [DAO]. Son code est le suivant :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.

package hello;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
public interface CustomerRepository extends CrudRepository<Customer, Long> {
}

List<Customer> findByLastName(String lastName);

C'est donc une interface et non une classe (ligne 7). Elle tend l'interface [CrudRepository], une interface de Spring Data (ligne 5).
Cette interface est paramtre par deux types : le premier est le type des lments grs, ici le type [Customer], le second le type de
la cl primaire des lments grs, ici un type [Long]. L'interface [CrudRepository] est la suivante :
1.
2.
3.
4.
5.
6.
7.

package org.springframework.data.repository;
import java.io.Serializable;
@NoRepositoryBean
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {

http://tahe.developpez.com

24/325

8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29. }

<S extends T> S save(S entity);


<S extends T> Iterable<S> save(Iterable<S> entities);
T findOne(ID id);
boolean exists(ID id);
Iterable<T> findAll();
Iterable<T> findAll(Iterable<ID> ids);
long count();
void delete(ID id);
void delete(T entity);
void delete(Iterable<? extends T> entities);
void deleteAll();

Cette interface dfinit les opration CRUD (Create Read Update Delete) qu'on peut faire sur un type JPA T :
ligne 8 : la mthode save permet de persister une entit T en base. Elle rend l'entit persiste avec la cl primaire que lui a
donne le SGBD. Elle permet galement de mettre jour une entit T identifie par sa cl primaire id. Le choix de l'une
ou l'autre action se fait selon la valeur de la cl primaire id : si celle-ci vaut null c'est l'opration de persistence qui a lieu,
sinon c'est l'opration de mise jour ;
ligne 10 : idem mais pour une liste d'entits ;
ligne 12 : la mthode findOne permet de retrouver une entit T identifie par sa cl primaire id ;
ligne 22 : la mthode delete permet de supprimer une entit T identifie par sa cl primaire id ;
lignes 24-28 : des variantes de la mthode [delete] ;
ligne 16 : la mthode [findAll] permet de retrouver toutes les entits persistes T ;
ligne 18 : idem mais limite aux entits dont on a pass la liste des identifiants ;

Revenons l'interface [CustomerRepository] :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.

package hello;

la ligne 9 permet de retrouver un [Customer] par son nom [lastName] ;

import java.util.List;
import org.springframework.data.repository.CrudRepository;
public interface CustomerRepository extends CrudRepository<Customer, Long> {
}

List<Customer> findByLastName(String lastName);

Et c'est tout pour la couche [DAO]. Il n'y a pas de classe d'implmentation de l'interface prcdente. Celle-ci est gnre
l'excution par [Spring Data]. Les mthodes de l'interface [CrudRepository] sont automatiquement implmentes. Pour les
mthodes rajoutes dans l'interface [CustomerRepository], a dpend. Revenons la dfinition de [Customer] :
1.
private long id;
2.
private String firstName;
3. private String lastName;

La mthode de la ligne 9 est implmente automatiquement par [Spring Data] parce qu'elle rfrence le champ [lastName] (ligne 3)
de [Customer]. Lorsqu'il rencontre une mthode [findBySomething] dans l'interface implmenter, Spring Data l'implmente par la
requte JPQL (Java Persistence Query Language) suivante :
select t from T t where t.something=:value

http://tahe.developpez.com

25/325

Il faut donc que le type T ait un champ nomm [something]. Ainsi la mthode
List<Customer> findByLastName(String lastName);

va tre implmente par un code ressemblant au suivant :


return [em].createQuery("select c from Customer c where
c.lastName=:value").setParameter("value",lastName).getResultList()

o [em] dsigne le contexte de persistance JPA. Cela n'est possible que si la classe [Customer] a un champ nomm [lastName], ce
qui est le cas.
En conclusion, dans les cas simples, Spring Data nous permet d'implmenter la couche [DAO] avec une simple interface.

2.2.4

La couche [console]
Couche
[console]

Couche
[DAO]

Couche
[JPA]

Pilote
[JDBC]

SGBD

7
Spring
4

La classe [Application] est la suivante :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.

package hello;
import java.util.List;
import
import
import
import

org.springframework.boot.SpringApplication;
org.springframework.boot.autoconfigure.EnableAutoConfiguration;
org.springframework.context.ConfigurableApplicationContext;
org.springframework.context.annotation.Configuration;

@Configuration
@EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class);
CustomerRepository repository = context.getBean(CustomerRepository.class);
// save a couple of
repository.save(new
repository.save(new
repository.save(new
repository.save(new
repository.save(new

customers
Customer("Jack", "Bauer"));
Customer("Chloe", "O'Brian"));
Customer("Kim", "Bauer"));
Customer("David", "Palmer"));
Customer("Michelle", "Dessler"));

// fetch all customers


Iterable<Customer> customers = repository.findAll();

http://tahe.developpez.com

26/325

28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53. }

System.out.println("Customers found with findAll():");


System.out.println("-------------------------------");
for (Customer customer : customers) {
System.out.println(customer);
}
System.out.println();
// fetch an individual customer by ID
Customer customer = repository.findOne(1L);
System.out.println("Customer found with findOne(1L):");
System.out.println("--------------------------------");
System.out.println(customer);
System.out.println();
// fetch customers by last name
List<Customer> bauers = repository.findByLastName("Bauer");
System.out.println("Customer found with findByLastName('Bauer'):");
System.out.println("--------------------------------------------");
for (Customer bauer : bauers) {
System.out.println(bauer);
}
}

context.close();

la ligne 10 : indique que la classe sert configurer Spring. Les versions rcentes de Spring peuvent en effet tre configures
en Java plutt qu'en XML. Les deux mthodes peuvent tre utilises simultanment. Dans le code d'une classe ayant
l'annotation [Configuration] on trouve normalement des beans Spring, --d des dfinitions de classe instancier. Ici
aucun bean n'est dfini. Il faut rappeler ici que lorsqu'on travaille avec un SGBD, divers beans Spring doivent tre dfinis :
un [EntityManagerFactory] qui dfinit l'implmentation JPA utiliser,
un [DataSource] qui dfinit la source de donnes utiliser,
un [TransactionManager] qui dfinit le gestionnaire de transactions utiliser ;

Ici aucun de ces beans n'est dfini.

la ligne 11 : l'annotation [EnableAutoConfiguration] est une annotation provenant du projet [Spring Boot] (lignes 5-6).
Cette annotation demande Spring Boot via la classe [SpringApplication] (ligne 16) de configurer l'application en fonction
des bibliothques trouves dans son Classpath. Parce que les bibliothques Hibernate sont dans le Classpath, le bean
[entityManagerFactory] sera implment avec Hibernate. Parce que la bibliothque du SGBD H2 est dans le Classpath, le
bean [dataSource] sera implment avec H2. Dans le bean [dataSource], on doit dfinir galement l'utilisateur et son mot
de passe. Ici Spring Boot utilisera l'administrateur par dfaut de H2, sa sans mot de passe. Parce que la bibliothque
[spring-tx] est dans le Classpath, c'est le gestionnaire de transactions de Spring qui sera utilis.
Par ailleurs, le dossier dans lequel se trouve la classe [Application] va tre scann la recherche de beans implicitement
reconnus par Spring ou dfinis explicitement par des annotations Spring. Ainsi les classes [Customer] et
[CustomerRepository] vont-elles tre inspectes. Parce que la premire a l'annotation [@Entity] elle sera catalogue
comme entit grer par Hibernate. Parce que la seconde tend l'interface [CrudRepository] elle sera enregistre comme
bean Spring.

Examinons les lignes 16-17 du code :


1. ConfigurableApplicationContext context = SpringApplication.run(Application.class);
2. CustomerRepository repository = context.getBean(CustomerRepository.class);

ligne 1 : la mthode statique [run] de la classe [SpringApplication] du projet Spring Boot est excute. Son paramtre est la
classe qui a une annotation [Configuration] ou [EnableAutoConfiguration]. Tout ce qui a t expliqu prcdemment va
alors se drouler. Le rsultat est un contexte d'application Spring, --d un ensemble de beans grs par Spring ;
ligne 17 : on demande ce contexte Spring, un bean implmentant l'interface [CustomerRepository]. Nous rcuprons ici,
la classe gnre par Spring Data pour implmenter cette interface.

Les oprations qui suivent ne font qu'utiliser les mthodes du bean implmentant l'interface [CustomerRepository]. On notera ligne
50, que le contexte est ferm. Les rsultats console sont les suivants :

http://tahe.developpez.com

27/325

1. .
____
_
__ _ _
2. /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
3. ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
4. \\/ ___)| |_)| | | | | || (_| | ) ) ) )
5.
' |____| .__|_| |_|_| |_\__, | / / / /
6. =========|_|==============|___/=/_/_/_/
7. :: Spring Boot ::
(v1.0.2.RELEASE)
8.
9. 2014-06-05 16:23:13.877 INFO 11664 --- [
main] hello.Application
: Starting Application on Gportpers3 with PID 11664 (D:\Temp\wksSTS\gs-accessing-data-jpacomplete\target\classes started by ST in D:\Temp\wksSTS\gs-accessing-data-jpa-complete)
10. 2014-06-05 16:23:13.936 INFO 11664 --- [
main]
s.c.a.AnnotationConfigApplicationContext : Refreshing
org.springframework.context.annotation.AnnotationConfigApplicationContext@331a8fa0: startup date
[Thu Jun 05 16:23:13 CEST 2014]; root of context hierarchy
11. 2014-06-05 16:23:15.424 INFO 11664 --- [
main]
j.LocalContainerEntityManagerFactoryBean : Building JPA container EntityManagerFactory for
persistence unit 'default'
12. 2014-06-05 16:23:15.518 INFO 11664 --- [
main]
o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [
13.
name: default
14.
...]
15. 2014-06-05 16:23:15.690 INFO 11664 --- [
main] org.hibernate.Version
: HHH000412: Hibernate Core {4.3.1.Final}
16. 2014-06-05 16:23:15.692 INFO 11664 --- [
main] org.hibernate.cfg.Environment
: HHH000206: hibernate.properties not found
17. 2014-06-05 16:23:15.694 INFO 11664 --- [
main] org.hibernate.cfg.Environment
: HHH000021: Bytecode provider name : javassist
18. 2014-06-05 16:23:15.988 INFO 11664 --- [
main] o.hibernate.annotations.common.Version
: HCANN000001: Hibernate Commons Annotations {4.0.4.Final}
19. 2014-06-05 16:23:16.078 INFO 11664 --- [
main] org.hibernate.dialect.Dialect
: HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
20. 2014-06-05 16:23:16.300 INFO 11664 --- [
main] o.h.h.i.ast.ASTQueryTranslatorFactory
: HHH000397: Using ASTQueryTranslatorFactory
21. 2014-06-05 16:23:16.613 INFO 11664 --- [
main]
org.hibernate.tool.hbm2ddl.SchemaExport : HHH000227: Running hbm2ddl schema export
22. Hibernate: drop table customer if exists
23. Hibernate: create table customer (id bigint generated by default as identity, first_name
varchar(255), last_name varchar(255), primary key (id))
24. 2014-06-05 16:23:16.619 INFO 11664 --- [
main]
org.hibernate.tool.hbm2ddl.SchemaExport : HHH000230: Schema export complete
25. 2014-06-05 16:23:17.074 INFO 11664 --- [
main] o.s.j.e.a.AnnotationMBeanExporter
: Registering beans for JMX exposure on startup
26. 2014-06-05 16:23:17.094 INFO 11664 --- [
main] hello.Application
: Started Application in 3.906 seconds (JVM running for 5.013)
27. Hibernate: insert into customer (id, first_name, last_name) values (null, ?, ?)
28. Hibernate: insert into customer (id, first_name, last_name) values (null, ?, ?)
29. Hibernate: insert into customer (id, first_name, last_name) values (null, ?, ?)
30. Hibernate: insert into customer (id, first_name, last_name) values (null, ?, ?)
31. Hibernate: insert into customer (id, first_name, last_name) values (null, ?, ?)
32. Hibernate: select customer0_.id as id1_0_, customer0_.first_name as first_na2_0_,
customer0_.last_name as last_nam3_0_ from customer customer0_
33. Customers found with findAll():
34. ------------------------------35. Customer[id=1, firstName='Jack', lastName='Bauer']
36. Customer[id=2, firstName='Chloe', lastName='O'Brian']
37. Customer[id=3, firstName='Kim', lastName='Bauer']
38. Customer[id=4, firstName='David', lastName='Palmer']
39. Customer[id=5, firstName='Michelle', lastName='Dessler']
40.
41. Hibernate: select customer0_.id as id1_0_0_, customer0_.first_name as first_na2_0_0_,
customer0_.last_name as last_nam3_0_0_ from customer customer0_ where customer0_.id=?
42. Customer found with findOne(1L):
43. -------------------------------44. Customer[id=1, firstName='Jack', lastName='Bauer']
45.

http://tahe.developpez.com

28/325

46. Hibernate: select customer0_.id as id1_0_, customer0_.first_name as first_na2_0_,


customer0_.last_name as last_nam3_0_ from customer customer0_ where customer0_.last_name=?
47. Customer found with findByLastName('Bauer'):
48. -------------------------------------------49. Customer[id=1, firstName='Jack', lastName='Bauer']
50. Customer[id=3, firstName='Kim', lastName='Bauer']
51. 2014-06-05 16:23:17.330 INFO 11664 --- [
main]
s.c.a.AnnotationConfigApplicationContext : Closing
org.springframework.context.annotation.AnnotationConfigApplicationContext@331a8fa0: startup date
[Thu Jun 05 16:23:13 CEST 2014]; root of context hierarchy
52. 2014-06-05 16:23:17.332 INFO 11664 --- [
main] o.s.j.e.a.AnnotationMBeanExporter
: Unregistering JMX-exposed beans on shutdown
53. 2014-06-05 16:23:17.333 INFO 11664 --- [
main]
j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit
'default'
54. 2014-06-05 16:23:17.334 INFO 11664 --- [
main]
org.hibernate.tool.hbm2ddl.SchemaExport : HHH000227: Running hbm2ddl schema export
55. Hibernate: drop table customer if exists
56. 2014-06-05 16:23:17.336 INFO 11664 --- [
main]
org.hibernate.tool.hbm2ddl.SchemaExport : HHH000230: Schema export complete

2.2.5

lignes 1-8 : le logo du projet Spring Boot ;


ligne 9 : la classe [hello.Application] est excute ;
ligne 10 : [AnnotationConfigApplicationContext] est une classe implmentant l'interface [ApplicationContext] de Spring.
C'est un conteneur de beans ;
ligne 11 : le bean [entityManagerFactory] est implmente avec la classe [LocalContainerEntityManagerFactory], une classe
de Spring ;
ligne 12 : on voit apparatre [hibernate]. C'est cette implmentation JPA qui a t choisie ;
ligne 19 : un dialecte Hibernate est la variante SQL utiliser avec le SGBD. Ici le dialecte [H2Dialect] montre qu'Hibernate
va travailler avec le SGBD H2 ;
lignes 22-24 : la table [CUSTOMER] est cre. Cela signifie qu'Hibernate a t configur pour gnrer les tables partir
des dfinitions JPA, ici la dfinition JPA de la classe [Customer] ;
lignes 27-32 : des logs d'Hibernate montrant les insertions de lignes dans la table [CUSTOMER]. Cela signifie
qu'Hibernate a t configur pour gnrer des logs ;
lignes 35-39 : les cinq clients insrs ;
lignes 42-44 : rsultat de la mthode [findOne] de l'interface ;
lignes 47-50 : rsultats de la mthode [findByLastName] ;
lignes 51 et suivantes : logs de la fermeture du contexte Spring.

Configuration manuelle du projet Spring Data

Nous dupliquons le projet prcdent dans le projet [gs-accessing-data-jpa-2] :

Dans ce nouveau projet, nous n'allons pas nous reposer sur la configuration automatique faite par Spring Boot. Nous allons la faire
manuellement. Cela peut tre utile si les configurations par dfaut ne nous conviennent pas.

http://tahe.developpez.com

29/325

Tout d'abord, nous allons expliciter les dpendances ncessaires dans le fichier [pom.xml] :
1. <dependencies>
2.
<!-- Spring Core -->
3.
<dependency>
4.
<groupId>org.springframework</groupId>
5.
<artifactId>spring-core</artifactId>
6.
<version>4.0.5.RELEASE</version>
7.
</dependency>
8.
<dependency>
9.
<groupId>org.springframework</groupId>
10.
<artifactId>spring-context</artifactId>
11.
<version>4.0.5.RELEASE</version>
12.
</dependency>
13.
<dependency>
14.
<groupId>org.springframework</groupId>
15.
<artifactId>spring-beans</artifactId>
16.
<version>4.0.5.RELEASE</version>
17.
</dependency>
18.
<!-- Spring transactions -->
19.
<dependency>
20.
<groupId>org.springframework</groupId>
21.
<artifactId>spring-aop</artifactId>
22.
<version>4.0.5.RELEASE</version>
23.
</dependency>
24.
<dependency>
25.
<groupId>org.springframework</groupId>
26.
<artifactId>spring-tx</artifactId>
27.
<version>4.0.5.RELEASE</version>
28.
</dependency>
29.
<!-- Spring Data -->
30.
<dependency>
31.
<groupId>org.springframework.data</groupId>
32.
<artifactId>spring-data-jpa</artifactId>
33.
<version>1.5.2.RELEASE</version>
34.
</dependency>
35.
<!-- Spring Boot -->
36.
<dependency>
37.
<groupId>org.springframework.boot</groupId>
38.
<artifactId>spring-boot</artifactId>
39.
<version>1.0.2.RELEASE</version>
40.
</dependency>
41.
<!-- Hibernate -->
42.
<dependency>
43.
<groupId>org.hibernate</groupId>
44.
<artifactId>hibernate-entitymanager</artifactId>
45.
<version>4.3.4.Final</version>
46.
</dependency>
47.
<!-- H2 Database -->
48.
<dependency>
49.
<groupId>com.h2database</groupId>
50.
<artifactId>h2</artifactId>
51.
<version>1.4.178</version>
52.
</dependency>
53.
<!-- Commons DBCP -->
54.
<dependency>
55.
<groupId>commons-dbcp</groupId>
56.
<artifactId>commons-dbcp</artifactId>
57.
<version>1.4</version>
58.
</dependency>
59.
<dependency>
60.
<groupId>commons-pool</groupId>
61.
<artifactId>commons-pool</artifactId>
62.
<version>1.6</version>
63.
</dependency>
64.
</dependencies>

http://tahe.developpez.com

30/325

lignes 3-17 : les bibliothques de base de Spring ;


lignes 19-28 : les bibliothques de Spring pour grer les transactions avec une base de donnes ;
lignes 30-34 : Spring Data utilis pour accder la base de donnes ;
lignes 36-40 : Spring Boot pour lancer l'application ;
lignes 48-52 : le SGBD H2 ;
lignes 54-63 : les bases de donnes sont souvent utilises avec des pools de connexions ouvertes qui vitent les
ouvertures / fermetures de connexion rptition. Ici, l'implmentation utilise est celle de [commons-dbcp] ;

Toujours dans [pom.xml], on modifie le nom de la classe excutable :


1.
<properties>
2. ...
3.
<start-class>demo.console.Main</start-class>
4. </properties>

Dans le nouveau projet, l'entit [Customer] et l'interface [CustomerRepository] ne changent pas. On va changer la classe
[Application] qui va tre scinde en deux classes :

[Config] qui sera la classe de configuration :

[Main] qui sera la classe excutable ;

La classe excutable [Main] est la mme que prcdemment sans les annotations de configuration :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.

package demo.console;

ligne 12 : la classe [Main] n'a plus d'annotations de configuration ;


ligne 16 : l'application est lance avec Spring Boot. Le paramtre [Config.class] est la nouvelle classe de configuration du
projet ;

import java.util.List;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import demo.config.Config;
import demo.entities.Customer;
import demo.repositories.CustomerRepository;
public class Main {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Config.class);
CustomerRepository repository = context.getBean(CustomerRepository.class);
...
}

context.close();

La classe [Config] qui configure le projet est la suivante :

http://tahe.developpez.com

31/325

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.

package demo.config;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import
import
import
import
import
import
import
import
import
import
import

org.apache.commons.dbcp.BasicDataSource;
org.springframework.context.annotation.Bean;
org.springframework.context.annotation.Configuration;
org.springframework.data.jpa.repository.config.EnableJpaRepositories;
org.springframework.orm.jpa.JpaTransactionManager;
org.springframework.orm.jpa.JpaVendorAdapter;
org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
org.springframework.orm.jpa.vendor.Database;
org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
org.springframework.transaction.PlatformTransactionManager;
org.springframework.transaction.annotation.EnableTransactionManagement;

//@ComponentScan(basePackages = { "demo" })
//@EntityScan(basePackages = { "demo.entities" })
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = { "demo.repositories" })
@Configuration
public class Config {
// la source de donnes H2
@Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUrl("jdbc:h2:./demo");
dataSource.setUsername("sa");
dataSource.setPassword("");
return dataSource;
}
// le provider JPA
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
hibernateJpaVendorAdapter.setShowSql(false);
hibernateJpaVendorAdapter.setGenerateDdl(true);
hibernateJpaVendorAdapter.setDatabase(Database.H2);
return hibernateJpaVendorAdapter;
}
// EntityManagerFactory
@Bean
public EntityManagerFactory entityManagerFactory(JpaVendorAdapter jpaVendorAdapter,
DataSource dataSource) {
LocalContainerEntityManagerFactoryBean factory = new
LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(jpaVendorAdapter);
factory.setPackagesToScan("demo.entities");
factory.setDataSource(dataSource);
factory.afterPropertiesSet();
return factory.getObject();
}
// Transaction manager
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory
entityManagerFactory) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}

59.
60.
61.
62.
63.
64. }

http://tahe.developpez.com

32/325

ligne 22 : l'annotation [@Configuration] fait de la classe [Config] une classe de configuration Spring ;
ligne 21 : l'annotation [@EnableJpaRepositories] permet de dsigner les dossiers o se trouvent les interfaces Spring Data
[CrudRepository]. Ces interfaces vont devenir des composants Spring et tre disponibles dans son contexte ;
ligne 20 : l'annotation [@EnableTransactionManagement] indique que les mthodes des interfaces [CrudRepository]
doivent se drouler l'intrieur d'une transaction ;
ligne 19 : l'annotation [@EntityScan] permet de nommer les dossiers o doivent tre cherches les entits JPA. Ici elle a
t mise en commentaires, parce que cette information a t donne explicitement ligne 50. Cette annotation devrait tre
prsente si on utilise le mode [@EnableAutoConfiguration] et que les entits JPA ne sont pas dans le mme dossier que la
classe de configuration ;
ligne 18 : l'annotation [@ComponentScan] permet de lister les dossiers o les composants Spring doivent tre recherchs.
Les composants Spring sont des classes tagues avec des annotations Spring telles que @Service, @Component,
@Controller, ... Ici il n'y en a pas d'autres que ceux qui sont dfinis au sein de la classe [Config], aussi l'annotation a-t-elle
t mise en commentaires ;
lignes 25-33 : dfinissent la source de donnes, la base de donnes H2. C'est l'annotation @Bean de la ligne 25 qui fait de
l'objet cr par cette mthode un composant gr par Spring. Le nom de la mthode peut tre ici quelconque. Cependant
elle doit tre appele [dataSource] si l'EntityManagerFactory de la ligne 47 est absent et dfini par autoconfiguration ;
ligne 29 : la base de donnes s'appellera [demo] et sera gnre dans le dossier du projet ;
lignes 36-43 : dfinissent l'implmentation JPA utilise, ici une implmentation Hibernate. Le nom de la mthode peut tre
ici quelconque ;
ligne 39 : pas de logs SQL ;
ligne 30 : la base de donnes sera cre si elle n'existe pas ;
lignes 46-54 : dfinissent l'EntityManagerFactory qui va grer la persistance JPA. La mthode doit s'appeler
obligatoirement [entityManagerFactory] ;
ligne 47 : la mthode reoit deux paramtres ayant le type des deux beans dfinis prcdemment. Ceux-ci seront alors
construits puis injects par Spring comme paramtres de la mthode ;
ligne 49 : fixe l'implmentation JPA utilise ;
ligne 50 : fixent les dossiers o trouver les entits JPA ;
ligne 51 : fixe la source de donne grer ;
lignes 57-62 : le gestionnaire de transactions. La mthode doit s'appeler obligatoirement [transactionManager]. Elle
reoit pour paramtre le bean des lignes 46-54 ;
ligne 60 : le gestionnaire de transactions est associ l'EntityManagerFactory ;

Les mthodes prcdentes peuvent tre dfinies dans un ordre quelconque.


L'excution du projet donne les mmes rsultats. Un nouveau fichier apparat dans le dossier du projet, celui de la base de donnes
H2 :

Enfin, on peut se passer de Spring Boot. On cre une seconde classe excutable [Main2] :

http://tahe.developpez.com

33/325

La classe [Main2] a le code suivant :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.

package demo.console;

ligne 15 : la classe de configuration [Config] est dsormais exploite par la classe Spring
[AnnotationConfigApplicationContext]. On peut voir ligne 5 qu'il n'y a maintenant plus de dpendance vis vis de Spring
Boot.

import java.util.List;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import demo.config.Config;
import demo.entities.Customer;
import demo.repositories.CustomerRepository;
public class Main2 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
CustomerRepository repository = context.getBean(CustomerRepository.class);
....
context.close();
}
}

L'excution donne les mmes rsultats que prcdemment.

2.2.6

Cration d'une archive excutable

Pour crer une archive excutable du projet, on peut procder ainsi :

http://tahe.developpez.com

34/325

5
2
3
4

en [1] : on cre une configuration d'excution ;


en [2] : de type [Java Application]
en [3] : dsigne le projet excuter (utiliser le bouton Browse) ;
en [4] : dsigne la classe excuter ;
en [5] : le nom de la configuration d'excution peut tre quelconque ;

9
8
7

en [6] : on exporte le projet ;


en [7] : sous la forme d'une archive JAR excutable ;
en [8] : indique le chemin et le nom du fichier excutable crer ;
en [9] : le nom de la configuration d'excution cre en [5] ;

Ceci fait, on ouvre une console dans le dossier contenant l'archive excutable :
.....\dist>dir
12/06/2014 09:11

15 104 869 gs-accessing-data-jpa-2.jar

L'archive est excute de la faon suivante :


1. .....\dist>java -jar gs-accessing-data-jpa-2.jar

Les rsultats obtenus dans la console sont les suivants :


2. SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
3. SLF4J: Defaulting to no-operation (NOP) logger implementation

http://tahe.developpez.com

35/325

4. SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.


5. juin 12, 2014 9:48:38 AM org.hibernate.ejb.HibernatePersistence logDeprecation
6. WARN: HHH015016: Encountered a deprecated javax.persistence.spi.PersistenceProvider
[org.hibernate.ejb.HibernatePersistence]; use [org.hibernate.jpa.HibernatePersistenceProvider]
instead.
7. juin 12, 2014 9:48:38 AM org.hibernate.jpa.internal.util.LogHelper logPersistenceUnitInformation
8. INFO: HHH000204: Processing PersistenceUnitInfo [
9.
name: default
10.
...]
11. juin 12, 2014 9:48:38 AM org.hibernate.Version logVersion
12. INFO: HHH000412: Hibernate Core {4.3.4.Final}
13. juin 12, 2014 9:48:38 AM org.hibernate.cfg.Environment <clinit>
14. INFO: HHH000206: hibernate.properties not found
15. juin 12, 2014 9:48:38 AM org.hibernate.cfg.Environment buildBytecodeProvider
16. INFO: HHH000021: Bytecode provider name : javassist
17. juin 12, 2014 9:48:39 AM org.hibernate.annotations.common.reflection.java.JavaReflectionManager
<clinit>
18. INFO: HCANN000001: Hibernate Commons Annotations {4.0.4.Final}
19. juin 12, 2014 9:48:39 AM org.hibernate.dialect.Dialect <init>
20. INFO: HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
21. juin 12, 2014 9:48:39 AM org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory <init>
22. INFO: HHH000397: Using ASTQueryTranslatorFactory
23. juin 12, 2014 9:48:40 AM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
24. INFO: HHH000228: Running hbm2ddl schema update
25. juin 12, 2014 9:48:40 AM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
26. INFO: HHH000102: Fetching database metadata
27. juin 12, 2014 9:48:40 AM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
28. INFO: HHH000396: Updating schema
29. juin 12, 2014 9:48:40 AM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
30. INFO: HHH000262: Table not found: Customer
31. juin 12, 2014 9:48:40 AM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
32. INFO: HHH000262: Table not found: Customer
33. juin 12, 2014 9:48:40 AM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
34. INFO: HHH000262: Table not found: Customer
35. juin 12, 2014 9:48:40 AM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
36. INFO: HHH000232: Schema update complete
37. Customers found with findAll():
38. ------------------------------39. Customer[id=1, firstName='Jack', lastName='Bauer']
40. Customer[id=2, firstName='Chloe', lastName='O'Brian']
41. Customer[id=3, firstName='Kim', lastName='Bauer']
42. Customer[id=4, firstName='David', lastName='Palmer']
43. Customer[id=5, firstName='Michelle', lastName='Dessler']
44.
45. Customer found with findOne(1L):
46. -------------------------------47. Customer[id=1, firstName='Jack', lastName='Bauer']
48.
49. Customer found with findByLastName('Bauer'):
50. -------------------------------------------51. Customer[id=1, firstName='Jack', lastName='Bauer']
52. Customer[id=3, firstName='Kim', lastName='Bauer']

2.2.7

Crer un nouveau projet Spring Data

Pour crer un squelette de projet Spring Data, on peut procder de la faon suivante :

http://tahe.developpez.com

36/325

6
2

3
4

1
5

en [1], on cre un nouveau projet ;


en [2] : de type [Spring Starter Project] ;
le projet gnr sera un projet Maven. En [3], on indique le nom du groupe du projet ;
en [4] : on indique le nom de l'artifact (un jar ici) qui sera cr par construction du projet ;
en [5] : on indique le package de la classe excutable qui va tre cre dans le projet ;
en [6] : le nom Eclipse du projet peut tre quelconque (n'a pas tre identique [4]) ;
en [7] : on indique qu'on va crer un projet ayant une couche [JPA]. Les dpendances ncessaires un tel projet vont alors
tre incluses dans le fichier [pom.xml] ;

en [8] : le projet cr ;

Le fichier [pom.xml] intgre les dpendances ncessaires un projet JPA :


1.
2.
3.
4.
5.
6.
7.
8.
9.

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.1.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>

http://tahe.developpez.com

37/325

10.
<groupId>org.springframework.boot</groupId>
11.
<artifactId>spring-boot-starter-data-jpa</artifactId>
12.
</dependency>
13.
<dependency>
14.
<groupId>org.springframework.boot</groupId>
15.
<artifactId>spring-boot-starter-test</artifactId>
16.
<scope>test</scope>
17.
</dependency>
18. </dependencies>

lignes 9-12 : les dpendances ncessaires JPA vont inclure [Spring Data] ;
lignes 13-17 : les dpendances ncessaires aux test JUnit intgrs avec Spring ;

La classe excutable [Application] ne fait rien mais est pr-configure :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.

package istia.st;
import
import
import
import

org.springframework.boot.SpringApplication;
org.springframework.boot.autoconfigure.EnableAutoConfiguration;
org.springframework.context.annotation.ComponentScan;
org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
@EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

La classe de tests [ApplicationTests] ne fait rien mais est pr-configure :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.

package istia.st;

ligne 9 : l'annotation [@SpringApplicationConfiguration] permet d'exploiter le fichier de configuration [Application]. La


classe de test bnficiera ainsi de tous les beans qui seront dfinis par ce fichier ;
ligne 8 : l'annotation [@RunWith] permet l'intgration de Spring avec JUnit : la classe va pouvoir tre excute comme un
test JUnit. [@RunWith] est une annotation JUnit (ligne 4) alors que la classe [SpringJUnit4ClassRunner] est une classe
Spring (ligne 6) ;

import
import
import
import

org.junit.Test;
org.junit.runner.RunWith;
org.springframework.boot.test.SpringApplicationConfiguration;
org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
public class ApplicationTests {
@Test
public void contextLoads() {
}
}

Maintenant que nous avons un squelette d'application JPA, nous pouvons le complter pour crire le projet de la couche de
persistance du serveur de notre application de gestion de rendez-vous.

http://tahe.developpez.com

38/325

2.3

Le projet Eclipse du serveur

Couche
[web /
JSON]

Couche
[metier]

Couche
[DAO]

Couche
[JPA]

Pilote
[JDBC]

SGBD

7 4
Spring

Les lments principaux du projet sont les suivants :

2.4

[pom.xml] : fichier de configuration Maven du projet ;


[rdvmedecins.entities] : les entits JPA ;
[rdvmedecins.repositories] : les interfaces Spring Data d'accs aux entits JPA ;
[rdvmedecins.metier] : la couche [mtier] ;
[rdvmedecins.domain] : les entits manipules par la couche [mtier] ;
[rdvmdecins.config] : les classes de configuration de la couche de persistance ;
[rdvmedecins.boot] : une application console basique ;

La configuration Maven

http://tahe.developpez.com

39/325

Le fichier [pom.xml] du projet est le suivant :


1. <?xml version="1.0" encoding="UTF-8"?>
2. <project xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
3.
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
4.
<modelVersion>4.0.0</modelVersion>
5.
<groupId>istia.st.spring4.rdvmedecins</groupId>
6.
<artifactId>rdvmedecins-metier-dao</artifactId>
7.
<version>0.0.1-SNAPSHOT</version>
8.
<parent>
9.
<groupId>org.springframework.boot</groupId>
10.
<artifactId>spring-boot-starter-parent</artifactId>
11.
<version>1.0.0.RELEASE</version>
12.
</parent>
13.
<dependencies>
14.
<dependency>
15.
<groupId>org.springframework.boot</groupId>
16.
<artifactId>spring-boot-starter-data-jpa</artifactId>
17.
</dependency>
18.
<dependency>
19.
<groupId>org.springframework.boot</groupId>
20.
<artifactId>spring-boot-starter-test</artifactId>
21.
<scope>test</scope>
22.
</dependency>
23.
<dependency>
24.
<groupId>mysql</groupId>
25.
<artifactId>mysql-connector-java</artifactId>
26.
</dependency>
27.
<dependency>
28.
<groupId>commons-dbcp</groupId>
29.
<artifactId>commons-dbcp</artifactId>
30.
</dependency>
31.
<dependency>
32.
<groupId>commons-pool</groupId>
33.
<artifactId>commons-pool</artifactId>
34.
</dependency>
35.
<dependency>
36.
<groupId>com.fasterxml.jackson.core</groupId>
37.
<artifactId>jackson-databind</artifactId>
38.
</dependency>
39.
<dependency>
40.
<groupId>com.google.guava</groupId>
41.
<artifactId>guava</artifactId>
42.
<version>16.0.1</version>

http://tahe.developpez.com

40/325

43.
</dependency>
44.
</dependencies>
45.
<properties>
46.
<!-- use UTF-8 for everything -->
47.
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
48.
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
49.
<start-class>istia.st.spring.data.main.Application</start-class>
50.
</properties>
51.
<build>
52.
<plugins>
53.
<plugin>
54.
<artifactId>maven-compiler-plugin</artifactId>
55.
</plugin>
56.
<plugin>
57.
<groupId>org.springframework.boot</groupId>
58.
<artifactId>spring-boot-maven-plugin</artifactId>
59.
</plugin>
60.
</plugins>
61.
</build>
62.
<repositories>
63.
<repository>
64.
<id>spring-milestones</id>
65.
<name>Spring Milestones</name>
66.
<url>http://repo.spring.io/libs-milestone</url>
67.
<snapshots>
68.
<enabled>false</enabled>
69.
</snapshots>
70.
</repository>
71.
<repository>
72.
<id>org.jboss.repository.releases</id>
73.
<name>JBoss Maven Release Repository</name>
74.
<url>https://repository.jboss.org/nexus/content/repositories/releases</url>
75.
<snapshots>
76.
<enabled>false</enabled>
77.
</snapshots>
78.
</repository>
79.
</repositories>
80.
<pluginRepositories>
81.
<pluginRepository>
82.
<id>spring-milestones</id>
83.
<name>Spring Milestones</name>
84.
<url>http://repo.spring.io/libs-milestone</url>
85.
<snapshots>
86.
<enabled>false</enabled>
87.
</snapshots>
88.
</pluginRepository>
89.
</pluginRepositories>
90. </project>

lignes 8-12 : le projet s'appuie sur le projet parent [spring-boot-starter-parent]. Pour les dpendances dj prsentes dans le
projet parent, on ne prcise pas de version. C'est la version dfinie dans le parent qui sera utilise. Pour les autres
dpendances, on les dclare normalement ;
lignes 14-17 : pour Spring Data ;
lignes 18-22 : pour les tests JUnit ;
lignes 23-26 : pilote JDBC du SGBD MySQL5 ;
lignes 27-34 : pool de connexions Commons DBCP ;
lignes 35-38 : bibliothque Jackson de gestion du JSON ;
lignes 39-43 : bibliothque Google de gestion des collections ;

La version 1.1.0.RC1 de [spring-boot-starter-parent] utilise les versions suivantes des bibliothques :


1. <activemq.version>5.9.1</activemq.version>
2.
<aspectj.version>1.8.0</aspectj.version>
3.
<codahale-metrics.version>3.0.2</codahale-metrics.version>
4.
<commons-beanutils.version>1.9.1</commons-beanutils.version>
5.
<commons-collections.version>3.2.1</commons-collections.version>

http://tahe.developpez.com

41/325

6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.

<commons-dbcp.version>1.4</commons-dbcp.version>
<commons-digester.version>2.1</commons-digester.version>
<commons-pool.version>1.6</commons-pool.version>
<commons-pool2.version>2.2</commons-pool2.version>
<crashub.version>1.3.0-beta20</crashub.version>
<flyway.version>3.0</flyway.version>
<freemarker.version>2.3.20</freemarker.version>
<gemfire.version>7.0.2</gemfire.version>
<gradle.version>1.6</gradle.version>
<groovy.version>2.3.2</groovy.version>
<h2.version>1.3.175</h2.version>
<hamcrest.version>1.3</hamcrest.version>
<hibernate-entitymanager.version>4.3.1.Final</hibernate-entitymanager.version>
<hibernate-jpa-api.version>1.0.1.Final</hibernate-jpa-api.version>
<hibernate-validator.version>5.0.3.Final</hibernate-validator.version>
<hibernate.version>4.3.1.Final</hibernate.version>
<hikaricp.version>1.3.8</hikaricp.version>
<hornetq.version>2.4.1.Final</hornetq.version>
<hsqldb.version>2.3.2</hsqldb.version>
<httpasyncclient.version>4.0.1</httpasyncclient.version>
<httpclient.version>4.3.3</httpclient.version>
<jackson.version>2.3.3</jackson.version>
<java.version>1.6</java.version>
<javassist.version>3.18.1-GA</javassist.version>
<jedis.version>2.4.1</jedis.version>
<jetty-jsp.version>2.2.0.v201112011158</jetty-jsp.version>
<jetty.version>8.1.14.v20131031</jetty.version>
<joda-time.version>2.3</joda-time.version>
<jolokia.version>1.2.0</jolokia.version>
<jstl.version>1.2</jstl.version>
<junit.version>4.11</junit.version>
<liquibase.version>3.0.8</liquibase.version>
<log4j.version>1.2.17</log4j.version>
<logback.version>1.1.2</logback.version>
<mockito.version>1.9.5</mockito.version>
<mongodb.version>2.12.1</mongodb.version>
<mysql.version>5.1.30</mysql.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<reactor.version>1.1.1.RELEASE</reactor.version>
<servlet-api.version>3.0.1</servlet-api.version>
<slf4j.version>1.7.7</slf4j.version>
<snakeyaml.version>1.13</snakeyaml.version>
<solr.version>4.7.2</solr.version>
<spock.version>0.7-groovy-2.0</spock.version>
<spring-amqp.version>1.3.4.RELEASE</spring-amqp.version>
<spring-batch.version>3.0.0.RELEASE</spring-batch.version>
<spring-boot.version>1.1.0.RC1</spring-boot.version>
<spring-data-releasetrain.version>Dijkstra-RELEASE</spring-data-releasetrain.version>
<spring-hateoas.version>0.12.0.RELEASE</spring-hateoas.version>
<spring-integration.version>4.0.2.RELEASE</spring-integration.version>
<spring-loaded.version>1.2.0.RELEASE</spring-loaded.version>
<spring-mobile.version>1.1.1.RELEASE</spring-mobile.version>
<spring-security-jwt.version>1.0.2.RELEASE</spring-security-jwt.version>
<spring-security.version>3.2.4.RELEASE</spring-security.version>
<spring-social-facebook.version>1.1.1.RELEASE</spring-social-facebook.version>
<spring-social-linkedin.version>1.0.1.RELEASE</spring-social-linkedin.version>
<spring-social-twitter.version>1.1.0.RELEASE</spring-social-twitter.version>
<spring-social.version>1.1.0.RELEASE</spring-social.version>
<spring.version>4.0.5.RELEASE</spring.version>
<thymeleaf-extras-springsecurity3.version>2.1.1.RELEASE</thymeleaf-extrasspringsecurity3.version>
<thymeleaf-layout-dialect.version>1.2.4</thymeleaf-layout-dialect.version>
<thymeleaf.version>2.1.3.RELEASE</thymeleaf.version>
<tomcat.version>7.0.54</tomcat.version>
<velocity-tools.version>2.0</velocity-tools.version>
<velocity.version>1.7</velocity.version>

http://tahe.developpez.com

42/325

2.5

Les entits JPA

Couche
[web /
JSON]

Couche
[metier]

Couche
[DAO]

Couche
[JPA]

Pilote
[JDBC]

SGBD

7 4
Spring
Les entits JPA sont les objets qui vont encapsuler les lignes des tables de la base de donnes.

La classe [AbstractEntity] est la classe parent des entits [Personne, Creneau, Rv]. Sa dfinition est la suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.

package rdvmedecins.entities;
import java.io.Serializable;
import
import
import
import
import

javax.persistence.GeneratedValue;
javax.persistence.GenerationType;
javax.persistence.Id;
javax.persistence.MappedSuperclass;
javax.persistence.Version;

@MappedSuperclass
public class AbstractEntity implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
protected Long id;
@Version
protected Long version;
@Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
// initialisation
public AbstractEntity build(Long id, Long version) {
this.id = id;

http://tahe.developpez.com

43/325

31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48. }

this.version = version;
return this;
}
@Override
public boolean equals(Object entity) {
String class1 = this.getClass().getName();
String class2 = entity.getClass().getName();
if (!class2.equals(class1)) {
return false;
}
AbstractEntity other = (AbstractEntity) entity;
return this.id == other.id;
}
// getters et setters
..

ligne 11 : l'annotation [@MappedSuperclass] indique que la classe annote est parente d'entits JPA [@Entity] ;
lignes 15-17 : dfinissent la cl primaire [id] de chaque entit. C'est l'annotation [@Id] qui fait du champ [id] une cl
primaire. L'annotation [@GeneratedValue(strategy = GenerationType.AUTO)] indique que la valeur de cette cl primaire
est gnre par le SGBD et qu'aucun mode de gnration n'est impos ;
lignes 18-19 : dfinissent la version de chaque entit. L'implmentation JPA va incrmenter ce n de version chaque fois
que l'entit sera modifie. Ce n sert empcher la mise jour simultane de l'entit par deux utilisateur diffrents : deux
utilisateurs U1 et U2 lisent l'entit E avec un n de version gal V1. U1 modifie E et persiste cette modification en base :
le n de version passe alors V1+1. U2 modifie E son tour et persiste cette modification en base : il recevra une
exception car il possde une version (V1) diffrente de celle en base (V1+1) ;
lignes 29-33 : la mthode [build] permet d'initialiser les deux champs de [AbstractEntity]. Cette mthode rend la rfrence
de l'instance [AbstractEntity] ainsi initialise ;
lignes 36-44 : la mthode [equals] de la classe est redfinie : deux entits seront dites gales si elles ont le mme nom de
classe et le mme identifiant id ;

L'entit [Personne] est la classe parente des entits [Medecin] et [Client] :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.

package rdvmedecins.entities;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
@MappedSuperclass
public class Personne extends AbstractEntity {
private static final long serialVersionUID = 1L;
// attributs d'une personne
@Column(length = 5)
private String titre;
@Column(length = 20)
private String nom;
@Column(length = 20)
private String prenom;
// constructeur par dfaut
public Personne() {
}
// constructeur avec paramtres
public Personne(String titre, String nom, String prenom) {
this.titre = titre;
this.nom = nom;
this.prenom = prenom;
}
// toString
public String toString() {
return String.format("Personne[%s, %s, %s, %s, %s]", id, version, titre, nom, prenom);
}

http://tahe.developpez.com

44/325

32.
33.
34.
35. }

// getters et setters
...

ligne 6 : l'annotation [@MappedSuperclass] indique que la classe annote est parente d'entits JPA [@Entity] ;
lignes 10-15 : une personne a un titre (Melle), un prnom (Jacqueline), un nom (Tatou). aucune information n'est donne
sur les colonnes de la table. Elles porteront donc par dfaut les mmes noms que les champs ;

L'entit [Medecin] est la suivante :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.

package rdvmedecins.entities;

ligne 6 : la classe est une entit JPA ;


ligne 7 : associe la table [MEDECINS] de la base de donnes ;
ligne 8 : l'entit [Medecin] drive de l'entit [Personne] ;

import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@Table(name = "medecins")
public class Medecin extends Personne {
private static final long serialVersionUID = 1L;
// constructeur par dfaut
public Medecin() {
}
// constructeur avec paramtres
public Medecin(String titre, String nom, String prenom) {
super(titre, nom, prenom);
}
public String toString() {
return String.format("Medecin[%s]", super.toString());
}
}

Un mdecin pourra tre initialis de la faon suivante :


Medecin m=new Medecin("Mr","Paul","Tatou");

Si de plus, on veut lui affecter un identifiant et une version on pourra crire :


Medecin m=new Medecin("Mr","Paul","Tatou").build(10,1);

o la mthode [build] est celle dfinie dans [AbstractEntity].


L'entit [Client] est la suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.

package rdvmedecins.entities;
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@Table(name = "clients")
public class Client extends Personne {
private static final long serialVersionUID = 1L;
// constructeur par dfaut
public Client() {

http://tahe.developpez.com

45/325

14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26. }

}
// constructeur avec paramtres
public Client(String titre, String nom, String prenom) {
super(titre, nom, prenom);
}
// identit
public String toString() {
return String.format("Client[%s]", super.toString());
}

ligne 6 : la classe est une entit JPA ;


ligne 7 : associe la table [CLIENTS] de la base de donnes ;
ligne 8 : l'entit [Client] drive de l'entit [Personne] ;

L'entit [Creneau] est la suivante :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.

package rdvmedecins.entities;
import
import
import
import
import
import

javax.persistence.Column;
javax.persistence.Entity;
javax.persistence.FetchType;
javax.persistence.JoinColumn;
javax.persistence.ManyToOne;
javax.persistence.Table;

@Entity
@Table(name = "creneaux")
public class Creneau extends AbstractEntity {
private static final long serialVersionUID = 1L;
// caractristiques d'un crneau de RV
private int hdebut;
private int mdebut;
private int hfin;
private int mfin;
// un crneau est li un mdecin
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "id_medecin")
private Medecin medecin;
// cl trangre
@Column(name = "id_medecin", insertable = false, updatable = false)
private long idMedecin;
// constructeur par dfaut
public Creneau() {
}
// constructeur avec paramtres
public Creneau(Medecin medecin, int hdebut, int mdebut, int hfin, int mfin) {
this.medecin = medecin;
this.hdebut = hdebut;
this.mdebut = mdebut;
this.hfin = hfin;
this.mfin = mfin;
}

// toString
public String toString() {
return String.format("Crneau[%d, %d, %d, %d:%d, %d:%d]", id, version, idMedecin, hdebut,
mdebut, hfin, mfin);
46.
}

http://tahe.developpez.com

46/325

47.
48.
49.
50.
51.
52.
53.
54.
55. }

// cl trangre
public long getIdMedecin() {
return idMedecin;
}
// setters - getters
...

ligne 10 : la classe est une entit JPA ;


ligne 11 : associe la table [CRENEAUX] de la base de donnes ;
ligne 12 : l'entit [Creneau] drive de l'entit [AbstractEntity] et hrite donc de l'identifiant [id] et de la version [version] ;
ligne 16 : heure de dbut du crneau (14) ;
ligne 17 : minutes de dbut du crneau (20) ;
ligne 18 : heure de fin du crneau (14) ;
ligne 19 : minutes de fin du crneau (40) ;
lignes 22-24 : le mdecin propritaire du crneau. La table [CRENEAUX] a une cl trangre sur la table [MEDECINS].
Cette relation est matrialise par les lignes 22-24 ;
ligne 22 : l'annotation [@ManyToOne] signale une relation plusieurs (crneaux) un (mdecin). L'attribut
[fetch=FetchType.LAZY] indique que lorsqu'on demande une entit [Creneau] au contexte de persistance et que celleci doit tre cherche dans la base de donnes, alors l'entit [Medecin] n'est pas ramene avec elle. L'intrt de ce mode est
que l'entit [Medecin] n'est cherche que si le dveloppeur le demande. On conomise ainsi la mmoire et on gagne en
performances ;
ligne 23 : indique le nom de la colonne cl trangre dans la table [CRENEAUX] ;
lignes 27-28 : la cl trangre sur la table [MEDECINS] ;
ligne 27 : la colonne [ID_MEDECIN] a dj t utilise ligne 23. Cela veut dire qu'elle peut tre modifie par deux voies
diffrentes ce que n'accepte pas la norme JPA. On ajoute donc les attributs [insertable = false, updatable = false], ce qui
fait que la colonne ne peut qu'tre lue ;

L'entit [Rv] est la suivante :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.

package rdvmedecins.entities;
import java.util.Date;
import
import
import
import
import
import
import
import

javax.persistence.Column;
javax.persistence.Entity;
javax.persistence.FetchType;
javax.persistence.JoinColumn;
javax.persistence.ManyToOne;
javax.persistence.Table;
javax.persistence.Temporal;
javax.persistence.TemporalType;

@Entity
@Table(name = "rv")
public class Rv extends AbstractEntity {
private static final long serialVersionUID = 1L;
// caractristiques d'un Rv
@Temporal(TemporalType.DATE)
private Date jour;
// un rv est li un client
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "id_client")
private Client client;
// un rv est li un crneau
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "id_creneau")
private Creneau creneau;
// cls trangres

http://tahe.developpez.com

47/325

34.
@Column(name = "id_client", insertable = false, updatable = false)
35.
private long idClient;
36.
@Column(name = "id_creneau", insertable = false, updatable = false)
37.
private long idCreneau;
38.
39.
// constructeur par dfaut
40.
public Rv() {
41.
}
42.
43.
// avec paramtres
44.
public Rv(Date jour, Client client, Creneau creneau) {
45.
this.jour = jour;
46.
this.client = client;
47.
this.creneau = creneau;
48.
}
49.
50.
// toString
51.
public String toString() {
52.
return String.format("Rv[%d, %s, %d, %d]", id, jour, client.id, creneau.id);
53.
}
54.
55.
// cls trangres
56.
public long getIdCreneau() {
57.
return idCreneau;
58.
}
59.
60.
public long getIdClient() {
61.
return idClient;
62.
}
63.
64.
// getters et setters
65. ...
66. }

2.6

ligne 14 : la classe est une entit JPA ;


ligne 15 : associe la table [RV] de la base de donnes ;
ligne 16 : l'entit [Rv] drive de l'entit [AbstractEntity] et hrite donc de l'identifiant [id] et de la version [version] ;
ligne 21 : la date du rendez-vous ;
ligne 20 : le type [Date] de Java contient la fois une date et une heure. Ici on prcise que seule la date est utilise ;
lignes 24-26 : le client pour lequel ce rendez-vous a t pris. La table [RV] a une cl trangre sur la table [CLIENTS].
Cette relation est matrialise par les lignes 24-26 ;
lignes 29-31 : le crneau horaire du rendez-vous. La table [RV] a une cl trangre sur la table [CRENEAUX]. Cette
relation est matrialise par les lignes 29-31 ;
lignes 34-35 : la cl trangre [idClient] ;
lignes 36-37 : la cl trangre [idCreneau] ;

La couche [DAO]

Couche
[web /
JSON]

Couche
[metier]

Couche
[DAO]

Couche
[JPA]

Pilote
[JDBC]

SGBD

7 4
Spring
Nous allons implmenter la couche [DAO] avec Spring Data :

http://tahe.developpez.com

48/325

La couche [DAO] est implmente avec quatre interfaces Spring Data :

[ClientRepository] : donne accs aux entits JPA [Client] ;


[CreneauRepository] : donne accs aux entits JPA [Creneau] ;
[MedecinRepository] : donne accs aux entits JPA [Medecin] ;
[RvRepository] : donne accs aux entits JPA [Rv] ;

L'interface [MedecinRepository] est la suivante :


1.
2.
3.
4.
5.
6.
7.
8.

package rdvmedecins.repositories;

ligne 7 : l'interface [MedecinRepository] se contente d'hriter des mthodes de l'interface [CrudRepository] sans en ajouter
d'autres ;

import org.springframework.data.repository.CrudRepository;
import rdvmedecins.entities.Medecin;
public interface MedecinRepository extends CrudRepository<Medecin, Long> {
}

L'interface [ClientRepository] est la suivante :


1.
2.
3.
4.
5.
6.
7.
8.

package rdvmedecins.repositories;

ligne 7 : l'interface [ClientRepository] se contente d'hriter des mthodes de l'interface [CrudRepository] sans en ajouter
d'autres ;

import org.springframework.data.repository.CrudRepository;
import rdvmedecins.entities.Client;
public interface ClientRepository extends CrudRepository<Client, Long> {
}

L'interface [CreneauRepository] est la suivante :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.

package rdvmedecins.repositories;

ligne 8 : l'interface [CreneauRepository] hrite des mthodes de l'interface [CrudRepository] ;


lignes 10-11 : la mthode [getAllCreneaux] permet d'avoir les crneaux horaires d'un mdecin ;

import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import rdvmedecins.entities.Creneau;
public interface CreneauRepository extends CrudRepository<Creneau, Long> {
// liste des crneaux horaires d'un mdecin
@Query("select c from Creneau c where c.medecin.id=?1")
Iterable<Creneau> getAllCreneaux(long idMedecin);
}

http://tahe.developpez.com

49/325

ligne 11 : le paramtre est l'identifiant du mdecin. Le rsultat est une liste de crneaux horaires sous la forme d'un objet
[Iterable<Creneau>] ;
ligne 10 : l'annotation [@Query] permet de spcifier la requte JPQL (Java Persistence Query Language) qui implmente la
mthode. Le paramtre [?1] sera remplac par le paramtre [idMedecin] de la mthode ;

L'interface [RvRepository] est la suivante :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.

package rdvmedecins.repositories;

ligne 10 : l'interface [RvRepository] hrite des mthodes de l'interface [CrudRepository] ;


lignes 12-13 : la mthode [getRvMedecinJour] permet d'avoir les rendez-vous d'un mdecin pour un jour donn ;
ligne 13 : les paramtres sont l'identifiant du mdecin et le jour. Le rsultat est une liste de rendez-vous sous la forme d'un
objet [Iterable<Rv>] ;
ligne 12 : l'annotation [@Query] permet de spcifier la requte JPQL qui implmente la mthode. Le paramtre [?1] sera
remplac par le paramtre [idMedecin] de la mthode et le paramtre [?2] sera remplac par le paramtre [jour] de la
mthode. On ne peut se contenter de la requte JPQL suivante :

import java.util.Date;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import rdvmedecins.entities.Rv;
public interface RvRepository extends CrudRepository<Rv, Long> {

@Query("select rv from Rv rv left join fetch rv.client c left join fetch rv.creneau cr where
cr.medecin.id=?1 and rv.jour=?2")
13.
Iterable<Rv> getRvMedecinJour(long idMedecin, Date jour);
14. }

select rv from Rv rv where rv.creneau.medecin.id=?1 and rv.jour=?2


car les champs de la classe Rv, de types [Client] et [Creneau] sont obtenus en mode [FetchType.LAZY], ce qui signifie
qu'ils doivent tre demands explicitement pour tre obtenus. Ceci est fait dans la requte JPQL avec la syntaxe [left join
fetch entit] qui demandent qu'une jointure soit faite avec la table sur laquelle pointe la cl trangre afin de rcuprer
l'entit pointe ;

2.7

La couche [mtier]

Couche
[web /
JSON]

Couche
[mtier]

Couche
[DAO]

Couche
[JPA]

Pilote
[JDBC]

SGBD

7 4
Spring

[IMetier] est l'interface de la couche [mtier] et [Metier] son implmentation ;


[AgendaMedecinJour] et [CreneauMedecinJour] sont deux entits mtier ;

http://tahe.developpez.com

50/325

2.7.1

Les entits

L'entit [CreneauMedecinJour] associe un crneau horaire et le rendez-vous ventuel pris dans ce crneau :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.

package rdvmedecins.domain;

ligne 12 : le crneau horaire ;


ligne 13 : l'ventuel rendez-vous null sinon ;

import java.io.Serializable;
import rdvmedecins.entities.Creneau;
import rdvmedecins.entities.Rv;
public class CreneauMedecinJour implements Serializable {
private static final long serialVersionUID = 1L;
// champs
private Creneau creneau;
private Rv rv;
// constructeurs
public CreneauMedecinJour() {
}
public CreneauMedecinJour(Creneau creneau, Rv rv) {
this.creneau=creneau;
this.rv=rv;
}
// toString
@Override
public String toString() {
return String.format("[%s %s]", creneau, rv);
}
// getters et setters
...
}

L'entit [AgendaMedecinJour] est l'agenda d'un mdecin pour un jour donn, --d la liste de ses rendez-vous :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.

package rdvmedecins.domain;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;
import rdvmedecins.entities.Medecin;
public class AgendaMedecinJour implements Serializable {
private static final long serialVersionUID = 1L;
// champs
private Medecin medecin;
private Date jour;
private CreneauMedecinJour[] creneauxMedecinJour;
// constructeurs
public AgendaMedecinJour() {
}

public AgendaMedecinJour(Medecin medecin, Date jour, CreneauMedecinJour[]


creneauxMedecinJour) {
23.
this.medecin = medecin;

http://tahe.developpez.com

51/325

24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.

this.jour = jour;
this.creneauxMedecinJour = creneauxMedecinJour;
}

public String toString() {


StringBuffer str = new StringBuffer("");
for (CreneauMedecinJour cr : creneauxMedecinJour) {
str.append(" ");
str.append(cr.toString());
}
return String.format("Agenda[%s,%s,%s]", medecin, new
SimpleDateFormat("dd/MM/yyyy").format(jour), str.toString());
35.
}
36.
37.
// getters et setters
38. ...
39. }

2.7.2

ligne 13 : le mdecin ;
ligne 14 : le jour dans l'agenda ;
ligne 15 : ses crneaux horaires avec ou sans rendez-vous ;

Le service

L'interface de la couche [mtier] est la suivante :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.

package rdvmedecins.metier;
import java.util.Date;
import java.util.List;
import
import
import
import
import

rdvmedecins.domain.AgendaMedecinJour;
rdvmedecins.entities.Client;
rdvmedecins.entities.Creneau;
rdvmedecins.entities.Medecin;
rdvmedecins.entities.Rv;

public interface IMetier {


// liste des clients
public List<Client> getAllClients();
// liste des Mdecins
public List<Medecin> getAllMedecins();
// liste des crneaux horaires d'un mdecin
public List<Creneau> getAllCreneaux(long idMedecin);
// liste des Rv d'un mdecin, un jour donn
public List<Rv> getRvMedecinJour(long idMedecin, Date jour);
// trouver un client identifi par son id
public Client getClientById(long id);
// trouver un client identifi par son id
public Medecin getMedecinById(long id);
// trouver un Rv identifi par son id
public Rv getRvById(long id);
// trouver un crneau horaire identifi par son id
public Creneau getCreneauById(long id);
// ajouter un RV
public Rv ajouterRv(Date jour, Creneau crneau, Client client);
// supprimer un RV

http://tahe.developpez.com

52/325

42.
43.
44.
45.
46.
47. }

public void supprimerRv(Rv rv);


// metier
public AgendaMedecinJour getAgendaMedecinJour(long idMedecin, Date jour);

Les commentaires expliquent le rle de chacune des mthodes.


L'implmentation de l'interface [IMetier] est la classe [Metier] suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.

package rdvmedecins.metier;
import
import
import
import

java.util.Date;
java.util.Hashtable;
java.util.List;
java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import
import
import
import
import
import
import
import
import
import

rdvmedecins.domain.AgendaMedecinJour;
rdvmedecins.domain.CreneauMedecinJour;
rdvmedecins.entities.Client;
rdvmedecins.entities.Creneau;
rdvmedecins.entities.Medecin;
rdvmedecins.entities.Rv;
rdvmedecins.repositories.ClientRepository;
rdvmedecins.repositories.CreneauRepository;
rdvmedecins.repositories.MedecinRepository;
rdvmedecins.repositories.RvRepository;

import com.google.common.collect.Lists;
@Service("mtier")
public class Metier implements IMetier {
// rpositories
@Autowired
private MedecinRepository medecinRepository;
@Autowired
private ClientRepository clientRepository;
@Autowired
private CreneauRepository creneauRepository;
@Autowired
private RvRepository rvRepository;
// implmentation interface
@Override
public List<Client> getAllClients() {
return Lists.newArrayList(clientRepository.findAll());
}
@Override
public List<Medecin> getAllMedecins() {
return Lists.newArrayList(medecinRepository.findAll());
}
@Override
public List<Creneau> getAllCreneaux(long idMedecin) {
return Lists.newArrayList(creneauRepository.getAllCreneaux(idMedecin));
}
@Override
public List<Rv> getRvMedecinJour(long idMedecin, Date jour) {
return Lists.newArrayList(rvRepository.getRvMedecinJour(idMedecin, jour));
}

http://tahe.developpez.com

53/325

57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92. }

@Override
public Client getClientById(long id) {
return clientRepository.findOne(id);
}
@Override
public Medecin getMedecinById(long id) {
return medecinRepository.findOne(id);
}
@Override
public Rv getRvById(long id) {
return rvRepository.findOne(id);
}
@Override
public Creneau getCreneauById(long id) {
return creneauRepository.findOne(id);
}
@Override
public Rv ajouterRv(Date jour, Creneau crneau, Client client) {
return rvRepository.save(new Rv(jour, client, crneau));
}
@Override
public void supprimerRv(Rv rv) {
rvRepository.delete(rv.getId());
}
public AgendaMedecinJour getAgendaMedecinJour(long idMedecin, Date jour) {
...
}

ligne 24 : l'annotation [@Service] est une annotation Spring qui fait de la classe annote un composant gr par Spring. On
peut ou non donner un nom un composant. Celui-ci est nomm [mtier] ;
ligne 25 : la classe [Metier] implmente l'interface [IMetier] ;
ligne 28 : l'annotation [@Autowired] est une annotation Spring. La valeur du champ ainsi annot sera initialise (injecte)
par Spring avec la rfrence d'un composant Spring du type ou du nom prciss. Ici l'annotation [@Autowired] ne prcise
pas de nom. Ce sera donc une injection par type qui sera faite ;
ligne 29 : le champ [medecinRepository] sera initialis avec la rfrence d'un composant Spring de type
[MedecinRepository]. Ce sera la rfrence de la classe gnre par Spring Data pour implmenter l'interface
[MedecinRepository] que nous avons dj prsente ;
lignes 30-35 : ce processus est rpt pour les trois autres interfaces tudies ;
lignes 39-41 : implmentation de la mthode [getAllClients] ;
ligne 40 : nous utilisons la mthode [findAll] de l'interface [ClientRepository]. Cette mthode rend un type
[Iterable<Client>] que nous transformons en [List<Client>] avec la mthode statique [Lists.newArrayList]. La classe
[Lists] est dfinie dans la bibliothque Google Guava. Dans [pom.xml] cette dpendance a t importe :
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>16.0.1</version>
</dependency>

lignes 38-86 : les mthodes de l'interface [IMetier] sont implmentes avec l'aide des classes de la couche [DAO] ;

Seule la mthode de la ligne 88 est spcifique la couche [mtier]. Elle a t place ici parce qu'elle fait un traitement mtier qui
n'est pas qu'un simple accs aux donnes. Sans cette mthode, il n'y avait pas de raison de crer une couche [mtier]. La mthode
[getAgendaMedecinJour] est la suivante :
1.
2.

public AgendaMedecinJour getAgendaMedecinJour(long idMedecin, Date jour) {


// liste des crneaux horaires du mdecin

http://tahe.developpez.com

54/325

3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.

List<Creneau> creneauxHoraires = getAllCreneaux(idMedecin);


// liste des rservations de ce mme mdecin pour ce mme jour
List<Rv> reservations = getRvMedecinJour(idMedecin, jour);
// on cre un dictionnaire partir des Rv pris
Map<Long, Rv> hReservations = new Hashtable<Long, Rv>();
for (Rv resa : reservations) {
hReservations.put(resa.getCreneau().getId(), resa);
}
// on cre l'agenda pour le jour demand
AgendaMedecinJour agenda = new AgendaMedecinJour();
// le mdecin
agenda.setMedecin(getMedecinById(idMedecin));
// le jour
agenda.setJour(jour);
// les crneaux de rservation
CreneauMedecinJour[] creneauxMedecinJour = new CreneauMedecinJour[creneauxHoraires.size()];
agenda.setCreneauxMedecinJour(creneauxMedecinJour);
// remplissage des crneaux de rservation
for (int i = 0; i < creneauxHoraires.size(); i++) {
// ligne i agenda
creneauxMedecinJour[i] = new CreneauMedecinJour();
// crneau horaire
Creneau crneau = creneauxHoraires.get(i);
long idCreneau = crneau.getId();
creneauxMedecinJour[i].setCreneau(crneau);
// le crneau est-il libre ou rserv ?
if (hReservations.containsKey(idCreneau)) {
// le crneau est occup - on note la rsa
Rv resa = hReservations.get(idCreneau);
creneauxMedecinJour[i].setRv(resa);
}
}
// on rend le rsultat
return agenda;
}

Le lecteur est invit lire les commentaires. L'algorithme est le suivant :

2.8

on rcupre tous les crneaux horaires du mdecin indiqu ;


on rcupre tous ses rendez-vous pour le jour indiqu ;
avec ces deux informations, on est capable de dire si un crneau horaire est libre ou occup ;

La configuration du projet

La classe [DomainAndPersitenceConfig] configure l'ensemble du projet :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.

package rdvmedecins.config;
import javax.sql.DataSource;
import
import
import
import
import
import
import

http://tahe.developpez.com

org.apache.commons.dbcp.BasicDataSource;
org.springframework.boot.autoconfigure.EnableAutoConfiguration;
org.springframework.boot.orm.jpa.EntityScan;
org.springframework.context.annotation.Bean;
org.springframework.context.annotation.ComponentScan;
org.springframework.data.jpa.repository.config.EnableJpaRepositories;
org.springframework.orm.jpa.JpaVendorAdapter;

55/325

12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.

import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableJpaRepositories(basePackages = { "rdvmedecins.repositories" })
@EnableAutoConfiguration
@ComponentScan(basePackages = { "rdvmedecins" })
@EntityScan(basePackages = { "rdvmedecins.entities" })
@EnableTransactionManagement
public class DomainAndPersistenceConfig {
// la source de donnes MySQL
@Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/dbrdvmedecins");
dataSource.setUsername("root");
dataSource.setPassword("");
return dataSource;
}
// le provider JPA - n'est pas ncessaire si on est satisfait des valeurs par dfaut
utilises par Spring boot
// ici on le dfinit pour activer / dsactiver les logs SQL
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
hibernateJpaVendorAdapter.setShowSql(false);
hibernateJpaVendorAdapter.setGenerateDdl(false);
hibernateJpaVendorAdapter.setDatabase(Database.MYSQL);
return hibernateJpaVendorAdapter;
}
// l'EntityManagerFactory et le TransactionManager sont dfinis avec des valeurs par dfaut
par Spring boot

46.
47. }

lignes 45 : nous n'allons pas dfinir les beans [EntityManagerFactory] et [TransactionManager]. Nous allons pour cela nous
appuyer sur l'annotation [@EnableAutoConfiguration] de Spring Boot (ligne 17) ;
lignes 24-32 : dfinissent la source de donnes MySQL5. C'est un bean qui en gnral ne peut tre devin par Spring
Boot ;
lignes 36-43 : nous configurons galement l'implmentation JPA pour mettre l'attribut [showSql] d'Hibernate faux (ligne
39). Par dfaut, il est vrai ;
pour l'instant, les seuls composants grs par Spring sont les beans des lignes 25 et 37 plus les beans
[EntityManagerFactory] et [TransactionManager] par autoconfiguration. Il nous faut ajouter les beans des couches [mtier]
et [DAO] ;
la ligne 16 ajoute au contexte de Spring les interfaces du package [rdvmdecins.repositories] qui hritent de l'interface
[CrudRepository] ;
la ligne 18 ajoute au contexte de Spring toutes les classes du package [rdvmedecins] et ses descendants ayant une
annotation Spring. Dans le package [rdvmdecins.metier], la classe [Metier] avec son annotation [@Service] va tre trouve
et ajoute au contexte Spring ;
ligne 45 : un bean [entityManagerFactory] va tre dfini par dfaut par Spring Boot. On doit indiquer ce bean o se
trouvent les entits JPA qu'il doit grer. C'est la ligne 19 qui fait cela ;
ligne 20 : indique que les mthodes des interfaces hritant de l'interface [CrudRepository] doivent tre excutes au sein
d'une transaction ;

http://tahe.developpez.com

56/325

2.9

Les tests de la couche [mtier]

La classe [rdvmedecins.tests.Metier] est une classe de test Spring / JUnit 4 :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.

package rdvmedecins.tests;
import java.text.ParseException;
import java.util.Date;
import java.util.List;
import
import
import
import
import
import

org.junit.Assert;
org.junit.Test;
org.junit.runner.RunWith;
org.springframework.beans.factory.annotation.Autowired;
org.springframework.boot.test.SpringApplicationConfiguration;
org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import
import
import
import
import
import
import

rdvmedecins.config.DomainAndPersistenceConfig;
rdvmedecins.domain.AgendaMedecinJour;
rdvmedecins.entities.Client;
rdvmedecins.entities.Creneau;
rdvmedecins.entities.Medecin;
rdvmedecins.entities.Rv;
rdvmedecins.metier.IMetier;

@SpringApplicationConfiguration(classes = DomainAndPersistenceConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class Metier {
@Autowired
private IMetier mtier;
@Test
public void test1(){
// affichage clients
List<Client> clients = mtier.getAllClients();
display("Liste des clients :", clients);
// affichage mdecins
List<Medecin> medecins = mtier.getAllMedecins();
display("Liste des mdecins :", medecins);
// affichage crneaux d'un mdecin
Medecin mdecin = medecins.get(0);
List<Creneau> creneaux = mtier.getAllCreneaux(mdecin.getId());
display(String.format("Liste des crneaux du mdecin %s", mdecin), creneaux);
// liste des Rv d'un mdecin, un jour donn
Date jour = new Date();
display(String.format("Liste des rv du mdecin %s, le [%s]", mdecin, jour),
mtier.getRvMedecinJour(mdecin.getId(), jour));
// ajouter un RV
Rv rv = null;
Creneau crneau = creneaux.get(2);
Client client = clients.get(0);
System.out.println(String.format("Ajout d'un Rv le [%s] dans le crneau %s pour le client
%s", jour, crneau,
client));
rv = mtier.ajouterRv(jour, crneau, client);
// vrification
Rv rv2 = mtier.getRvById(rv.getId());
Assert.assertEquals(rv, rv2);

http://tahe.developpez.com

57/325

54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.

display(String.format("Liste des Rv du mdecin %s, le [%s]", mdecin, jour),


mtier.getRvMedecinJour(mdecin.getId(), jour));
// ajouter un RV dans le mme crneau du mme jour
// doit provoquer une exception
System.out.println(String.format("Ajout d'un Rv le [%s] dans le crneau %s pour le client
%s", jour, crneau,
client));
Boolean erreur = false;
try {
rv = mtier.ajouterRv(jour, crneau, client);
System.out.println("Rv ajout");
} catch (Exception ex) {
Throwable th = ex;
while (th != null) {
System.out.println(ex.getMessage());
th = th.getCause();
}
// on note l'erreur
erreur = true;
}
// on vrifie qu'il y a eu une erreur
Assert.assertTrue(erreur);
// liste des RV
display(String.format("Liste des Rv du mdecin %s, le [%s]", mdecin, jour),
mtier.getRvMedecinJour(mdecin.getId(), jour));
// affichage agenda
AgendaMedecinJour agenda = mtier.getAgendaMedecinJour(mdecin.getId(), jour);
System.out.println(agenda);
Assert.assertEquals(rv, agenda.getCreneauxMedecinJour()[2].getRv());
// supprimer un RV
System.out.println("Suppression du Rv ajout");
mtier.supprimerRv(rv);
// vrification
rv2 = mtier.getRvById(rv.getId());
Assert.assertNull(rv2);
display(String.format("Liste des Rv du mdecin %s, le [%s]", mdecin, jour),
mtier.getRvMedecinJour(mdecin.getId(), jour));
}

87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97. }

// mthode utilitaire - affiche les lments d'une collection


private void display(String message, Iterable<?> elements) {
System.out.println(message);
for (Object element : elements) {
System.out.println(element);
}
}

ligne 22 : l'annotation [@SpringApplicationConfiguration] permet d'exploiter le fichier de configuration


[DomainAndPersistenceConfig] tudi prcdemment. La classe de test bnficie ainsi de tous les beans dfinis par ce
fichier ;
ligne 23 : l'annotation [@RunWith] permet l'intgration de Spring avec JUnit : la classe va pouvoir tre excute comme un
test JUnit. [@RunWith] est une annotation JUnit (ligne 9) alors que la classe [SpringJUnit4ClassRunner] est une classe
Spring (ligne 12) ;
lignes 26-27 : injection dans la classe de test d'une rfrence sur la couche [mtier] ;
beaucoup de tests ne sont que de simples tests visuels :
lignes 32-33 : liste des clients ;
lignes 35-36 : liste des mdecins ;
lignes 39-40 : liste des crneaux d'un mdecin ;
ligne 43 : liste des rendez-vous d'un mdecin ;
ligne 50 : ajout d'un nouveau rendez-vous. La mthode [ajouterRv] rend le rendez-vous avec une information
supplmentaire, sa cl primaire id ;
ligne 53 : on utilise cette cl primaire pour rechercher le rendez-vous en base ;

http://tahe.developpez.com

58/325

ligne 54 : on vrifie que le rendez-vous cherch et le rendez-vous trouv sont les mmes. On rappelle que la mthode
[equals] de l'entit [Rv] a t redfinie : deux rendez-vous sont gaux s'ils ont le mme id. Ici, cela nous montre que le
rendez-vous ajout a bien t mis en base ;
lignes 61-73 : on essaie d'ajouter une deuxime fois le mme rendez-vous. Cela doit tre rejet par le SGBD car on a une
contrainte d'unicit :
1. CREATE TABLE IF NOT EXISTS `rv` (
2.
`ID` bigint(20) NOT NULL AUTO_INCREMENT,
3.
`JOUR` date NOT NULL,
4.
`ID_CLIENT` bigint(20) NOT NULL,
5.
`ID_CRENEAU` bigint(20) NOT NULL,
6.
`VERSION` int(11) NOT NULL DEFAULT '0',
7.
PRIMARY KEY (`ID`),
8.
UNIQUE KEY `UNQ1_RV` (`JOUR`,`ID_CRENEAU`),
9.
KEY `FK_RV_ID_CRENEAU` (`ID_CRENEAU`),
10.
KEY `FK_RV_ID_CLIENT` (`ID_CLIENT`)
11.
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_swedish_ci AUTO_INCREMENT=60 ;

La ligne 8 ci-dessus indique que la combinaison [JOUR, ID_CRENEAU] doit tre unique, ce qui empche de mettre deux
rendez-vous le mme jour dans le mme crneau horaire.
ligne 73 : on vrifie qu'une exception s'est bien produite ;
ligne 77 : on demande l'agenda du mdecin pour lequel on vient d'ajouter un rendez-vous ;
ligne 79 : on vrifie que le rendez-vous ajout est bien prsent dans son agenda ;
ligne 82 : on supprime le rendez-vous ajout ;
ligne 84 : on va chercher en base le rendez-vous supprim ;
ligne 85 : on vrifie qu'on a rcupr un pointeur null, montrant par l que le rendez-vous cherch n'existe pas ;

L'excution du test russit :

2.10

Le programme console

Couche
[console]

Couche
[mtier]

Couche
[DAO]

Couche
[JPA]

Pilote
[JDBC]

SGBD

7 4
Spring

Le programme console est basique. Il illustre comment rcuprer une cl trangre :

http://tahe.developpez.com

59/325

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.

package rdvmedecins.boot;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import
import
import
import
import

rdvmedecins.config.DomainAndPersistenceConfig;
rdvmedecins.entities.Client;
rdvmedecins.entities.Creneau;
rdvmedecins.entities.Rv;
rdvmedecins.metier.IMetier;

public class Boot {


// le boot
public static void main(String[] args) {
// on prpare la configuration
SpringApplication app = new SpringApplication(DomainAndPersistenceConfig.class);
app.setLogStartupInfo(false);
// on la lance
ConfigurableApplicationContext context = app.run(args);
// mtier
IMetier mtier = context.getBean(IMetier.class);
try {
// ajouter un RV
Date jour = new Date();
System.out.println(String.format("Ajout d'un Rv le [%s] dans le crneau 1 pour le
client 1", new SimpleDateFormat("dd/MM/yyyy").format(jour)));
Client client = (Client) new Client().build(1L, 1L);
Creneau crneau = (Creneau) new Creneau().build(1L, 1L);
Rv rv = mtier.ajouterRv(jour, crneau, client);
System.out.println(String.format("Rv ajout = %s", rv));
// vrification
crneau = mtier.getCreneauById(1L);
long idMedecin = crneau.getIdMedecin();
display("Liste des rendez-vous", mtier.getRvMedecinJour(idMedecin, jour));
} catch (Exception ex) {
System.out.println("Exception : " + ex.getCause());
}
// fermeture du contexte Spring
context.close();
}

29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52. }

// mthode utilitaire - affiche les lments d'une collection


private static <T> void display(String message, Iterable<T> elements) {
System.out.println(message);
for (T element : elements) {
System.out.println(element);
}
}

Le programme ajoute un rendez-vous et ensuite vrifie qu'il a t ajout.

ligne 19 : la classe [SpringApplication] va exploiter la classe de configuration [DomainAndPersistenceConfig] ;


ligne 20 : suppression des logs de dmarrage de l'application ;
ligne 22 : la classe [SpringApplication] est excute. Elle rend un contexte Spring, --d la liste des beans enregistrs ;
ligne 24 : on rcupre une rfrence sur le bean implmentant l'interface [IMetier]. Il s'agit donc d'une rfrence sur la
couche [mtier] ;
lignes 27-31 : ajout d'un nouveau rendez-vous pour aujourd'hui, pour le client n1 dans le crneau n 1. Le client et le
crneau ont t crs de toute pice pour montrer que seuls les identifiants sont utiliss. On a initialis ici la version mais
on n'aurait pu mettre n'importe quoi. Elle n'est pas utilise ici ;

http://tahe.developpez.com

60/325

ligne 34 : on veut connatre le mdecin ayant le crneau n 1. Pour cela on a besoin d'aller en base chercher le crneau n
1. Parce qu'on est en mode [FetchType.LAZY], le mdecin n'est pas ramen avec le crneau. Cependant, on a pris soin de
prvoir un champ [idMedecin] dans l'entit [Creneau] pour rcuprer la cl primaire du mdecin ;
ligne 35 : on rcupre la primaire du mdecin ;
ligne 36 : on affiche la liste des rendez-vous du mdecin ;

Les rsultats console sont les suivants :


1.
2.
3.
4.

2.11

Ajout d'un Rv le [10/06/2014] dans le crneau 1 pour le client 1


Rv ajout = Rv[113, Tue Jun 10 16:51:01 CEST 2014, 1, 1]
Liste des rendez-vous
Rv[113, 2014-06-10, 1, 1]

Introduction Spring MVC

Couche
[web /
JSON]

Couche
[DAO]

Couche
[metier]

Couche
[JPA]

Pilote
[JDBC]

SGBD

7 4
Spring
Nous abordons maintenant la construction de la couche web. Celle-ci est principalement constitue de mthodes qui traitent des
URL prcises et rpondent avec une ligne de texte au format JSON (Javascript Object Notation). Cette couche web est une
interface web qu'on appelle parfois une API web. Nous allons implmenter cette interface avec Spring MVC, une autre branche de
l'cosystme Spring. Nous commenons par tudier l'un des guides trouvs sur [http://spring.io].

2.11.1

Le projet de dmonstration

en [1], nous importons l'un des guides Spring ;

http://tahe.developpez.com

61/325

2
6

3
4

en [2], nous choisissons l'exemple [Rest Service] ;


en [3], on choisit le projet Maven ;
en [4], on prend la version finale du guide ;
en [5], on valide ;
en [6], le projet import ;

Les services web accessibles via des URL standard et qui dlivrent du texte JSON sont souvent appels des services REST
(REpresentational State Transfer). Dans ce document, je me contenterai d'appeler le service que nous allons construire, un service
web / JSON. Un service est dit Restful s'il respecte certaines rgles. Je n'ai pas cherch respecter celles-ci.
Examinons maintenant le projet import, d'abord sa configuration Maven.

2.11.2

Configuration Maven

Le fichier [pom.xml] est le suivant :


1. <?xml version="1.0" encoding="UTF-8"?>
2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchemainstance"
3.
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven4.0.0.xsd">
4.
<modelVersion>4.0.0</modelVersion>
5.
6.
<groupId>org.springframework</groupId>
7.
<artifactId>gs-rest-service</artifactId>
8.
<version>0.1.0</version>
9.
10.
<parent>
11.
<groupId>org.springframework.boot</groupId>
12.
<artifactId>spring-boot-starter-parent</artifactId>
13.
<version>1.1.0.RELEASE</version>
14.
</parent>
15.
16.
<dependencies>
17.
<dependency>
18.
<groupId>org.springframework.boot</groupId>
19.
<artifactId>spring-boot-starter-web</artifactId>

http://tahe.developpez.com

62/325

20.
</dependency>
21.
<dependency>
22.
<groupId>com.fasterxml.jackson.core</groupId>
23.
<artifactId>jackson-databind</artifactId>
24.
</dependency>
25.
</dependencies>
26.
27.
<properties>
28.
<start-class>hello.Application</start-class>
29.
</properties>
30.
31.
<build>
32.
<plugins>
33.
<plugin>
34.
<artifactId>maven-compiler-plugin</artifactId>
35.
</plugin>
36.
<plugin>
37.
<groupId>org.springframework.boot</groupId>
38.
<artifactId>spring-boot-maven-plugin</artifactId>
39.
</plugin>
40.
</plugins>
41.
</build>
42.
43.
<repositories>
44.
<repository>
45.
<id>spring-releases</id>
46.
<url>http://repo.spring.io/release</url>
47.
</repository>
48.
</repositories>
49.
<pluginRepositories>
50.
<pluginRepository>
51.
<id>spring-releases</id>
52.
<url>http://repo.spring.io/release</url>
53.
</pluginRepository>
54.
</pluginRepositories>
55. </project>

lignes 10-14 : comme dans le projet [Spring Data], on trouve le projet parent [Spring Boot] ;
lignes 17-20 : l'artifact [spring-boot-starter-web] amne avec lui les bibliothques ncessaires un projet spring MVC. Il
amne en particulier avec lui un serveur Tomcat embarqu. C'est sur ce serveur que l'application sera excute ;
lignes 21-24 : la bibliothque Jackson gre le JSON : transformation d'un objet Java en chane JSON et inversement ;

Les bibliothques amenes par cette configuration sont trs nombreuses :

Ci-dessus on voit les trois archives du serveur Tomcat.

http://tahe.developpez.com

63/325

2.11.3

L'architecture d'un service Spring REST

Spring MVC implmente le modle d'architecture dit MVC (Modle Vue Contrleur) de la faon suivante :

Application web
couche [web]
2b
2a
Dispatcher
Servlet
Contrleurs/

Navigateur
4b

Actions

Vue1
Vue2

Modles

Vuen

couches
[mtier, DAO, JPA]

Donnes

2c

Le traitement d'une demande d'un client se droule de la faon suivante :


1. demande - les URL demandes sont de la forme http://machine:port/contexte/Action/param1/param2/....?p1=v1&p2=v2&... La
[Dispatcher Servlet] est la classe de Spring qui traite les URL entrantes. Elle "route" l'URL vers l'action qui doit la traiter. Ces
actions sont des mthodes de classes particulires appeles [Contrleurs]. Le C de MVC est ici la chane [Dispatcher Servlet,
Contrleur, Action]. Si aucune action n'a t configure pour traiter l'URL entrante, la servlet [Dispatcher Servlet] rpondra que
l'URL demande n'a pas t trouve (erreur 404 NOT FOUND) ;
2. traitement

l'action choisie peut exploiter les paramtres parami que la servlet [Dispatcher Servlet] lui a transmis. Ceux-ci peuvent
provenir de plusieurs sources :

du chemin [/param1/param2/...] de l'URL,

des paramtres [p1=v1&p2=v2] de l'URL,

de paramtres posts par le navigateur avec sa demande ;

dans le traitement de la demande de l'utilisateur, l'action peut avoir besoin de la couche [metier] [2b]. Une fois la
demande du client traite, celle-ci peut appeler diverses rponses. Un exemple classique est :

une page d'erreur si la demande n'a pu tre traite correctement

une page de confirmation sinon

l'action demande une certaine vue de s'afficher [3]. Cette vue va afficher des donnes qu'on appelle le modle de la
vue. C'est le M de MVC. L'action va crer ce modle M [2c] et demander une vue V de s'afficher [3] ;
3. rponse - la vue V choisie utilise le modle M construit par l'action pour initialiser les parties dynamiques de la rponse HTML
qu'elle doit envoyer au client puis envoie cette rponse.
Pour un service web / JSON, l'architecture prcdente est lgrement modifie :

Application web
couche [web]
2b
2a
Dispatcher
Servlet
Contrleurs/

Navigateur
4b

JSON

Modles

Actions
2c

couches
[mtier, DAO,
ORM]

Donnes

4a

2.11.4

en [4a], le modle qui est une classe Java est transform en chane JSON par une bibliothque JSON ;
en [4b], cette chane JSON est envoye au navigateur ;

Le contrleur C

http://tahe.developpez.com

64/325

L'application importe a le contrleur suivant :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.

package hello;

ligne 9 : l'annotation [@Controller] fait de la classe [GreetingController] un contrleur Spring, --d que ses mthodes
sont enregistres pour traiter des URL ;
ligne 15 : l'annotation [@RequestMapping] indique l'URL que traite la mthode, ici l'URL [/greeting]. Nous verrons
ultrieurement que cette URL peut tre paramtre et qu'il est possible de rcuprer ces paramtres ;
ligne 16 : l'annotation [@ResponseBody] indique que la mthode ne produit pas un modle pour une vue (JSP, JSF,
Thymeleaf, ...) qui sera envoye ensuite au navigateur client mais produit elle-mme la rponse faite au navigateur. Ici, elle
produit un objet de type [Greeting] (ligne 18). De faon non apparente ici, cet objet va d'abord tre transform en JSON
avant d'tre envoy au navigateur. C'est la prsence d'une bibliothque JSON dans les dpendances du projet qui fait que
Spring Boot va, par autoconfiguration, configurer le projet de cette faon ;
ligne 17 : la mthode [greeting] a un paramtre [String name]. L'annotation [@RequestParam(value = "name", required =
false, defaultValue = "World"] indique que ce paramtre doit tre initialis avec un paramtre nomm [name]
(@RequestParam(value = "name"). Celui-ci peut tre le paramtre d'un GET ou d'un POST. Ce paramtre n'est pas
obligatoire (required = false). Dans ce dernier cas, le paramtre [name] de la mthode sera initialis avec la valeur [World]
(defaultValue = "World").

import
import
import
import
import

java.util.concurrent.atomic.AtomicLong;
org.springframework.stereotype.Controller;
org.springframework.web.bind.annotation.RequestMapping;
org.springframework.web.bind.annotation.RequestParam;
org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();

@RequestMapping("/greeting")
public @ResponseBody
Greeting greeting(@RequestParam(value = "name", required = false, defaultValue = "World")
String name) {
18.
return new Greeting(counter.incrementAndGet(), String.format(template, name));
19.
}
20. }

2.11.5

Le modle M

Le modle M produit par la mthode prcdente est l'objet [Greeting] suivant :

1. package hello;
2.
3. public class Greeting {
4.

http://tahe.developpez.com

65/325

5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20. }

private final long id;


private final String content;
public Greeting(long id, String content) {
this.id = id;
this.content = content;
}
public long getId() {
return id;
}
public String getContent() {
return content;
}

La transformation JSON de cet objet crera la chane de caractres {"id":n,"content":"texte"}. Au final, la chane JSON
produite par la mthode du contrleur sera de la forme :
{"id":2,"content":"Hello, World!"}
ou
{"id":2,"content":"Hello, John!"}

2.11.6

Configuration du projet

Le projet est configur par la classe [Application] suivante :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.

package hello;

ligne 11 : curieusement cette classe est excutable avec une mthode [main] propre aux applications console. C'est bien le
cas. La classe [SpringApplication] de la ligne 12 va lancer le serveur Tomcat prsent dans les dpendances et dployer le
service REST dessus ;
ligne 4 : on voit que la classe [SpringApplication] appartient au projet [Spring Boot] ;
ligne 12 : le premier paramtre est la classe qui configure le projet, le second d'ventuels paramtres ;
ligne 8 : l'annotation [@EnableAutoConfiguration] demande Spring Boot de faire la configuration du projet ;
ligne 7 : l'annotation [@ComponentScan] fait que le dossier qui contient la classe [Application] va tre explor pour
rechercher les composants Spring. Un sera trouv, la classe [GreetingController] qui a l'annotation [@Controller] qui en
fait un composant Spring ;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
@EnableAutoConfiguration
public class Application {

public static void main(String[] args) {


SpringApplication.run(Application.class, args);
}

http://tahe.developpez.com

66/325

2.11.7

Excution du projet

Excutons le projet :

On obtient les logs console suivants :


____
_

__ _ _

1. /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \


2. ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
3. \\/ ___)| |_)| | | | | || (_| | ) ) ) )
4.
' |____| .__|_| |_|_| |_\__, | / / / /
5. =========|_|==============|___/=/_/_/_/
6. :: Spring Boot ::
(v1.1.0.RELEASE)
7.
8. 2014-06-11 14:31:36.435 INFO 11744 --- [
main] hello.Application
: Starting Application on Gportpers3 with PID 11744 (D:\Temp\wksSTS\gs-rest-servicecomplete\target\classes started by ST in D:\Temp\wksSTS\gs-rest-service-complete)
9. 2014-06-11 14:31:36.473 INFO 11744 --- [
main]
ationConfigEmbeddedWebApplicationContext : Refreshing
org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@7684af0b
: startup date [Wed Jun 11 14:31:36 CEST 2014]; root of context hierarchy
10. 2014-06-11 14:31:36.966 INFO 11744 --- [
main] o.s.b.f.s.DefaultListableBeanFactory
: Overriding bean definition for bean 'beanNameViewResolver': replacing [Root bean: class
[null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0;
autowireCandidate=true; primary=false;
factoryBeanName=org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$WhitelabelE
rrorViewConfiguration; factoryMethodName=beanNameViewResolver; initMethodName=null;
destroyMethodName=(inferred); defined in class path resource
[org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfigu
ration.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false;
autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false;
factoryBeanName=org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoCon
figurationAdapter; factoryMethodName=beanNameViewResolver; initMethodName=null;
destroyMethodName=(inferred); defined in class path resource
[org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapt
er.class]]
11. 2014-06-11 14:31:37.760 INFO 11744 --- [
main]
.t.TomcatEmbeddedServletContainerFactory : Server initialized with port: 8080
12. 2014-06-11 14:31:37.955 INFO 11744 --- [
main] o.apache.catalina.core.StandardService
: Starting service Tomcat
13. 2014-06-11 14:31:37.956 INFO 11744 --- [
main]
org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/7.0.54
14. 2014-06-11 14:31:38.053 INFO 11744 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]
: Initializing Spring embedded WebApplicationContext
15. 2014-06-11 14:31:38.054 INFO 11744 --- [ost-startStop-1] o.s.web.context.ContextLoader
: Root WebApplicationContext: initialization completed in 1584 ms
16. 2014-06-11 14:31:38.596 INFO 11744 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean
: Mapping servlet: 'dispatcherServlet' to [/]

http://tahe.developpez.com

67/325

17. 2014-06-11 14:31:38.598 INFO 11744 --- [ost-startStop-1]


o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
18. 2014-06-11 14:31:38.919 INFO 11744 --- [
main]
o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of
type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
19. 2014-06-11 14:31:39.125 INFO 11744 --- [
main]
s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped
"{[/greeting],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public
hello.Greeting hello.GreetingController.greeting(java.lang.String)
20. 2014-06-11 14:31:39.129 INFO 11744 --- [
main]
s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped
"{[/error],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public
org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>>
org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpSer
vletRequest)
21. 2014-06-11 14:31:39.130 INFO 11744 --- [
main]
s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped
"{[/error],methods=[],params=[],headers=[],consumes=[],produces=[text/html],custom=[]}" onto
public org.springframework.web.servlet.ModelAndView
org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.Htt
pServletRequest)
22. 2014-06-11 14:31:39.160 INFO 11744 --- [
main]
o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class
org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
23. 2014-06-11 14:31:39.160 INFO 11744 --- [
main]
o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type
[class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
24. 2014-06-11 14:31:39.448 INFO 11744 --- [
main] o.s.j.e.a.AnnotationMBeanExporter
: Registering beans for JMX exposure on startup
25. 2014-06-11 14:31:39.490 INFO 11744 --- [
main]
s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080/http
26. 2014-06-11 14:31:39.492 INFO 11744 --- [
main] hello.Application
: Started Application in 3.45 seconds (JVM running for 3.93)

ligne 12 : le serveur Tomcat dmarre sur le port 8080 (ligne 11) ;


ligne 16 : la servlet [DispatcherServlet] est prsente ;
ligne 19 : la mthode [GreetingController.greeting] a t dcouverte ;

Pour tester l'application web, on demande l'URL [http://localhost:8080/greeting] :

On reoit bien la chane JSON attendue. Il peut tre intressant de voir les enttes HTTP envoys par le serveur. Pour cela, on va
utiliser le plugin de Chrome appel [Advanced Rest Client] (cf Annexes) :

http://tahe.developpez.com

68/325

1
5

7
4

2.11.8

en [1], l'URL demande ;


en [2], la mthode GET est utilise ;
en [3], la rponse JSON ;
en [4], le serveur a indiqu qu'il envoyait une rponse au format JSON ;
en [5], on demande la mme URL mais cette fois-ci avec un POST ;
en [7], les informations sont envoyes au serveur sous la forme [urlencoded] ;
en [6], le paramtre name avec sa valeur ;
en [8], le navigateur indique au serveur qu'il lui envoie des informations [urlencoded] ;
en [9], la rponse JSON du serveur ;

Cration d'une archive excutable

Il est possible de crer une archive excutable en-dehors d'Eclipse. La configuration ncessaire est dans le fichier [pom.xml] :
1.
2.
3.
4.
5.
6.
7.

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>istia.st.Application</start-class>
<java.version>1.7</java.version>
</properties>
<build>

http://tahe.developpez.com

69/325

8.
<plugins>
9.
<plugin>
10.
<groupId>org.springframework.boot</groupId>
11.
<artifactId>spring-boot-maven-plugin</artifactId>
12.
</plugin>
13.
</plugins>
14. </build>

les lignes 9-12 dfinissent le plugin qui va crer l'archive excutable ;


la ligne 3 dfinit la classe excutable du projet ;

On procde ainsi :

3
1

en [1] : on excute une cible Maven ;


en [2] : il y a deux cibles (goals) : [clean] pour supprimer le dossier [target] du projet Maven, [package] pour le rgnrer ;
en [3] : le dossier [target] gnr, le sera dans ce dossier ;
en [4] : on gnre la cible ;

Dans les logs qui apparaissent dans la console, il est important de voir apparatre le plugin [spring-boot-maven-plugin]. C'est
lui qui gnre l'archive excutable.
[INFO] --- spring-boot-maven-plugin:1.1.0.RELEASE:repackage (default) @ gs-rest-service ---

Avec une console, on se place dans le dossier gnr :


1.
2.
3.
4.
5.
6.
7.
8.
9.

D:\Temp\wksSTS\gs-rest-service-complete\target>dir
...
11/06/2014 15:30
<DIR>
classes
11/06/2014 15:30
<DIR>
generated-sources
11/06/2014 15:30
11 073 572 gs-rest-service-0.1.0.jar
11/06/2014 15:30
3 690 gs-rest-service-0.1.0.jar.original
11/06/2014 15:30
<DIR>
maven-archiver
11/06/2014 15:30
<DIR>
maven-status
...

ligne 5 : l'archive gnre ;

http://tahe.developpez.com

70/325

Cette archive est excute de la faon suivante :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.

D:\Temp\wksSTS\gs-rest-service-complete\target>java -jar gs-rest-service-0.1.0.jar


.
____
_
__ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot ::
(v1.1.0.RELEASE)
2014-06-11 15:32:47.088 INFO 4972 --- [
main] hello.Application
: Starting Application on Gportpers3 with PID 4972 (D:\Temp\wk
sSTS\gs-rest-service-complete\target\gs-rest-service-0.1.0.jar started by ST in
D:\Temp\wksSTS\gs-rest-service-complete\target)
...

Maintenant que l'application web est lance, on peut l'interroger avec un navigateur :

2.11.9

Dployer l'application sur un serveur Tomcat

Si Spring Boot s'avre trs pratique en mode dveloppement, il est probable qu'une application en production sera dploye sur un
vrai serveur Tomcat. Voici comment procder :
Modifier le fichier [pom.xml] de la faon suivante :
1. <?xml version="1.0" encoding="UTF-8"?>
2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchemainstance"
3.
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven4.0.0.xsd">
4.
<modelVersion>4.0.0</modelVersion>
5.
6.
<groupId>org.springframework</groupId>
7.
<artifactId>gs-rest-service</artifactId>
8.
<version>0.1.0</version>
9.
<packaging>war</packaging>
10.
11.
<parent>
12.
<groupId>org.springframework.boot</groupId>
13.
<artifactId>spring-boot-starter-parent</artifactId>
14.
<version>1.1.0.RELEASE</version>
15.
</parent>
16.
17.
<dependencies>
18.
<dependency>
19.
<groupId>org.springframework.boot</groupId>
20.
<artifactId>spring-boot-starter-web</artifactId>
21.
</dependency>
22.
<dependency>
23.
<groupId>com.fasterxml.jackson.core</groupId>
24.
<artifactId>jackson-databind</artifactId>
25.
</dependency>

http://tahe.developpez.com

71/325

26.
<dependency>
27.
<groupId>org.springframework.boot</groupId>
28.
<artifactId>spring-boot-starter-tomcat</artifactId>
29.
<scope>provided</scope>
30.
</dependency>
31.
</dependencies>
32.
33.
<properties>
34.
<start-class>hello.Application</start-class>
35.
</properties>
36. ....
37. </project>

Les modifications sont faire deux endroits :

ligne 9 : il faut indiquer qu'on va gnrer une archive war (Web ARchive) ;
lignes 26-30 : il faut ajouter une dpendance sur l'artifact [spring-boot-starter-tomcat]. Cet artifact amne toutes les classes
de Tomcat dans les dpendances du projet ;
ligne 29 : cet artifact est [provided], --d que les archives correspondantes ne seront pas places dans le war gnr. En
effet, ces archives seront trouves sur le serveur Tomcat sur lequel s'excutera l'application ;

Il faut par ailleurs configurer l'application web. En l'absence de fichier [web.xml], cela se fait avec une classe hritant de
[SpringBootServletInitializer] :

La classe [ApplicationInitializer] est la suivante :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.

package hello;

ligne 6 : la classe [ApplicationInitializer] tend la classe [SpringBootServletInitializer] ;


ligne 9 : la mthode [configure] est redfinie (ligne 8) ;
ligne 10 : on fournit la classe qui configure le projet ;

import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;
public class ApplicationInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
}

Pour excuter le projet, on peut procder ainsi :

http://tahe.developpez.com

72/325

en [1], on excute le projet sur l'un des serveurs enregistrs dans l'IDE Eclipse ;
en [2], on choisit [tc Server Developer] qui est prsent par dfaut. C'est une variante de Tomcat ;

Ceci fait, on peut demander l'URL [http://localhost:8080/gs-rest-service/greeting/?name=Mitchell] dans un navigateur :

Nous savons dsormais gnrer une archive war. Par la suite, nous continuerons travailler avec Spring Boot et son archive jar
excutable.

2.11.10

Crer un nouveau projet web

Pour construire un nouveau projet web, on peut procder de la faon suivante :

6
1
3
4

5
2

en [1] : File / New / Spring Starter Project


en [2] : slectionner [Web]. On ne slectionne pas de bibliothques de vues car dans un service web / JSON, il n'y a pas de
vues ;
le projet cr va tre un projet Maven. En [3], on met le groupe de l'artifact Maven qui va tre cr, en [4], le nom de
l'artifact ;
en [5], on met le nom d'un package o Spring va placer la classe de configuration du projet ;

http://tahe.developpez.com

73/325

2.12

en [6], on donne un nom au projet Eclipse peut tre diffrent de [4] ;

La couche [web]
Application web
couche [web]
2b
2a
Dispatcher
Servlet
Contrleurs/

Navigateur
4b

JSON

Modles

Actions

couches
[mtier, DAO, JPA]

Donnes

2c

4a

Nous allons construire la couche web en plusieurs tapes :

2.12.1

tape 1 : une couche web oprationnelle sans authentification ;


tape 2 : mise en place de l'authentification avec Spring Security ;
tape 3 : mise en place des CORS [Cross-origin resource sharing (CORS) is a mechanism that allows many resources
(e.g. fonts, JavaScript, etc.) on a web page to be requested from another domain outside the domain the resource originated
from. (Wikipedia)]. Le client de notre service web sera un client web Angular qui n'appartiendra pas ncessairement au
mme domaine que notre service web. Par dfaut, il ne peut alors pas y accder sauf si le service web l'y autorise. Nous
verrons comment ;

Configuration Maven

Le fichier [pom.xml] du projet est le suivant :


1. <modelVersion>4.0.0</modelVersion>
2.
<groupId>istia.st.spring4.mvc</groupId>
3.
<artifactId>rdvmedecins-webapi-v1</artifactId>
4.
<version>0.0.1-SNAPSHOT</version>

http://tahe.developpez.com

74/325

5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.

2.12.2

<name>rdvmedecins-webapi-v1</name>
<description>Gestion de RV Mdecins</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.0.0.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>istia.st.spring4.rdvmedecins</groupId>
<artifactId>rdvmedecins-metier-dao</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>

lignes 7-11 : le projet Maven parent ;


lignes 13-16 : les dpendances pour un projet Spring MVC ;
lignes 17-21 : les dpendances sur le projet des couches [mtier, DAO, JPA] ;

L'interface du service web


Application web
couche [web]
2b
2a
Dispatcher
Servlet
Contrleurs/

Navigateur
4b

JSON

Actions

Modles

couches
[mtier, DAO, JPA]

Donnes

2c

4a

en [1], ci-dessus, le navigateur ne peut demander qu'un nombre restreint d'URL avec une syntaxe prcise ;
en [4], il reoit une rponse JSON ;

Les rponses de notre service web auront toutes la mme forme correspondant la transformation JSON d'un objet de type
[Reponse] suivant :
1. package rdvmedecins.web.models;
2.
3. public class Reponse {
4.
5.
// ----------------- proprits
6.
// statut de l'opration
7.
private int status;
8.
// la rponse JSON
9.
private Object data;
10.
11.
// ---------------constructeurs
12.
public Reponse() {
13.
}
14.
15.
public Reponse(int status, Object data) {
16.
this.status = status;
17.
this.data = data;
18.
}
19.
20.
// mthodes
21.
public void incrStatusBy(int increment) {
22.
status += increment;

http://tahe.developpez.com

75/325

23.
}
24.
25.
// ----------------------getters et setters
26. ...
27. }

ligne 7 : code d'erreur de la rponse 0: OK, autre chose : KO ;


ligne 9 : le corps de la rponse ;

Nous prsentons maintenant les copies d'cran qui illustrent l'interface du service web / JSON :
Liste de tous les patients du cabinet mdical [/getAllClients]

Liste de tous les mdecins du cabinet mdical [/getAllMedecins]

Liste des crneaux horaires d'un mdecin [/getAllCreneaux/{idMedecin}]

http://tahe.developpez.com

76/325

Liste des rendez-vous d'un mdecin [/getRvMedecinJour/{idMedecin}/{aaaa-mm-jj}

Agenda d'un mdecin [/getAgendaMedecinJour/{idMedecin}/{aaaa-mm-jj}]

http://tahe.developpez.com

77/325

Pour ajouter / supprimer un rendez-vous nous utilisons le complment Chrome [Advanced Rest Client] car ces oprations se font
avec un POST.
Ajouter un rendez-vous [/ajouterRv]
0
1

en [0], l'URL du service web ;


en [1], la mthode POST est utilise ;
en [2], le texte JSON des informations tarnsmises au service web sous la forme {jour, idClient, idCreneau} ;
en [3], le client prcise au service web qu'il lui envoie des informations au format JSON ;

http://tahe.developpez.com

78/325

La rponse est alors la suivante :

en [4] : le client envoie l'entte signifiant que les donnes qu'il envoie sont au format JSON ;
en [5] : le service web rpond qu'il envoie lui aussi du JSON ;
en [6] : la rponse JSON du service web. Le champ [data] contient la forme JSON du rendez-vous ajout ;

La prsence du nouveau rendez-vous peut tre vrifi :

Supprrimer un rendez-vous [/supprimerRv]

http://tahe.developpez.com

79/325

1
2

en [1], l'URL du service web ;


en [2], la mthode POST est utilise;
en [3], le texte JSON des informations transmises au service web sous la forme {idRv} ;
en [4], le client prcise au service web qu'il lui envoie des informations JSON ;

La rponse est alors la suivante :

en [5] : le champ [status] est 0, montrant par l que l'opration a russi ;

La suppression du rendez-vous peut tre vrifie :

http://tahe.developpez.com

80/325

Ci-dessus, le rendez-vous du patient [Mme GERMAN] n'est plus prsent.


Le service web permet galement de rcuprer des entits via leur identifiant :

Toutes ces URL sont traites par le contrleur [RdvMedecinsController] que nous prsentons maintenant.

http://tahe.developpez.com

81/325

2.12.3

Le squelette du contrleur [RdvMedecinsController]

Le contrleur [RdvMedecinsController] est le suivant :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.

package rdvmedecins.web.controllers;
import java.text.ParseException;
...
@RestController
public class RdvMedecinsController {
@Autowired
private ApplicationModel application;
private List<String> messages;
@PostConstruct
public void init() {
// messages d'erreur de l'application
messages = application.getMessages();
}
// liste des mdecins
@RequestMapping(value = "/getAllMedecins", method = RequestMethod.GET)
public Reponse getAllMedecins() {
...
}
// liste des clients
@RequestMapping(value = "/getAllClients", method = RequestMethod.GET)
public Reponse getAllClients() {
...
}
// liste des crneaux d'un mdecin
@RequestMapping(value = "/getAllCreneaux/{idMedecin}", method = RequestMethod.GET)
public Reponse getAllCreneaux(@PathVariable("idMedecin") long idMedecin) {
...
}
// liste des rendez-vous d'un mdecin
@RequestMapping(value = "/getRvMedecinJour/{idMedecin}/{jour}", method = RequestMethod.GET)
public Reponse getRvMedecinJour(@PathVariable("idMedecin") long idMedecin,
@PathVariable("jour") String jour) {
...
}
@RequestMapping(value = "/getClientById/{id}", method = RequestMethod.GET)
public Reponse getClientById(@PathVariable("id") long id) {
...
}
@RequestMapping(value = "/getMedecinById/{id}", method = RequestMethod.GET)
public Reponse getMedecinById(@PathVariable("id") long id) {
...

http://tahe.developpez.com

82/325

52.
}
53.
54.
@RequestMapping(value = "/getRvById/{id}", method = RequestMethod.GET)
55.
public Reponse getRvById(@PathVariable("id") long id) {
56. ...
57.
}
58.
59.
@RequestMapping(value = "/getCreneauById/{id}", method = RequestMethod.GET)
60.
public Reponse getCreneauById(@PathVariable("id") long id) {
61. ...
62.
}
63.
64.
@RequestMapping(value = "/ajouterRv", method = RequestMethod.POST, consumes =
"application/json; charset=UTF-8")
65.
public Reponse ajouterRv(@RequestBody PostAjouterRv post) {
66. ...
67.
}
68.
69.
@RequestMapping(value = "/supprimerRv", method = RequestMethod.POST, consumes =
"application/json; charset=UTF-8")
70.
public Reponse supprimerRv(@RequestBody PostSupprimerRv post) {
71. ...
72.
}
73.
74.
@RequestMapping(value = "/getAgendaMedecinJour/{idMedecin}/{jour}", method =
RequestMethod.GET)
75.
public Reponse getAgendaMedecinJour(
76.
@PathVariable("idMedecin") long idMedecin,
77.
@PathVariable("jour") String jour) {
78. ...
79.
}

80. }

ligne 6 : l'annotation [@RestController] fait de la classe [RdvMedecinsController] un contrleur Spring. Par ailleurs, elle
entrane galement que les mthodes traitant les URL vont gnrer une rponse qui sera automatiquement transforme en
JSON ;
lignes 9-10 : un objet de type [ApplicationModel] sera inject ici par Spring ;
ligne 13 : l'annotation [@PostConstruct] tague une mthode excuter juste aprs l'instanciation de la classe. Lorsqu'elle
celle-ci s'excute, les objets injects par Spring sont disponibles ;
toutes les mthodes rendent un objet de type [Reponse] suivant :
1. package rdvmedecins.web.models;
2.
3. public class Reponse {
4.
5.
// ----------------- proprits
6.
// statut de l'opration
7.
private int status;
8.
// la rponse
9.
private Object data;
10.
...
11.
}

Cet objet est srialis en JSON avant d'tre envoy au navigateur client ;

ligne 20 : l'annotation [@RequestMapping] fixe les conditions d'appel de la mthode. Ici la mthode traite une demande
GET de l'URL [/getAllMedecins]. Si cette URL tait demande par un POST, elle serait refuse et Spring MVC enverrait
un code HTTP d'erreur au client web ;
ligne 32 : l'URL est paramtre par {idMedecin}. Ce paramtre est rcupr avec l'annotation [@PathVariable] ligne 33 ;
ligne 33 : l'unique paramtre [long idMedecin] reoit sa valeur du paramtre {idMedecin} de l'URL
[@PathVariable("idMedecin")]. Le paramtre dans l'URL et celui de la mthode peuvent porter des noms diffrents. Il faut
noter ici que [@PathVariable("idMedecin")] est de type String (toute l'URL est un String) alors que le paramtre [long
idMedecin] est de type [long]. Le changement de type est fait automatiquement. Un code d'erreur HTTP est renvoy si ce
changement de type choue ;

http://tahe.developpez.com

83/325

ligne 65 : l'annotation [@RequestBody] dsigne le corps de la requte. Dans une requte GET, il n'y a quasiment jamais
de corps (mais il est possible d'en mettre un). Dans une requte POST, il y en a le plus souvent (mais il est possible de ne
pas en mettre). Pour l'URL [ajouterRv], le client web envoie dans son POST la chane JSON suivante :
{"jour":"2014-06-12", "idClient":3, "idCreneau":7}

La syntaxe [@RequestBody PostAjouterRv post] (ligne 65) ajoute au fait que la mthode attend du JSON [consumes =
"application/json; charset=UTF-8"] ligne 64 va faire que la chane JSON envoye par le client web va tre dsrialise en
un objet de type [PostAjouter]. Celui-ci est le suivant :
1. package rdvmedecins.web.models;
2.
3. public class PostAjouterRv {
4.
5.
// donnes du post
6.
private String jour;
7.
private long idClient;
8.
private long idCreneau;
9.
10.
// getters et setters
11.
...
12.
}

L galement, les changements de type ncessaires auront lieu automatiquement ;


lignes 69-70, on trouve un mcanisme similaire pour l'URL [/supprimerRv]. La chane JSON poste est la suivante :
{"idRv":116}

et le type [PostSupprimerRv] le suivant :


1. package rdvmedecins.web.models;
2.
3. public class PostSupprimerRv {
4.
5.
// donnes du post
6.
private long idRv;
7.
8.
// getters et setters
9.
...
10.
}

2.12.4

Les modles du service web

Nous avons dj prsent les modles [Reponse, PostAjouterRv, PostSupprimerRv]. Le modle [ApplicationModel] est le suivant :
1.
2.
3.
4.
5.
6.
7.

package rdvmedecins.web.models;
import java.util.Date;
...
@Component
public class ApplicationModel implements IMetier {

http://tahe.developpez.com

84/325

8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.

// la couche [mtier]
@Autowired
private IMetier mtier;
// donnes provenant de la couche [mtier]
private List<Medecin> mdecins;
private List<Client> clients;
// messages d'erreur
private List<String> messages;
@PostConstruct
public void init() {
// on rcupre les mdecins et les clients
try {
mdecins = mtier.getAllMedecins();
clients = mtier.getAllClients();
} catch (Exception ex) {
messages = Static.getErreursForException(ex);
}
}
// getter
public List<String> getMessages() {
return messages;
}
// ------------------------- interface couche [mtier]
@Override
public List<Client> getAllClients() {
return clients;
}
@Override
public List<Medecin> getAllMedecins() {
return mdecins;
}
@Override
public List<Creneau> getAllCreneaux(long idMedecin) {
return mtier.getAllCreneaux(idMedecin);
}
@Override
public List<Rv> getRvMedecinJour(long idMedecin, Date jour) {
return mtier.getRvMedecinJour(idMedecin, jour);
}
@Override
public Client getClientById(long id) {
return mtier.getClientById(id);
}
@Override
public Medecin getMedecinById(long id) {
return mtier.getMedecinById(id);
}
@Override
public Rv getRvById(long id) {
return mtier.getRvById(id);
}
@Override
public Creneau getCreneauById(long id) {
return mtier.getCreneauById(id);
}

http://tahe.developpez.com

85/325

75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91. }

@Override
public Rv ajouterRv(Date jour, Creneau creneau, Client client) {
return mtier.ajouterRv(jour, creneau, client);
}
@Override
public void supprimerRv(Rv rv) {
mtier.supprimerRv(rv);
}
@Override
public AgendaMedecinJour getAgendaMedecinJour(long idMedecin, Date jour) {
return mtier.getAgendaMedecinJour(idMedecin, jour);
}

ligne 6 : l'annotation [@Component] fait de la classe [ApplicationModel] un composant Spring. Comme tous les
composants Spring vus jusqu'ici ( l'exception de @Controller), un seul objet de ce type sera instanci (singleton) ;
ligne 7 : la classe [ApplicationModel] implmente l'interface [IMetier] ;
lignes 10-11 : une rfrence sur la couche [mtier] est injecte par Spring ;
ligne 19 : l'annotation [@PostConstruct] fait que la mthode [init] va tre excute juste aprs l'instanciation de la classe
[ApplicationModel] ;
lignes 23-24 : on rcupre les listes de mdecins et de clients auprs de la couche [mtier] ;
ligne 26 : si une exception se produit, on stocke les messages de la pile d'exceptions dans le champ de la ligne 17 ;

La classe [ApplicationModel] va nous servir deux choses :

de cache pour stocker les listes de mdecins et de patients (clients) ;

d'interface unique pour les contrleurs ;


L'architecture de la couche web volue comme suit :

Application web
couche [web]
1

2a

Dispatcher
Servlet
3

Navigateur
4b

JSON

Contrleurs/
Actions

Modles

2c

2b
couches
[Application [mtier,
DAO,
Model]
JPA]

Donnes

4a

en [2b], les mthodes du ou des contrleurs communiquent avec le singleton [ApplicationModel] ;

Cette stratgie amne de la souplesse quant la gestion du cache. Actuellement les crneaux horaires des mdecins ne sont pas mis
en cache. Pour les y mettre, il suffit de modifier la classe [ApplicationModel]. Cela n'a aucun impact sur le contrleur qui continuera
utiliser la mthode [List<Creneau> getAllCreneaux(long idMedecin)] comme il le faisait auparavant. C'est l'implmentation de
cette mthode dans [ApplicationModel]qui sera change.

2.12.5

La classe Static

La classe [Static] regroupe un ensemble de mthodes statiques utilitaires qui n'ont pas d'aspect " mtier " ou " web " :

http://tahe.developpez.com

86/325

Son code est le suivant :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.

package rdvmedecins.web.helpers;

ligne 12 : la mthode [Static.getErreursForException] qui a t utilise (ligne 8 ci-dessous) dans la mthode [init] de la
classe [ApplicationModel] :

import java.text.SimpleDateFormat;
...
public class Static {
public Static() {
}
// liste des messages d'erreur d'une exception
public static List<String> getErreursForException(Exception exception) {
// on rcupre la liste des messages d'erreur de l'exception
Throwable cause = exception;
List<String> erreurs = new ArrayList<String>();
while (cause != null) {
erreurs.add(cause.getMessage());
cause = cause.getCause();
}
return erreurs;
}
// mappers Object --> Map
// -------------------------------------------------------....
}

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.

@PostConstruct
public void init() {
// on rcupre les mdecins et les clients
try {
mdecins = mtier.getAllMedecins();
clients = mtier.getAllClients();
} catch (Exception ex) {
messages = Static.getErreursForException(ex);
}
}

La mthode construit un objet [List<String>] avec les messages d'erreur [exception.getMessage()] d'une exception
[exception] et de celles qu'elle contient [exception.getCause()].
La classe [Static] contient d'autres mthodes utilitaires sur lesquelles nous reviendrons lorsqe nous les rencontrerons.
Nous allons maintenant dtailler le traitement des URL du service web. Trois classes principales sont en jeu dans ce traitement :

le contrleur [RdvMedecinsController] ;

la classe de mthodes utilitaires [Static] ;

la classe de cache [ApplicationModel] ;

http://tahe.developpez.com

87/325

2.12.6

La mthode [init] du contrleur

Le contrleur [RdvMedecinsController] (cf page 82) a une mthode [init] qui est excute juste aprs son instanciation :
1.
2.
3.
4.
5.
6.
7.
8.
9. }

@Autowired
private ApplicationModel application;
private List<String> messages;
@PostConstruct
public void init() {
// messages d'erreur de l'application
messages = application.getMessages();

ligne 8 : les messages d'erreur stocks dans l'application cache [ApplicationModel] sont mmoriss en local dans le champ
de la ligne 3. Cela va permettre aux mthodes de savoir si l'application s'est initialise correctement.

2.12.7

L'URL [/getAllMedecins]

L'URL [/getAllMedecins] est traite par la mthode suivante du contrleur [RdvMedecinsController] :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14. }

// liste des mdecins


@RequestMapping(value = "/getAllMedecins", method = RequestMethod.GET)
public Reponse getAllMedecins() {
// tat de l'application
if (messages != null) {
return new Reponse(-1, messages);
}
// liste des mdecins
try {
return new Reponse(0, application.getAllMedecins());
} catch (Exception e) {
return new Reponse(1, Static.getErreursForException(e));
}

ligne 5 : on regarde si l'application s'est correctement initialise (messages==null). Si ce n'est pas le cas, on renvoie une
rponse avec status=-1 et data=messages ;
ligne 10 : sinon on renvoie la liste des mdecins avec un status gal 0. La mthode [application.getAllMedecins()] ne lance
pas d'exception car elle se contente de rendre une liste qui est en cache. Nanmoins on gardera cette gestion d'exception
pour le cas o les mdecins ne seraient plus mis en cache ;

Nous n'avons pas encore illustr le cas o l'application s'est mal initialise. Arrtons le SGBD MySQL5, lanons le service web puis
demandons l'URL [/getAllMedecins] :

http://tahe.developpez.com

88/325

On obtient bien une erreur. Dans un contexte normal, on obtient la vue suivante :

2.12.8

L'URL [/getAllClients]

L'URL [/getAllClients] est traite par la mthode suivante du contrleur [RdvMedecinsController] :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14. }

// liste des clients


@RequestMapping(value = "/getAllClients")
public Reponse getAllClients() {
// tat de l'application
if (messages != null) {
return new Reponse(-1, messages);
}
// liste des clients
try {
return new Reponse(0, application.getAllClients());
} catch (Exception e) {
return new Reponse(1, Static.getErreursForException(e));
}

Elle est analogue la mthode [getAllMedecins] dj tudie. Les rsultats obtenus sont les suivants :

http://tahe.developpez.com

89/325

2.12.9

L'URL [/getAllCreneaux/{idMedecin}]

L'URL [/getAllCreneaux/{idMedecin}] est traite par la mthode suivante du contrleur [RdvMedecinsController] :


1. // liste des crneaux d'un mdecin
2.
@RequestMapping(value = "/getAllCreneaux/{idMedecin}", method = RequestMethod.GET)
3.
public Reponse getAllCreneaux(@PathVariable("idMedecin") long idMedecin) {
4.
// tat de l'application
5.
if (messages != null) {
6.
return new Reponse(-1, messages);
7.
}
8.
// on rcupre le mdecin
9.
Reponse rponse = getMedecin(idMedecin);
10.
if (rponse.getStatus() != 0) {
11.
return rponse;
12.
}
13.
Medecin mdecin = (Medecin) rponse.getData();
14.
// crneaux du mdecin
15.
List<Creneau> crneaux = null;
16.
try {
17.
crneaux = application.getAllCreneaux(mdecin.getId());
18.
} catch (Exception e1) {
19.
return new Reponse(3, Static.getErreursForException(e1));
20.
}
21.
// on rend la rponse
22.
return new Reponse(0, Static.getListMapForCreneaux(crneaux));
23.
}

ligne 9 : le mdecin identifi par le paramtre [id] est demand une mthode locale :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15. }

private Reponse getMedecin(long id) {


// on rcupre le mdecin
Medecin mdecin = null;
try {
mdecin = application.getMedecinById(id);
} catch (Exception e1) {
return new Reponse(1, Static.getErreursForException(e1));
}
// mdecin existant ?
if (mdecin == null) {
return new Reponse(2, null);
}
// ok
return new Reponse(0, mdecin);

On revient de cette mthode avec un status dans [0,1,2]. Revenons au code de la mthode [getAllCreneaux] :
lignes 10-12 : si status!=0, on rend immdiatement la rponse ;
ligne 13 : on rcupre le mdecin ;
ligne 17 : on rcupre les crneaux de ce mdecin ;
ligne 22 : on envoie comme rponse un objet [Static.getListMapForCreneaux(crneaux)] ;

Rappelons la dfinition de la classe [Creneau] :


1. @Entity
2. @Table(name = "creneaux")
3. public class Creneau extends AbstractEntity {
4.
5.
private static final long serialVersionUID = 1L;
6.
// caractristiques d'un crneau de RV
7.
private int hdebut;
8.
private int mdebut;
9.
private int hfin;
10.
private int mfin;
11.
12.
// un crneau est li un mdecin

http://tahe.developpez.com

90/325

13.
@ManyToOne(fetch = FetchType.LAZY)
14.
@JoinColumn(name = "id_medecin")
15.
private Medecin medecin;
16.
17.
// cl trangre
18.
@Column(name = "id_medecin", insertable = false, updatable = false)
19.
private long idMedecin;
20. ...

21. }

ligne 13 : le mdecin est cherch en mode [FetchType.LAZY] ;

Rappelons la requte JPQL qui implmente la mthode [getAllCreneaux] dans la couche [DAO] :
@Query("select c from Creneau c where c.medecin.id=?1")

La notation [c.medecin.id] force la jointure entre les tables [CRENEAUX] et [MEDECINS]. Aussi la requte ramne-t-elle tous les
crneaux du mdecin avec dans chacun d'eux le mdecin. Lorsqu'on srialize en JSON ces crneaux, on voit apparatre la chane
JSON du mdecin dans chacun d'eux. C'est inutile. Aussi plutt que de srialiser un objet [Creneau], on va srialiser un objet [Map]
dans lequel on ne mettra que les champs dsirs.
Revenons au code tudi initialement :
1. // on rend la rponse
2. return new Reponse(0, Static.getListMapForCreneaux(crneaux));

La mthode [Static.getListMapForCreneaux] est la suivante :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10. }

// List<Creneau> --> List<Map>


public static List<Map<String, Object>> getListMapForCreneaux(List<Creneau> crneaux) {
// liste de dictionnaires <String,Object>
List<Map<String, Object>> liste = new ArrayList<Map<String, Object>>();
for (Creneau crneau : crneaux) {
liste.add(Static.getMapForCreneau(crneau));
}
// on rend la liste
return liste;

et la mthode [Static.getMapForCreneau] est la suivante :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16. }

// Creneau --> Map


public static Map<String, Object> getMapForCreneau(Creneau crneau) {
// qq chose faire ?
if (crneau == null) {
return null;
}
// dictionnaire <String,Object>
Map<String, Object> hash = new HashMap<String, Object>();
hash.put("id", crneau.getId());
hash.put("hDebut", crneau.getHdebut());
hash.put("mDebut", crneau.getMdebut());
hash.put("hFin", crneau.getHfin());
hash.put("mFin", crneau.getMfin());
// on rend le dictionnaire
return hash;

ligne 8 : on cre un dictionnaire ;


lignes 9-13 : on y met les champs qu'on veut garder dans la chane JSON. Le champ [medecin] n'y est pas ;
ligne 15 : on rend ce dictionnaire ;

Les rsultats obtenus sont les suivants :

http://tahe.developpez.com

91/325

ou bien ceux-ci si le crneau n'existe pas :

ou bien ceux-ci en cas d'erreur d'accs la base :

2.12.10

L'URL [/getRvMedecinJour/{idMedecin}/{jour}]

L'URL [/getRvMedecinJour/{idMedecin}/{jour}] est traite par la mthode suivante du contrleur [RdvMedecinsController] :


1. // liste des rendez-vous d'un mdecin
2.
@RequestMapping(value = "/getRvMedecinJour/{idMedecin}/{jour}", method = RequestMethod.GET)
3.
public Reponse getRvMedecinJour(@PathVariable("idMedecin") long idMedecin,
@PathVariable("jour") String jour) {
4.
// tat de l'application

http://tahe.developpez.com

92/325

5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32. }

if (messages != null) {
return new Reponse(-1, messages);
}
// on vrifie la date
Date jourAgenda = null;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setLenient(false);
try {
jourAgenda = sdf.parse(jour);
} catch (ParseException e) {
return new Reponse(3, null);
}
// on rcupre le mdecin
Reponse rponse = getMedecin(idMedecin);
if (rponse.getStatus() != 0) {
return rponse;
}
Medecin mdecin = (Medecin) rponse.getData();
// liste de ses rendez-vous
List<Rv> rvs = null;
try {
rvs = application.getRvMedecinJour(mdecin.getId(), jourAgenda);
} catch (Exception e1) {
return new Reponse(4, Static.getErreursForException(e1));
}
// on rend la rponse
return new Reponse(0, Static.getListMapForRvs(rvs));

ligne 31 : on rend un objet List<Map<String,Object>> au lieu d'un objet List<Rv>. Rappelons la dfinition de la
classe [Rv] :

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.

@Entity
@Table(name = "rv")
public class Rv extends AbstractEntity {
private static final long serialVersionUID = 1L;

ligne 11 : le client est recherch avec le mode [FetchType.LAZY] ;


ligne 18 : le crneau est recherch avec le mode [FetchType.LAZY] ;

// caractristiques d'un Rv
@Temporal(TemporalType.DATE)
private Date jour;
// un rv est li un client
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "id_client")
private Client client;
// un rv est li un crneau
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "id_creneau")
private Creneau creneau;
// cls trangres
@Column(name = "id_client", insertable = false, updatable = false)
private long idClient;
@Column(name = "id_creneau", insertable = false, updatable = false)
private long idCreneau;
...

28. }

Rappelons la requte JPQL qui va chercher les rendez-vous :

http://tahe.developpez.com

93/325

@Query("select rv from Rv rv left join fetch rv.client c left join fetch rv.creneau cr where
cr.medecin.id=?1 and rv.jour=?2")

De jointures sont faites explicitement pour ramener les champs [client] et [creneau]. Par ailleurs cause de la jointure
[cr.medecin.id=?1], nous aurons galement le mdecin. Le mdecin va donc apparatre dans la chane JSON de chaque rendez-vous.
Or cette information duplique est en outre inutile. Revenons au code de la mthode :

ligne 31 : nous construisons nous mmes le dictionnaire srialiser en JSON ;

Le dictionnaire construit pour un rendez-vous est le suivant :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14. }

// Rv --> Map
public static Map<String, Object> getMapForRv(Rv rv) {
// qq chose faire ?
if (rv == null) {
return null;
}
// dictionnaire <String,Object>
Map<String, Object> hash = new HashMap<String, Object>();
hash.put("id", rv.getId());
hash.put("client", rv.getClient());
hash.put("creneau", getMapForCreneau(rv.getCreneau()));
// on rend le dictionnaire
return hash;

ligne 11 : nous reprenons le dictionnaire de l'objet [Creneau] que nous avons prsent prcdemment ;

Les rsultats obtenus sont les suivants :

ou encore ceux-ci avec un jour incorrect :

ou encore ceux-ci avec un mdecin incorrect :

http://tahe.developpez.com

94/325

2.12.11

L'URL [/getAgendaMedecinJour/{idMedecin}/{jour}]

L'URL [/getAgendaMedecinJour/{idMedecin}/{jour}]
[RdvMedecinsController] :

est

traite

par

la

mthode

suivante

du

contrleur

1. @RequestMapping(value = "/getAgendaMedecinJour/{idMedecin}/{jour}", method = RequestMethod.GET)


2.
public Reponse getAgendaMedecinJour(@PathVariable("idMedecin") long idMedecin,
@PathVariable("jour") String jour) {
3.
// tat de l'application
4.
if (messages != null) {
5.
return new Reponse(-1, messages);
6.
}
7.
// on vrifie la date
8.
Date jourAgenda = null;
9.
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
10.
sdf.setLenient(false);
11.
try {
12.
jourAgenda = sdf.parse(jour);
13.
} catch (ParseException e) {
14.
return new Reponse(3, new String[] { String.format("jour [%s] invalide", jour) });
15.
}
16.
// on rcupre le mdecin
17.
Reponse rponse = getMedecin(idMedecin);
18.
if (rponse.getStatus() != 0) {
19.
return rponse;
20.
}
21.
Medecin mdecin = (Medecin) rponse.getData();
22.
// on rcupre son agenda
23.
AgendaMedecinJour agenda = null;
24.
try {
25.
agenda = application.getAgendaMedecinJour(mdecin.getId(), jourAgenda);
26.
} catch (Exception e1) {
27.
return new Reponse(4, Static.getErreursForException(e1));
28.
}
29.
// ok
30.
return new Reponse(0, Static.getMapForAgendaMedecinJour(agenda));
31.
}

32. }

ligne 30, on rend un objet de type List<Map<String,Object>.

La mthode [Static.getMapForAgendaMedecinJour] est la suivante :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18. }

// AgendaMedecinJour --> Map


public static Map<String, Object> getMapForAgendaMedecinJour(AgendaMedecinJour agenda) {
// qq chose faire ?
if (agenda == null) {
return null;
}
// dictionnaire <String,Object>
Map<String, Object> hash = new HashMap<String, Object>();
hash.put("medecin", agenda.getMedecin());
hash.put("jour", new SimpleDateFormat("yyyy-MM-dd").format(agenda.getJour()));
List<Map<String, Object>> crneaux = new ArrayList<Map<String, Object>>();
for (CreneauMedecinJour crneau : agenda.getCreneauxMedecinJour()) {
crneaux.add(getMapForCreneauMedecinJour(crneau));
}
hash.put("creneauxMedecin", crneaux);
// on rend le dictionnaire
return hash;

Le dictionnaire construit a trois champs :

[medecin] : le mdecin propritaire de l'agenda. On a gard cette information car elle n'est prsente qu'une fois alors que
dans les cas prcdents, elle tait rpte dans chaque chane JSON ;

[jour] : le jour de l'agenda ;

http://tahe.developpez.com

95/325

[creneauxMedecin] : la liste des crneaux du mdecin avec un ventuel rendez-vous sur ce crneau ;

La mthode [getMapForCreneauMedecinJour] utilise ligne 13 est la suivante :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13. }

// CreneauMedecinJour --> map


public static Map<String, Object> getMapForCreneauMedecinJour(CreneauMedecinJour crneau) {
// qq chose faire ?
if (crneau == null) {
return null;
}
// dictionnaire <String,Object>
Map<String, Object> hash = new HashMap<String, Object>();
hash.put("creneau", getMapForCreneau(crneau.getCreneau()));
hash.put("rv", getMapForRv(crneau.getRv()));
// on rend le dictionnaire
return hash;

lignes 9-10 : on utilise les dictionnaires dj tudis pour les types [Creneau] et [Rv] qui n'embarquent donc pas d'objet
[Medecin] ;

Les rsultats obtenus sont les suivants :

ou bien ceux-ci si le jour est erron :

ou bien ceux-ci si le n du mdecin est invalide :

http://tahe.developpez.com

96/325

2.12.12

L'URL [/getMedecinById/{id}]

L'URL [/getMedecinById/{id}] est traite par la mthode suivante du contrleur [RdvMedecinsController] :


1.
2.
3.
4.
5.
6.
7.
8.
9. }

@RequestMapping(value = "/getMedecinById/{id}", method = RequestMethod.GET)


public Reponse getMedecinById(@PathVariable("id") long id) {
// tat de l'application
if (messages != null) {
return new Reponse(-1, messages);
}
// on rcupre le mdecin
return getMedecin(id);

Ligne 8, la mthode [getMedecin] est la suivante :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15. }

private Reponse getMedecin(long id) {


// on rcupre le mdecin
Medecin mdecin = null;
try {
mdecin = application.getMedecinById(id);
} catch (Exception e1) {
return new Reponse(1, Static.getErreursForException(e1));
}
// mdecin existant ?
if (mdecin == null) {
return new Reponse(2, null);
}
// ok
return new Reponse(0, mdecin);

Les rsultats obtenus sont les suivants :

ou bien ceux-ci si le n du mdecin est incorrect :

http://tahe.developpez.com

97/325

2.12.13

L'URL [/getClientById/{id}]

L'URL [/getClientById/{id}] est traite par la mthode suivante du contrleur [RdvMedecinsController] :


1.
2.
3.
4.
5.
6.
7.
8.
9. }

@RequestMapping(value = "/getClientById/{id}", method = RequestMethod.GET)


public Reponse getClientById(@PathVariable("id") long id) {
// tat de l'application
if (messages != null) {
return new Reponse(-1, messages);
}
// on rcupre le client
return getClient(id);

Ligne 8, la mthode [getClient] est la suivante :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15. }

private Reponse getClient(long id) {


// on rcupre le client
Client client = null;
try {
client = application.getClientById(id);
} catch (Exception e1) {
return new Reponse(1, Static.getErreursForException(e1));
}
// client existant ?
if (client == null) {
return new Reponse(2, null);
}
// ok
return new Reponse(0, client);

Les rsultats obtenus sont les suivants :

ou bien ceux-ci si le n du client est incorrect :

http://tahe.developpez.com

98/325

2.12.14

L'URL [/getCreneauById/{id}]

L'URL [/getCreneauById/{id}] est traite par la mthode suivante du contrleur [RdvMedecinsController] :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14. }

@RequestMapping(value = "/getCreneauById/{id}", method = RequestMethod.GET)


public Reponse getCreneauById(@PathVariable("id") long id) {
// tat de l'application
if (messages != null) {
return new Reponse(-1, messages);
}
// on rcupre le crneau
Reponse rponse = getCreneau(id);
if (rponse.getStatus() == 0) {
rponse.setData(Static.getMapForCreneau((Creneau) rponse.getData()));
}
// rsultat
return rponse;

Ligne 8, la mthode [getCreneau] est la suivante :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15. }

private Reponse getCreneau(long id) {


// on rcupre le crneau
Creneau crneau = null;
try {
crneau = application.getCreneauById(id);
} catch (Exception e1) {
return new Reponse(1, Static.getErreursForException(e1));
}
// crneau existant ?
if (crneau == null) {
return new Reponse(2, null);
}
// ok
return new Reponse(0, crneau);

Les rsultats obtenus sont les suivants :

ou ceux-ci si le n du crneau est incorrect :

2.12.15

L'URL [/getRvById/{id}]

L'URL [/getRvById/{id}] est traite par la mthode suivante du contrleur [RdvMedecinsController] :

http://tahe.developpez.com

99/325

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14. }

@RequestMapping(value = "/getRvById/{id}", method = RequestMethod.GET)


public Reponse getRvById(@PathVariable("id") long id) {
// tat de l'application
if (messages != null) {
return new Reponse(-1, messages);
}
// on rcupre le rv
Reponse rponse = getRv(id);
if (rponse.getStatus() == 0) {
rponse.setData(Static.getMapForRv2((Rv) rponse.getData()));
}
// rsultat
return rponse;

Ligne 8, la mthode [getRv] est la suivante :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15. }

private Reponse getRv(long id) {


// on rcupre le Rv
Rv rv = null;
try {
rv = application.getRvById(id);
} catch (Exception e1) {
return new Reponse(1, Static.getErreursForException(e1));
}
// Rv existant ?
if (rv == null) {
return new Reponse(2, null);
}
// ok
return new Reponse(0, rv);

Ligne 10, la mthode [Static.getMapForRv2] est la suivante :


1. // Rv --> Map
2.
public static Map<String, Object> getMapForRv2(Rv rv) {
3.
// qq chose faire ?
4.
if (rv == null) {
5.
return null;
6.
}
7.
// dictionnaire <String,Object>
8.
Map<String, Object> hash = new HashMap<String, Object>();
9.
hash.put("id", rv.getId());
10.
hash.put("idClient", rv.getIdClient());
11.
hash.put("idCreneau", rv.getIdCreneau());
12.
// on rend le dictionnaire
13.
return hash;
14.
}

Les rsultats obtenus sont les suivants :

ou bien ceux-ci si le n du rendez-vous est incorrect :

http://tahe.developpez.com

100/325

2.12.16

L'URL [/ajouterRv]

L'URL [/ajouterRv] est traite par la mthode suivante du contrleur [RdvMedecinsController] :


1. @RequestMapping(value = "/ajouterRv", method = RequestMethod.POST, consumes = "application/json;
charset=UTF-8")
2.
public Reponse ajouterRv(@RequestBody PostAjouterRv post) {
3.
// tat de l'application
4.
if (messages != null) {
5.
return new Reponse(-1, messages);
6.
}
7.
// on rcupre les valeurs postes
8.
String jour = post.getJour();
9.
long idCreneau = post.getIdCreneau();
10.
long idClient = post.getIdClient();
11.
// on vrifie la date
12.
Date jourAgenda = null;
13.
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
14.
sdf.setLenient(false);
15.
try {
16.
jourAgenda = sdf.parse(jour);
17.
} catch (ParseException e) {
18.
return new Reponse(6, null);
19.
}
20.
// on rcupre le crneau
21.
Reponse rponse = getCreneau(idCreneau);
22.
if (rponse.getStatus() != 0) {
23.
return rponse;
24.
}
25.
Creneau crneau = (Creneau) rponse.getData();
26.
// on rcupre le client
27.
rponse = getClient(idClient);
28.
if (rponse.getStatus() != 0) {
29.
rponse.incrStatusBy(2);
30.
return rponse;
31.
}
32.
Client client = (Client) rponse.getData();
33.
// on ajoute le Rv
34.
Rv rv = null;
35.
try {
36.
rv = application.ajouterRv(jourAgenda, crneau, client);
37.
} catch (Exception e1) {
38.
return new Reponse(5, Static.getErreursForException(e1));
39.
}
40.
// on rend la rponse
41.
return new Reponse(0, Static.getMapForRv(rv));
42.
}

Il n'y a l rien qui n'ait t dj vu. Ligne 41, on rend le rendez-vous qui a t ajout ligne 36.
Les rsultats obtenus ressemblent ceci avec le client [Advanced Rest Client] :

http://tahe.developpez.com

101/325

0
1
4

ou bien ceci si par exemple on donne un n de crneau inexistant :

http://tahe.developpez.com

102/325

2.12.17

L'URL [/supprimerRv]

L'URL [/supprimerRv] est traite par la mthode suivante du contrleur [RdvMedecinsController] :


1. @RequestMapping(value = "/supprimerRv", method = RequestMethod.POST, consumes =
"application/json; charset=UTF-8")
2.
public Reponse supprimerRv(@RequestBody PostSupprimerRv post) {
3.
// tat de l'application
4.
if (messages != null) {
5.
return new Reponse(-1, messages);
6.
}
7.
// on rcupre les valeurs postes
8.
long idRv = post.getIdRv();
9.
// on rcupre le rv
10.
Reponse rponse = getRv(idRv);
11.
if (rponse.getStatus() != 0) {
12.
return rponse;
13.
}
14.
// suppression du rv
15.
try {
16.
application.supprimerRv(idRv);
17.
} catch (Exception e1) {
18.
return new Reponse(3, Static.getErreursForException(e1));
19.
}
20.
// ok
21.
return new Reponse(0, null);
22.
}

Les rsultats obtenus sont les suivants :

http://tahe.developpez.com

103/325

1
2

ou bien ceux-ci si le n du rendez-vous n'existe pas :

http://tahe.developpez.com

104/325

Nous en avons termin avec le contrleur. Nous voyons maintenant comment configurer le projet.

2.12.18

Configuration du service web

La classe de configuration [AppConfig] est la suivante :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.

package rdvmedecins.web.config;

ligne 9 : on se met en mode [AutoConfiguration] afin que Spring Boot puisse configurer le projet en fonction des archives
qu'il trouvera dans le Classpath du projet ;
ligne 10 : on demande ce que les composants Spring soient cherchs dans le package [rdvmedecins.web] et ses
descendants. C'est ainsi que seront dcouverts les composants :
[@RestController RdvMedecinsController] dans le package [rdvmedecins.web.controllers] ;
[@Component ApplicationModel] dans le package [rdvmedecins.web.models] ;
ligne 11 : on importe la classe [DomainAndPersistenceConfig] qui configure le projet [rdvmedecins-metier-dao] afin
d'avoir accs aux beans de ce projet ;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import rdvmedecins.config.DomainAndPersistenceConfig;
@EnableAutoConfiguration
@ComponentScan(basePackages = { "rdvmedecins.web" })
@Import({ DomainAndPersistenceConfig.class })
public class AppConfig {
}

http://tahe.developpez.com

105/325

2.12.19

La classe excutable du service web

La classe [Boot] est la suivante :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.

package rdvmedecins.web.boot;
import org.springframework.boot.SpringApplication;
import rdvmedecins.web.config.AppConfig;
public class Boot {

public static void main(String[] args) {


SpringApplication.run(AppConfig.class, args);
}

Ligne 10, la mthode statique [SpringApplication.run] est excute avec comme premier paramtre, la classe [AppConfig] de
configuration du projet. Cette mthode va procder l'auto-configuration du projet, lancer le serveur Tomcat embarqu dans les
dpendances et y dployer le contrleur [RdvMedecinsController].
Les logs l'excution sont les suivants :
1. .
____
_
__ _ _
2. /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
3. ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
4. \\/ ___)| |_)| | | | | || (_| | ) ) ) )
5.
' |____| .__|_| |_|_| |_\__, | / / / /
6. =========|_|==============|___/=/_/_/_/
7. :: Spring Boot ::
(v1.0.0.RELEASE)
8.
9. 2014-06-12 17:30:41.261 INFO 9388 --- [
main] rdvmedecins.web.boot.Boot
: Starting Boot on Gportpers3 with PID 9388 (D:\data\istia-1314\polys\istia\angularjsspring4\dvp\rdvmedecins-webapi\target\classes started by ST)
10. 2014-06-12 17:30:41.306 INFO 9388 --- [
main]
ationConfigEmbeddedWebApplicationContext : Refreshing
org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@a1e932
e: startup date [Thu Jun 12 17:30:41 CEST 2014]; root of context hierarchy
11. 2014-06-12 17:30:42.058 INFO 9388 --- [
main] o.s.b.f.s.DefaultListableBeanFactory
: Overriding bean definition for bean
'org.springframework.boot.autoconfigure.AutoConfigurationPackages': replacing [Generic bean:
class [org.springframework.boot.autoconfigure.AutoConfigurationPackages$BasePackages]; scope=;
abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true;
primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null;
destroyMethodName=null] with [Generic bean: class
[org.springframework.boot.autoconfigure.AutoConfigurationPackages$BasePackages]; scope=;
abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true;
primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null;
destroyMethodName=null]
12. 2014-06-12 17:30:42.866 INFO 9388 --- [
main]
trationDelegate$BeanPostProcessorChecker : Bean
'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type
[class org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$

http://tahe.developpez.com

106/325

13.

14.

15.

16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.

35.

36.

$EnhancerBySpringCGLIB$$fd7a7b18] is not eligible for getting processed by all


BeanPostProcessors (for example: not eligible for auto-proxying)
2014-06-12 17:30:42.900 INFO 9388 --- [
main]
trationDelegate$BeanPostProcessorChecker : Bean 'transactionAttributeSource' of type [class
org.springframework.transaction.annotation.AnnotationTransactionAttributeSource] is not
eligible for getting processed by all BeanPostProcessors (for example: not eligible for autoproxying)
2014-06-12 17:30:42.915 INFO 9388 --- [
main]
trationDelegate$BeanPostProcessorChecker : Bean 'transactionInterceptor' of type [class
org.springframework.transaction.interceptor.TransactionInterceptor] is not eligible for
getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2014-06-12 17:30:42.920 INFO 9388 --- [
main]
trationDelegate$BeanPostProcessorChecker : Bean
'org.springframework.transaction.config.internalTransactionAdvisor' of type [class
org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor] is
not eligible for getting processed by all BeanPostProcessors (for example: not eligible for
auto-proxying)
2014-06-12 17:30:43.164 INFO 9388 --- [
main]
.t.TomcatEmbeddedServletContainerFactory : Server initialized with port: 8080
2014-06-12 17:30:43.403 INFO 9388 --- [
main]
o.apache.catalina.core.StandardService
: Starting service Tomcat
2014-06-12 17:30:43.403 INFO 9388 --- [
main]
org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/7.0.52
2014-06-12 17:30:43.582 INFO 9388 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]
: Initializing Spring embedded WebApplicationContext
2014-06-12 17:30:43.582 INFO 9388 --- [ost-startStop-1] o.s.web.context.ContextLoader
: Root WebApplicationContext: initialization completed in 2279 ms
2014-06-12 17:30:44.117 INFO 9388 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean
: Mapping servlet: 'dispatcherServlet' to [/]
2014-06-12 17:30:44.119 INFO 9388 --- [ost-startStop-1]
o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2014-06-12 17:30:44.662 INFO 9388 --- [
main]
j.LocalContainerEntityManagerFactoryBean : Building JPA container EntityManagerFactory for
persistence unit 'default'
2014-06-12 17:30:44.707 INFO 9388 --- [
main]
o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [
name: default
...]
2014-06-12 17:30:44.839 INFO 9388 --- [
main] org.hibernate.Version
: HHH000412: Hibernate Core {4.3.1.Final}
2014-06-12 17:30:44.842 INFO 9388 --- [
main] org.hibernate.cfg.Environment
: HHH000206: hibernate.properties not found
2014-06-12 17:30:44.844 INFO 9388 --- [
main] org.hibernate.cfg.Environment
: HHH000021: Bytecode provider name : javassist
2014-06-12 17:30:45.189 INFO 9388 --- [
main]
o.hibernate.annotations.common.Version
: HCANN000001: Hibernate Commons Annotations
{4.0.4.Final}
2014-06-12 17:30:45.616 INFO 9388 --- [
main] org.hibernate.dialect.Dialect
: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
2014-06-12 17:30:45.783 INFO 9388 --- [
main] o.h.h.i.ast.ASTQueryTranslatorFactory
: HHH000397: Using ASTQueryTranslatorFactory
2014-06-12 17:30:46.729 INFO 9388 --- [
main]
o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of
type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2014-06-12 17:30:46.825 INFO 9388 --- [
main]
s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/getRvMedecinJour/{idMedecin}/
{jour}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public
rdvmedecins.web.models.Reponse
rdvmedecins.web.controllers.RdvMedecinsController.getRvMedecinJour(long,java.lang.String)
2014-06-12 17:30:46.826 INFO 9388 --- [
main]
s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/getAllCreneaux/
{idMedecin}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto
public rdvmedecins.web.models.Reponse
rdvmedecins.web.controllers.RdvMedecinsController.getAllCreneaux(long)
2014-06-12 17:30:46.826 INFO 9388 --- [
main]
s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/getAgendaMedecinJour/{idMedecin}/
{jour}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public

http://tahe.developpez.com

107/325

37.

38.

39.

40.

41.

42.

43.

44.

45.
46.
47.
48.
49.
50.
51.
52.

rdvmedecins.web.models.Reponse
rdvmedecins.web.controllers.RdvMedecinsController.getAgendaMedecinJour(long,java.lang.String)
2014-06-12 17:30:46.826 INFO 9388 --- [
main]
s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped
"{[/getAllMedecins],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}"
onto public rdvmedecins.web.models.Reponse
rdvmedecins.web.controllers.RdvMedecinsController.getAllMedecins()
2014-06-12 17:30:46.826 INFO 9388 --- [
main]
s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/getMedecinById/
{id}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public
rdvmedecins.web.models.Reponse
rdvmedecins.web.controllers.RdvMedecinsController.getMedecinById(long)
2014-06-12 17:30:46.827 INFO 9388 --- [
main]
s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/getCreneauById/
{id}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public
rdvmedecins.web.models.Reponse
rdvmedecins.web.controllers.RdvMedecinsController.getCreneauById(long)
2014-06-12 17:30:46.827 INFO 9388 --- [
main]
s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/getClientById/
{id}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public
rdvmedecins.web.models.Reponse
rdvmedecins.web.controllers.RdvMedecinsController.getClientById(long)
2014-06-12 17:30:46.827 INFO 9388 --- [
main]
s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/getRvById/
{id}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public
rdvmedecins.web.models.Reponse
rdvmedecins.web.controllers.RdvMedecinsController.getRvById(long)
2014-06-12 17:30:46.827 INFO 9388 --- [
main]
s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped
"{[/getAllClients],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto
public rdvmedecins.web.models.Reponse
rdvmedecins.web.controllers.RdvMedecinsController.getAllClients()
2014-06-12 17:30:46.827 INFO 9388 --- [
main]
s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped
"{[/ajouterRv],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF8],produces=[],custom=[]}" onto public rdvmedecins.web.models.Reponse
rdvmedecins.web.controllers.RdvMedecinsController.ajouterRv(rdvmedecins.web.models.PostAjouter
Rv)
2014-06-12 17:30:46.828 INFO 9388 --- [
main]
s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped
"{[/supprimerRv],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF8],produces=[],custom=[]}" onto public rdvmedecins.web.models.Reponse
rdvmedecins.web.controllers.RdvMedecinsController.supprimerRv(rdvmedecins.web.models.PostSuppr
imerRv)
2014-06-12 17:30:46.851 INFO 9388 --- [
main]
o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class
org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2014-06-12 17:30:46.851 INFO 9388 --- [
main]
o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type
[class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2014-06-12 17:30:47.131 INFO 9388 --- [
main] o.s.j.e.a.AnnotationMBeanExporter
: Registering beans for JMX exposure on startup
2014-06-12 17:30:47.169 INFO 9388 --- [
main]
s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080/http
2014-06-12 17:30:47.170 INFO 9388 --- [
main] rdvmedecins.web.boot.Boot
: Started Boot in 6.302 seconds (JVM running for 6.906)
2014-06-12 17:30:55.520 INFO 9388 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]
: Initializing Spring FrameworkServlet 'dispatcherServlet'
2014-06-12 17:30:55.520 INFO 9388 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet
: FrameworkServlet 'dispatcherServlet': initialization started
2014-06-12 17:30:55.538 INFO 9388 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet
: FrameworkServlet 'dispatcherServlet': initialization completed in 18 ms

ligne 17 : le serveur Tomcat dmarre ;


lignes 23-31 : les couches [mtier, DAO, JPA] s'initialisent ;
ligne 34 : la mthode traitant l'URL [/getRvMedecinJour/{idMedecin}/{jour}] a t dcouverte. Ce processus de
dcouverte des mthodes du contrleur se rpte jusqu' la ligne 44 ;

http://tahe.developpez.com

108/325

ligne 52 : la servlet de Spring MVC [DispatcherServlet] est prte rpondre aux demandes de clients web ;

Nous avons dsormais un service web oprationnel interrogeable avec un client web. Nous abordons maintenant la scurisation de
ce service : nous voulons que seules certaines personnes puissent grer les rendez-vous des mdecins. Nous allons utiliser pour cela
le framework Spring Security, une branche de l'cosystme Spring.

2.13

Introduction Spring Security

Nous allons de nouveau importer un guide Spring en suivant les tapes 1 3 ci-dessous :

Le projet se compose des lments suivants :

2.13.1

dans le dossier [templates], on trouve les pages HTML du projet ;


[Application] : est la classe excutable du projet ;
[MvcConfig] : est la classe de configuration de Spring MVC ;
[WebSecurityConfig] : est la classe de configuration de Spring Security ;

Configuration Maven

Le projet [3] est un projet Maven. Examinons son fichier [pom.xml] pour connatre ses dpendances :

http://tahe.developpez.com

109/325

1.
<parent>
2.
<groupId>org.springframework.boot</groupId>
3.
<artifactId>spring-boot-starter-parent</artifactId>
4.
<version>1.1.1.RELEASE</version>
5.
</parent>
6.
7.
<dependencies>
8.
<dependency>
9.
<groupId>org.springframework.boot</groupId>
10.
<artifactId>spring-boot-starter-thymeleaf</artifactId>
11.
</dependency>
12.
<dependency>
13.
<groupId>org.springframework.boot</groupId>
14.
<artifactId>spring-boot-starter-security</artifactId>
15.
</dependency>
16. </dependencies>

2.13.2

lignes 1-5 : le projet est un projet Spring Boot ;


lignes 8-11 : dpendance sur le framework [Thymeleaf] qui permet de construire des pages HTML dynamiques. Ce
framework peut remplacer les pages JSP (Java Server Pages) qui jusqu' un pass rcent taient par dfaut, le framework de
vues de Spring MVC ;
lignes 12-15 : dpendance sur le framework Spring Security ;

Les vues Thymeleaf

La vue [home.html] est la suivante :

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example</title>
</head>
<body>
<h1>Welcome!</h1>
<p>
Click <a th:href="@{/hello}">here</a> to see a greeting.
</p>
</body>

</html>

http://tahe.developpez.com

110/325

les attributs [th:xx] sont des attributs Thymeleaf. Ils sont interpts par Thymeleaf avant que la page HTML ne soit
envoye au client. Celui ne les voit pas ;
ligne 12 : l'attribut [th:href="@{/hello}"] va gnrer l'attribut [href] de la balise <a>. La valeur [@{/hello}] va gnrer le
chemin [<context>/hello] o [context] est le contexte de l'application web ;

Le code HTML gnr est le suivant :


1. <!DOCTYPE html>
2.
3. <html xmlns="http://www.w3.org/1999/xhtml" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extrasspringsecurity3">
4. <head>
5. <title>Spring Security Example</title>
6. </head>
7. <body>
8.
<h1>Welcome!</h1>
9.
<p>
10.
Click <a href="/hello">here</a> to see a greeting.
11.
</p>
12. </body>
13. </html>

ligne 10 : le contexte de l'application est la racine / ;

La vue [hello.html] est la suivante :

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Hello World!</title>
</head>
<body>
<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
<form th:action="@{/logout}" method="post">
<input type="submit" value="Sign Out" />
</form>
</body>

ligne 9 : L'attribut [th:inline="text"] va gnrer le texte de la balise <h1>. Ce texte contient une expression $ qui doit tre
value. L'lment [[${#httpServletRequest.remoteUser}]] est la valeur de l'attribut [RemoteUser] de la requte HTTP
courante. C'est le nom de l'utilisateur connect ;
ligne 10 : un formulaire HTML. L'attribut [th:action="@{/logout}"] va gnrer l'attribut [action] de la balise [form]. La
valeur [@{/logout}] va gnrer le chemin [<context>/logout] o [context] est le contexte de l'application web ;

</html>

Le code HTML gnr est le suivant :


1. <!DOCTYPE html>

http://tahe.developpez.com

111/325

2.
3. <html xmlns="http://www.w3.org/1999/xhtml" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extrasspringsecurity3">
4. <head>
5. <title>Hello World!</title>
6. </head>
7. <body>
8.
<h1>Hello user!</h1>
9.
<form method="post" action="/logout">
10.
<input type="submit" value="Sign Out" />
11.
<input type="hidden" name="_csrf" value="c60cf557-1f3b-415f-a628-39380de7b69a" /></form>
12. </body>
13. </html>

ligne 8 : la traduction de Hello [[${#httpServletRequest.remoteUser}]]!;


ligne 9 : la traduction de @{/logout} ;
ligne 11 : un champ cach appel (attribut name) _csrf ;

La dernire vue [login.html] est la suivante :

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example</title>
</head>
<body>
<div th:if="${param.error}">Invalid username and password.</div>
<div th:if="${param.logout}">You have been logged out.</div>
<form th:action="@{/login}" method="post">
<div>
<label> User Name : <input type="text" name="username" />
</label>
</div>
<div>
<label> Password: <input type="password" name="password" />
</label>
</div>
<div>
<input type="submit" value="Sign In" />
</div>
</form>
</body>

ligne 9 : l'attribut [th:if="${param.error}"] fait que la balise <div> ne sera gnre que si l'URL qui affiche la page de login
contient le paramtre [error] (http://context/login?error);
ligne 10 : l'attribut [th:if="${param.logout}"] fait que la balise <div> ne sera gnre que si l'URL qui affiche la page de
login contient le paramtre [logout] (http://context/login?logout);
lignes 11-23 : un formulare HTML ;
ligne 11 : le formulaire sera post l'URL [<context>/login] o <context> est le contexte de l'application web ;

</html>

http://tahe.developpez.com

112/325

ligne 13 : un champ de saisie nomm [username] ;


ligne 17 : un champ de saisie nomm [password] ;

Le code HTML gnr est le suivant :


1. <!DOCTYPE html>
2.
3. <html xmlns="http://www.w3.org/1999/xhtml" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extrasspringsecurity3">
4. <head>
5. <title>Spring Security Example</title>
6. </head>
7. <body>
8.
9.
<form method="post" action="/login">
10.
<div>
11.
<label> User Name : <input type="text" name="username" />
12.
</label>
13.
</div>
14.
<div>
15.
<label> Password: <input type="password" name="password" />
16.
</label>
17.
</div>
18.
<div>
19.
<input type="submit" value="Sign In" />
20.
</div>
21.
<input type="hidden" name="_csrf" value="c60cf557-1f3b-415f-a628-39380de7b69a" /></form>
22. </body>
23. </html>

On notera ligne 21 que Thymeleaf a ajout un champ cach nomm [_csrf].

2.13.3

Configuration Spring MVC

La classe [MvcConfig] configure le framework Spring MVC :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.

package hello;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/home").setViewName("home");
registry.addViewController("/").setViewName("home");
registry.addViewController("/hello").setViewName("hello");
registry.addViewController("/login").setViewName("login");
}
}

http://tahe.developpez.com

113/325

ligne 7 : l'annotation [@Configuration] fait de la classe [MvcConfig] une classe de configuration ;


ligne 8 : la classe [MvcConfig] tend la classe [WebMvcConfigurerAdapter] pour en redfinir certaines mthodes ;
ligne 10 : redfinition d'une mthode de la classe parent ;
lignes 11- 16 : la mthode [addViewControllers] permet d'associer des URL des vues HTML. Les associations suivantes y
sont faites :

URL

vue

/,
/home

/templates/home.html

/hello

/templates/hello.html

/login

/templates/login.html

Le suffixe [html] et le dossier [templates] sont les valeurs par dfaut utilises par Thymeleaf. Elles peuvent tre changes par
configuration. Le dossier [templates] doit tre la racine du Classpath du projet :
1

Ci-dessus [1], les dossiers [main] et [resources] sont tous les deux des dossier source (source folders). Cela implique que leur
contenu sera la racine du Classpath du projet. Donc en [2], les dossiers [hello] et [templates] seront la racine du Classpath.

2.13.4

Configuration Spring Security

La classe [WebSecurityConfig] configure le framework Spring Security :


1.
2.
3.
4.

package hello;
import org.springframework.context.annotation.Configuration;
import
org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;

5.
6.
7.
8.
9. @Configuration
10. @EnableWebMvcSecurity
11. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
12.
@Override
13.
protected void configure(HttpSecurity http) throws Exception {
14.
http.authorizeRequests().antMatchers("/", "/home").permitAll().anyRequest().authenticated();
15.
http.formLogin().loginPage("/login").permitAll().and().logout().permitAll();
16.
}

http://tahe.developpez.com

114/325

17.
18.
19.
20.
21.
22. }

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
}

ligne 9 : l'annotation [@Configuration] fait de la classe [WebSecurityConfig] une classe de configuration ;


ligne 10 : l'annotation [@EnableWebSecurity] fait de la classe [WebSecurityConfig] une classe de configuration de Spring
Security ;
ligne 11 : la classe [WebSecurity] tend la classe [WebSecurityConfigurerAdapter] pour en redfinir certaines mthodes ;
ligne 12 : redfinition d'une mthode de la classe parent ;
lignes 13- 16 : la mthode [configure(HttpSecurity http)] est redfinie pour dfinir les droits d'accs aux diffrentes URL
de l'application ;
ligne 14 : la mthode [http.authorizeRequests()] permet d'associer des URL des droits d'accs. Les associations suivantes
y sont faites :

URL

rgle

/, /home

accs sans tre authentifi

code
http.authorizeRequests().antMatchers("/", "/home").permitAll()

autres URL accs authentifi uniquement http.anyRequest().authenticated();

ligne 15 : dfinit la mthode d'authentification. L'authentification se fait via un formulaire d'URL [/login] accessible tous
[http.formLogin().loginPage("/login").permitAll()]. La dconnexion (logout) est galement accessible tous.
lignes 19-21 : redfinissent la mthode [configure(AuthenticationManagerBuilder auth)] qui gre les utilisateurs ;
ligne 20 : l'autentification se fait avec des utilisateurs dfinis en " dur " [auth.inMemoryAuthentication()]. Un utilisateur est
ici dfini avec le login [user], le mot de passe [password] et le rle [USER]. On peut accorder les mmes droits des
utilisateurs ayant le mme rle ;

2.13.5

Classe excutable

La classe [Application] est la suivante :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.

package hello;

ligne 8 : l'annotation [@EnableAutoConfiguration] demande Spring Boot (ligne 3) de faire la configuration que le
dveloppeur n'aura pas fait explicitement ;

import
import
import
import

org.springframework.boot.autoconfigure.EnableAutoConfiguration;
org.springframework.boot.SpringApplication;
org.springframework.context.annotation.ComponentScan;
org.springframework.context.annotation.Configuration;

@EnableAutoConfiguration
@Configuration
@ComponentScan
public class Application {
public static void main(String[] args) throws Throwable {
SpringApplication.run(Application.class, args);
}
}

http://tahe.developpez.com

115/325

ligne 9 : fait de la classe [Application] une classe de configuration Spring ;


ligne 10 : demande le scan du dossier de la classe [Application] afin de rechercher des composants Spring. Les deux classes
[MvcConfig] et [WebSecurityConfig] vont tre ainsi dcouvertes car elles ont l'annotation [@Configuration] ;
ligne 13 : la mthode [main] de la classe excutable ;
ligne 14 : la mthode statique [SpringApplication.run] est excute avec comme paramtre la classe de configuration
[Application]. Nous avons dj rencontr ce processus et nous savons que le serveur Tomcat embarqu dans les
dpendances Maven du projet va tre lanc et le projet dploy dessus. Nous avons vu que quatre URL taient gres
[/, /home, /login, /hello] et que certaines taient protges par des droits d'accs.

2.13.6

Tests de l'application

Commenons par demander l'URL [/] qui est l'une des quatre URL acceptes. Elle est associe la vue [/templates/home.html] :

L'URL demande [/] est accessible tous. C'est pourquoi nous l'avons obtenue. Le lien [here] est le suivant :
Click <a href="/hello">here</a> to see a greeting.

L'URL [/hello] va tre demande lorsqu'on va cliquer sur le lien. Celle-ci est protge :
URL

rgle

/, /home

accs sans tre authentifi

code
http.authorizeRequests().antMatchers("/", "/home").permitAll()

autres URL accs authentifi uniquement http.anyRequest().authenticated();

Il faut tre authentifi pour l'obtenir. Spring Security va alors rediriger le navigateur client vers la page d'authentification. D'aprs la
configuration vue, c'est la page d'URL [/login]. Celle-ci est accessible tous :
http.formLogin().loginPage("/login").permitAll().and().logout().permitAll();

Nous l'obtenons donc [1] :

Le code source de la page obtenue est le suivant :


1. <!DOCTYPE html>
2.

http://tahe.developpez.com

116/325

3. <html xmlns="http://www.w3.org/1999/xhtml" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extrasspringsecurity3">


4. ...
5.
<form method="post" action="/login">
6. ...
7.
<input type="hidden" name="_csrf" value="87bea06a-a177-459d-b279-c6068a7ad3eb" />
8.
</form>
9. </body>
10. </html>

ligne 7, un champ cach apparat qui n'est pas dans la page [login.html] d'origine. C'est Thymeleaf qui l'a ajout. Ce code
appel CSRF (Cross Site Request Forgery) vise liminer une faille de scurit. Ce jeton doit tre renvoy Spring
Security avec l'authentification pour que cette dernire soit accepte ;
Nous nous souvenons que seul l'utilisateur user/password est reconnu par Spring Security. Si nous entons autre chose en [2], nous
obtenons la mme page avec un message d'erreur en [3]. Spring Security a redirig le navigateur vers l'URL
[http://localhost:8080/login?error]. La prsence du paramtre [error] a dclench l'affichage de la balise :

<div th:if="${param.error}">Invalid username and password.</div>

Maintenant, entrons les valeurs attendues user/password [4] :

en [4], nous nous identifions ;


en [5], Spring Security nous redirige vers l'URL [/hello] car c'est l'URL que nous demandions lorsque nous avons t
redirig vers la page de login. L'identit de l'utilisateur a t affiche par la ligne suivante de [hello.html] :

<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>

La page [5] affiche le formulaire suivant :


1.
<form th:action="@{/logout}" method="post">
2.
<input type="submit" value="Sign Out" />
3. </form>

Lorsqu'on clique sur le bouton [Sign Out], un POST va tre fait sur l'URL [/logout]. Celle-ci comme l'URL [/login] est accessible
tous :
http.formLogin().loginPage("/login").permitAll().and().logout().permitAll();

Dans notre association URL / vues, nous n'avons rien dfini pour l'URL [/logout]. Que va-t-il se passer ? Essayons :

http://tahe.developpez.com

117/325

en [6], nous cliquons sur le bouton [Sign Out] ;


en [7], nous voyons que nous avons t redirigs vers l'URL [http://localhost:8080/login?logout]. C'est Spring Security qui
a demand cette redirection. La prsence du paramtre [logout] dans l'URL a fait afficher la ligne suivante de la vue :

<div th:if="${param.logout}">You have been logged out.</div>

2.13.7

Conclusion

Dans l'exemple prcdent, nous aurions pu crire l'application web d'abord puis la scuriser ensuite. Spring Security n'est pas
intrusif. On peut mettre en place la scurit d'une application web dj crite. Par ailleurs, nous avons dcouvert les points suivants :

il est possible de dfinir une page d'authentification ;


l'authentification doit tre accompagne du jeton CSRF dlivr par Spring Security ;
si l'authentification choue, on est redirig vers la page d'authentification avec de plus un paramtre error dans l'URL ;
si l'authentification russit, on est redirig vers la page demande lorsque l'autentification a eu lieu. Si on demande
directement la page d'authentification sans passer par une page intermdiaire, alors Spring Security nous redirige vers
l'URL [/] (ce cas n'a pas t prsent) ;
on se dconnecte en demandant l'URL [/logout] avec un POST. Spring Security nous redirige alors vers la page
d'authentification avec le paramtre logout dans l'URL ;

Toutes ces conclusions reposent sur des comportements par dfaut de Spring Security. Ces comportements peuvent tre changs
par configuration en redfinissant certaines mthodes de la classe [WebSecurityConfigurerAdapter].
Le tutoriel prcdent nous aidera peu dans la suite. Nous allons en effet utiliser :

une base de donnes pour stocker les utilisateurs, leurs mots de passe et leurs rles ;
une authentification par entte HTTP ;

On trouve assez peu de tutoriels pour ce qu'on veut faire ici. La solution qui va tre propose est un assemblage de codes trouvs ici
et l.

2.14
2.14.1

Mise en place de la scurit sur le service web des rendez-vous


La base de donnes

La base de donnes [rdvmedecins] volue pour prendre en compte les utilisateurs, leurs mots de passe et leur rles. Trois nouvelles
tables apparaissent :

http://tahe.developpez.com

118/325

Table [USERS] : les utilisateurs

ID : cl primaire ;

VERSION : colonne de versioning de la ligne ;

IDENTITY : une identit descriptive de l'utilisateur ;

LOGIN : le login de l'utilisateur ;

PASSWORD : son mot de passe ;


Dans la table USERS, les mots de passe ne sont pas stocks en clair :

L'algorithme qui crypte les mots de passe est l'algorithme BCRYPT.


Table [ROLES] : les rles

ID : cl primaire ;
VERSION : colonne de versioning de la ligne ;
NAME : nom du rle. Par dfaut, Spring Security attend des noms de la forme ROLE_XX, par exemple ROLE_ADMIN
ou ROLE_GUEST ;

Table [USERS_ROLES] : table de jointure USERS / ROLES


Un utilisateur peut avoir plusieurs rles, un rle peut rassembler plusieurs utilisateurs. On a une relation plusieurs plusieurs
matrialise par la table [USERS_ROLES].

ID : cl primaire ;

VERSION : colonne de versioning de la ligne ;

USER_ID : identifiant d'un utilisateur ;

ROLE_ID : identifiant d'un rle ;

Parce que nous modifions la base de donnes, l'ensemble des couches du projet [mtier, DAO, JPA] doit tre modifi :

Couche
[web /
JSON]

Couche
[metier]

Couche
[DAO]

Couche
[JPA]

Pilote
[JDBC]

SGBD

7 4
Spring

http://tahe.developpez.com

119/325

2.14.2

Le nouveau projet Eclipse du [mtier, DAO, JPA]

Nous dupliquons le projet initial [rdvmedecins-metier-dao] en [rdvmedecins-metier-dao-v2] :

2.14.3

en [1] : le nouveau projet ;


en [2] : les modifications amenes par la prise en compte de la scurit ont t rassembles dans un unique paquetage
[rdvmedecins.security]. Ces nouveaux lments appartiennent aux couches [JPA] et [DAO] mais par simplicit je les ai
rassembls dans un mme paquetage.

Les nouvelles entits [JPA]

Couche
[web /
JSON]

Couche
[metier]

Couche
[DAO]

Couche
[JPA]

Pilote
[JDBC]

SGBD

7 4
Spring
La couche JPA dfinit trois nouvelles entits :

La classe [User] est l'image de la table [USERS] :


1.
2.
3.
4.
5.
6.
7.
8.

package rdvmedecins.entities;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@Table(name = "USERS")

http://tahe.developpez.com

120/325

9. public class User extends AbstractEntity {


10.
private static final long serialVersionUID = 1L;
11.
12.
// proprits
13.
private String identity;
14.
private String login;
15.
private String password;
16.
17.
// constructeur
18.
public User() {
19.
}
20.
21.
public User(String identity, String login, String password) {
22.
this.identity = identity;
23.
this.login = login;
24.
this.password = password;
25.
}
26.
27.
// identit
28.
@Override
29.
public String toString() {
30.
return String.format("User[%s,%s,%s]", identity, login, password);
31.
}
32.
33.
// getters et setters
34. ....
35. }

ligne 9 : la classe tend la classe [AbstractEntity] dj utilise pour les autres entits ;
lignes 13-15 : on ne prcise pas de nom pour les colonnes parce qu'elles portent le mme nom que les champs qui leur
sont associs ;

La classe [Role] est l'image de la table [ROLES] :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.

package rdvmedecins.entities;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@Table(name = "ROLES")
public class Role extends AbstractEntity {
private static final long serialVersionUID = 1L;
// proprits
private String name;
// constructeurs
public Role() {
}
public Role(String name) {
this.name = name;
}
// identit
@Override
public String toString() {
return String.format("Role[%s]", name);
}
// getters et setters
...
}

http://tahe.developpez.com

121/325

La classe [UserRole] est l'image de la table [USERS_ROLES] :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.

package rdvmedecins.entities;

lignes 15-17 : matrialisent la cl trangre de la table [USERS_ROLES] vers la table [USERS] ;


lignes 19-21 : matrialisent la cl trangre de la table [USERS_ROLES] vers la table [ROLES] ;

2.14.4

import
import
import
import

javax.persistence.Entity;
javax.persistence.JoinColumn;
javax.persistence.ManyToOne;
javax.persistence.Table;

@Entity
@Table(name = "USERS_ROLES")
public class UserRole extends AbstractEntity {
private static final long serialVersionUID = 1L;
// un UserRole rfrence un User
@ManyToOne
@JoinColumn(name = "USER_ID")
private User user;
// un UserRole rfrence un Role
@ManyToOne
@JoinColumn(name = "ROLE_ID")
private Role role;
// getters et setters
...
}

Modifications de la couche [DAO]

Couche
[web /
JSON]

Couche
[metier]

Couche
[DAO]

Couche
[JPA]

Pilote
[JDBC]

SGBD

7 4
Spring
La couche [DAO] s'enrichit de trois nouveaux [Repository] :

L'interface [UserRepository] gre les accs aux entits [User] :


1.
2.
3.
4.
5.
6.

package rdvmedecins.repositories;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import rdvmedecins.entities.Role;

http://tahe.developpez.com

122/325

7. import rdvmedecins.entities.User;
8.
9. public interface UserRepository extends CrudRepository<User, Long> {
10.
11.
// liste des rles d'un utilisateur identifi par son id
12.
@Query("select ur.role from UserRole ur where ur.user.id=?1")
13.
Iterable<Role> getRoles(long id);
14.
15.
// liste des rles d'un utilisateur identifi par son login et son mot de passe
16.
@Query("select ur.role from UserRole ur where ur.user.login=?1 and ur.user.password=?2")
17.
Iterable<Role> getRoles(String login, String password);
18.
19.
// recherche d'un utilisateur via son login
20.
User findUserByLogin(String login);
21. }

ligne 9 : l'interface [UserRepository] tend la l'interface [CrudRepository] de Spring Data (ligne 4) ;


lignes 12-13 : la mthode [getRoles(User user)] permet d'avoir tous les rles d'un utilisateur identifi par son [id]
lignes 16-17 : idem mais pour un utilisateur identifi pas ses login / mot de passe ;

L'interface [RoleRepository] gre les accs aux entits [Role] :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.

package rdvmedecins.security;

ligne 5 : l'interface [RoleRepository] tend l'interface [CrudRepository] ;


ligne 8 : on peut chercher un rle via son nom ;

import org.springframework.data.repository.CrudRepository;
public interface RoleRepository extends CrudRepository<Role, Long> {
// recherche d'un rle via son nom
Role findRoleByName(String name);
}

L'interface [userRoleRepository] gre les accs aux entits [UserRole] :


1.
2.
3.
4.
5.
6.
7.

package rdvmedecins.security;

ligne 5 : l'interface [UserRoleRepository] se contente d'tendre l'interface [CrudRepository] sans lui ajouter de nouvelles
mthodes ;

2.14.5

import org.springframework.data.repository.CrudRepository;
public interface UserRoleRepository extends CrudRepository<UserRole, Long> {
}

Les classes de gestion des utilisateurs et des rles

Spring Security impose la cration d'une classe implmentant l'interface [UsersDetail] suivante :

http://tahe.developpez.com

123/325

Cette interface est ici implmente par la classe [AppUserDetails] :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.

package rdvmedecins.security;
import java.util.ArrayList;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
public class AppUserDetails implements UserDetails {
private static final long serialVersionUID = 1L;
// proprits
private User user;
private UserRepository userRepository;
// constructeurs
public AppUserDetails() {
}
public AppUserDetails(User user, UserRepository userRepository) {
this.user = user;
this.userRepository = userRepository;
}
// -------------------------interface
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
for (Role role : userRepository.getRoles(user.getId())) {
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override

http://tahe.developpez.com

124/325

43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69. }

public String getUsername() {


return user.getLogin();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
// getters et setters
...

ligne 10 : la classe [AppUserDetails] implmente l'interface [UserDetails] ;


lignes 15-16 : la classe encapsule un utilisateur (ligne 15) et le repository qui permet d'avoir les dtails de cet utilisateur
(ligne 16) ;
lignes 22-25 : le constructeur qui instancie la classe avec un utilisateur et son repository ;
lignes 28-35 : implmentation de la mthode [getAuthorities] de l'interface [UserDetails]. Elle doit construire une
collection d'lments de type [GrantedAuthority] ou driv. Ici, nous utilisons le type driv [SimpleGrantedAuthority]
(ligne 32) qui encapsule le nom d'un des rles de l'utilisateur de la ligne 15 ;
lignes 31-33 : on parcourt la liste des rles de l'utilisateur de la ligne 15 pour construire une liste d'lments de type
[SimpleGrantedAuthority] ;
lignes 38-40 : implmentent la mthode [getPassword] de l'interface [UserDetails]. On rend le mot de passe de l'utilisateur
de la ligne 15 ;
lignes 38-40 : implmentent la mthode [getUserName] de l'interface [UserDetails]. On rend le login de l'utilisateur de la
ligne 15 ;
lignes 47-50 : le compte de l'utilisateur n'expire jamais ;
lignes 52-55 : le compte de l'utilisateur n'est jamais bloqu ;
lignes 57-60 : les identifiants de l'utilisateur n'expirent jamais ;
lignes 62-65 : le compte de l'utilisateur est toujours actif ;

Spring Security impose galement l'existence d'une classe implmentant l'interface [AppUserDetailsService] :

Cette interface est implmente par la classe [AppUserDetails] suivante :


1.
2.
3.
4.
5.

package rdvmedecins.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;

http://tahe.developpez.com

125/325

6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.

import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

ligne 9 : la classe sera un composant Spring, donc disponible dans son contexte ;
lignes 12-13 : le composant [UserRepository] sera inject ici ;
lignes 16-25 : implmentation de la mthode [loadUserByUsername] de l'interface [UserDetailsService] (ligne 10). Le
paramtre est le login de l'utilisateur ;
ligne 18 : l'utilisateur est recherch via son login ;
lignes 20-22 : s'il n'est pas trouv, une exception est lance ;
ligne 24 : un objet [AppUserDetails] est construit et rendu. Il est bien de type [UserDetails] (ligne 16) ;

2.14.6

@Service
public class AppUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
// on cherche l'utilisateur via son login
User user = userRepository.findUserByLogin(login);
// trouv ?
if (user == null) {
throw new UsernameNotFoundException(String.format("login [%s] inexistant", login));
}
// on rend les dtails de l'utilsateur
return new AppUserDetails(user, userRepository);
}
}

Tests de la couche [DAO]

Tout d'abord, nous crons une classe excutable [CreateUser] capable de crer un utilisateur avec un rle :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.

package rdvmedecins.security;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.security.crypto.bcrypt.BCrypt;
import
import
import
import
import
import
import

rdvmedecins.config.DomainAndPersistenceConfig;
rdvmedecins.security.Role;
rdvmedecins.security.RoleRepository;
rdvmedecins.security.User;
rdvmedecins.security.UserRepository;
rdvmedecins.security.UserRole;
rdvmedecins.security.UserRoleRepository;

public class CreateUser {


public static void main(String[] args) {
// syntaxe : login password roleName
// il faut trois paramtres
if (args.length != 3) {
System.out.println("Syntaxe : [pg] user password role");
System.exit(0);
}

http://tahe.developpez.com

126/325

24.
25.
26.
27.
28.
29.

// on rcupre les paramtres


String login = args[0];
String password = args[1];
String roleName = String.format("ROLE_%s", args[2].toUpperCase());
// contexte Spring
AnnotationConfigApplicationContext context = new
AnnotationConfigApplicationContext(DomainAndPersistenceConfig.class);
UserRepository userRepository = context.getBean(UserRepository.class);
RoleRepository roleRepository = context.getBean(RoleRepository.class);
UserRoleRepository userRoleRepository = context.getBean(UserRoleRepository.class);
// le rle existe-t-il dj ?
Role role = roleRepository.findRoleByName(roleName);
// s'il n'existe pas on le cre
if (role == null) {
role = roleRepository.save(new Role(roleName));
}
// l'utilisateur existe-t-il dj ?
User user = userRepository.findUserByLogin(login);
// s'il n'existe pas on le cre
if (user == null) {
// on hashe le mot de passe avec bcrypt
String crypt = BCrypt.hashpw(password, BCrypt.gensalt());
// on sauvegarde l'utilisateur
user = userRepository.save(new User(login, login, crypt));
// on cre la relation avec le rle
userRoleRepository.save(new UserRole(user, role));
} else {
// l'utilisateur existe dj- a-t-il le rle demand ?
boolean trouv = false;
for (Role r : userRepository.getRoles(user.getId())) {
if (r.getName().equals(roleName)) {
trouv = true;
break;
}
}
// si pas trouv, on cre la relation avec le rle
if (!trouv) {
userRoleRepository.save(new UserRole(user, role));
}
}

30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68. }

// fermeture contexte Spring


context.close();

ligne 17 : la classe attend trois arguments dfinissant un utilisateur : son login, son mot de passe, son rle ;
lignes 25-27 : les trois paramtres sont rcuprs ;
ligne 29 : le contexte Spring est construit partir de la classe de configuration [DomainAndPersistenceConfig]. Cette
classe existait dj dans le projet prcdent. Elle doit voluer de la faon suivante :

1.
2.
3.
4.
5.
6.
7.
8.

@EnableJpaRepositories(basePackages = { "rdvmedecins.repositories", "rdvmedecins.security" })


@EnableAutoConfiguration
@ComponentScan(basePackages = { "rdvmedecins" })
@EntityScan(basePackages = { "rdvmedecins.entities", "rdvmedecins.security" })
@EnableTransactionManagement
public class DomainAndPersistenceConfig {
....
}

ligne 1 : il faut indiquer qu'il y a maintenant des composants [Repository] dans le paquetage
[rdvmedecins.security] ;
ligne 4 : il faut indiquer qu'il y a maintenant des entits JPA dans le paquetage [rdvmedecins.security] ;

Revenons au code de cration d'un utilisateur :

http://tahe.developpez.com

127/325

lignes 30-32 : on rcupre les rfrences des trois [Repository] qui peuent nous tre utiles pour crer l'utilisateur ;
ligne 34 : on regarde si le rle existe dj ;
lignes 36-38 : si ce n'est pas le cas, on le cre en base. Il aura un nom du type [ROLE_XX] ;
ligne 40 : on regarde si le login existe dj ;
lignes 42-49 : si le login n'existe pas, on le cre en base ;
ligne 44 : on crypte le mot de passe. On utilise ici, la classe [BCrypt] de Spring Security (ligne 4). On a donc besoin des
archives de ce framework. Le fichier [pom.xml] inclut une nouvelle dpendance :
1.
<dependency>
2.
<groupId>org.springframework.boot</groupId>
3.
<artifactId>spring-boot-starter-security</artifactId>
4. </dependency>

ligne 46 : l'utilisateur est persist en base ;


ligne 48 : ainsi que la relation qui le lie son rle ;
lignes 51-57 : cas o le login existe dj on regarde alors si parmi ses rles se trouve dj le rle qu'on veut lui attribuer ;
ligne 59-61 : si le rle cherch n'a pas t trouv, on cre une ligne dans la table [USERS_ROLES] pour relier l'utilisateur
son rle ;
on ne s'est pas protg des exceptions ventuelles. C'est une classe de soutien pour crer rapidement un utilisateur avec un
rle.

Lorsqu'on excute la classe avec les arguments [x x guest], on obtient en base les rsultats suivants :
Table [USERS]

Table [ROLES]

Table [USERS_ROLES]

Considrons maintenant la seconde classe [UsersTest] qui est un test JUnit :

http://tahe.developpez.com

128/325

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.

package rdvmedecins.security;
import java.util.List;
import
import
import
import
import
import
import
import

org.junit.Assert;
org.junit.Test;
org.junit.runner.RunWith;
org.springframework.beans.factory.annotation.Autowired;
org.springframework.boot.test.SpringApplicationConfiguration;
org.springframework.security.core.authority.SimpleGrantedAuthority;
org.springframework.security.crypto.bcrypt.BCrypt;
org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import rdvmedecins.config.DomainAndPersistenceConfig;
import com.google.common.collect.Lists;
@SpringApplicationConfiguration(classes = DomainAndPersistenceConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class UsersTest {
@Autowired
private UserRepository userRepository;
@Autowired
private AppUserDetailsService appUserDetailsService;
@Test
public void findAllUsersWithTheirRoles() {
Iterable<User> users = userRepository.findAll();
for (User user : users) {
System.out.println(user);
display("Roles :", userRepository.getRoles(user.getId()));
}
}

@Test
public void findUserByLogin() {
// on rcupre l'utilisateur [admin]
User user = userRepository.findUserByLogin("admin");
// on vrifie que son mot de passe est [admin]
Assert.assertTrue(BCrypt.checkpw("admin", user.getPassword()));
// on vrifie le rle de admin / admin
List<Role> roles = Lists.newArrayList(userRepository.getRoles("admin",
user.getPassword()));
44.
Assert.assertEquals(1L, roles.size());
45.
Assert.assertEquals("ROLE_ADMIN", roles.get(0).getName());
46.
}
47.
48.
@Test
49.
public void loadUserByUsername() {
50.
// on rcupre l'utilisateur [admin]
51.
AppUserDetails userDetails = (AppUserDetails)
appUserDetailsService.loadUserByUsername("admin");
52.
// on vrifie que son mot de passe est [admin]
53.
Assert.assertTrue(BCrypt.checkpw("admin", userDetails.getPassword()));
54.
// on vrifie le rle de admin / admin
55.
@SuppressWarnings("unchecked")

http://tahe.developpez.com

129/325

56.

List<SimpleGrantedAuthority> authorities = (List<SimpleGrantedAuthority>)


userDetails.getAuthorities();
57.
Assert.assertEquals(1L, authorities.size());
58.
Assert.assertEquals("ROLE_ADMIN", authorities.get(0).getAuthority());
59.
}
60.
61.
// mthode utilitaire - affiche les lments d'une collection
62.
private void display(String message, Iterable<?> elements) {
63.
System.out.println(message);
64.
for (Object element : elements) {
65.
System.out.println(element);
66.
}
67.
}
68. }

lignes 27-34 : test visuel. On affiche tous les utilisateurs avec leurs rles ;
lignes 36-46 : on vrifie que l'utilisateur [admin] a le mot de passe [admin] et le rle [ROLE_ADMIN] en utilisant le
repository [UserRepository] ;
ligne 41 : [admin] est le mot de passe en clair. En base, il est crypt selon l'algorithme BCrypt. La mthode
[ BCrypt.checkpw] permet de vrifier que le mot de passe en clair une fois crypt est bien gal celui qui est en base ;
lignes 48-59 : on vrifie que l'utilisateur [admin] a le mot de passe [admin] et le rle [ROLE_ADMIN] en utilisant le
service [appUserDetailsService] ;

L'excution des tests russit avec les logs suivants :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.

2.14.7

User[guest,guest,$2a$10$Gzyp54mvkgMH0SPQkXo.Zeu.DvJ/Ql50PRXLf2FkolMTs7fr6A2J2]
Roles :
Role[ROLE_GUEST]
User[admin,admin,$2a$10$m79V6MKt9GPDdpjSulyqReqUioqYwXy8ollt/.ia15FhX2fym3AE6]
Roles :
Role[ROLE_ADMIN]
User[user,user,$2a$10$ph5y/1H89YC11oGVLB49fON.dZwnu44bAOKMK1FFl//xjAvsr/Ese]
Roles :
Role[ROLE_USER]
User[x,x,$2a$10$dAKd2SuQplR1iFhoBUUFs.XiA0lYxNqOmrkv97Gbr5KBoHzEi/5HG]
Roles :
Role[ROLE_GUEST]

Conclusion intermdiaire

L'ajout des classes ncessaires Spring Security a pu se faire avec peu de modifications du projet originel. Rappelons-les :

ajout d'une dpendance sur Spring Security dans le fichier [pom.xml] ;


cration de trois tables supplmentaires dans la base de donnes ;
cration d'entits JPA et de composants Spring dans le package [rdvmedecins.security] ;

Ce cas trs favorable dcoule du fait que les trois tables ajoutes dans la base de donnes sont indpendantes des tables existantes.
On aurait mme pu les mettre dans une base de donnes spare. Ceci a t possible parce qu'on a dcid qu'un utilisateur avait une
existence indpendante des mdecins et des clients. Si ces derniers avaient t des utilisateurs potentiels, il aurait fallu crer des liens
entre la table [USERS] et les tables [MEDECINS] et [CLIENTS]. Cela aurait eu alors un impact important sur le projet existant.

2.14.8

Le projet Eclipse de la couche [web]

Couche
[web /
JSON]

Couche
[metier]

Couche
[DAO]

Couche
[JPA]

Pilote
[JDBC]

SGBD

7 4
Spring

http://tahe.developpez.com

130/325

Le projet [rdvmedecins-webapi] prcdent est dupliqu dans le projet [rdvmedecins-webapi-v2] [1] :


1

Les seules modifications sont faire dans le package [rdvmedecins.web.config] o il faut configurer Spring Security. Nous avons
dj rencontr une classe de configuration de Spring Security :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.

package hello;
import org.springframework.context.annotation.Configuration;
import
org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/", "/home").permitAll().anyRequest().authenticated();
http.formLogin().loginPage("/login").permitAll().and().logout().permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
}
}

Nous allons suivre le mme dmarche :

ligne 11 : dfinir une classe qui tend la classe [WebSecurityConfigurerAdapter] ;


ligne 13 : dfinir une mthode [configure(HttpSecurity http)] qui dfinit les droits d'accs aux diffrentes URL du service
web ;
ligne 19 : dfinir une mthode [configure(AuthenticationManagerBuilder auth)] qui dfinit les utilisateurs et leurs rles ;

La configuration de Spring Security est assure par la classe [SecurityConfig] :


1.
2.
3.
4.
5.
6.

package rdvmedecins.web.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.http.HttpMethod;
import
org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
7. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
8. import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
9. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
10. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
11.
12. import rdvmedecins.security.AppUserDetailsService;
13.
14. @EnableAutoConfiguration
15. @EnableWebSecurity
16. public class SecurityConfig extends WebSecurityConfigurerAdapter {
17.
@Autowired
18.
private AppUserDetailsService appUserDetailsService;
19.

http://tahe.developpez.com

131/325

20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38. }

@Override
protected void configure(AuthenticationManagerBuilder registry) throws Exception {
// l'authentification est faite par le bean [appUserDetailsService]
// le mot de passe est crypt par l'algorithme de hachage Bcrypt
registry.userDetailsService(appUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// CSRF
http.csrf().disable();
// le mot de passe est transmis par le header Authorization: Basic xxxx
http.httpBasic();
// seul le rle ADMIN peut utiliser l'application
http.authorizeRequests() //
.antMatchers("/", "/**") // toutes les URL
.hasRole("ADMIN");
}

lignes 14-15 : on a repris les annotations de l'exemple ;


lignes 17-18 : la classe [AppUserDetails] qui donne accs aux utilisateurs de l'application est injecte ;
lignes 20-21 : la mthode [configure(HttpSecurity http)] dfinit les utilisateurs et leurs rles. Elle reoit en paramtre un
type [AuthenticationManagerBuilder]. Ce paramtre est enrichi de deux informations :
une rfrence sur le service [appUserDetailsService] de la ligne 18 qui donne accs aux utilisateurs enregistrs. On
notera ici que le fait qu'ils soient enregistrs dans une base de donnes n'apparat pas. Ils pourraient donc tre dans un
cache, dlivrs par un service web, ...
le type de cryptage utilis pour le mot de passe. On rappelle ici que nous avons utilis l'algorithme BCrypt ;
lignes 27-40 : la mthode [configure(HttpSecurity http)] dfinit les droits d'accs aux URL du service web ;
ligne 30 : nous avons vu dans le projet d'introduction que par dfaut Spring Security grait un jeton CSRF (Cross Site
Request Forgery) que l'utilisateur qui voulait s'authentifier devait renvoyer au serveur. Ici ce mcanisme est dsactiv ;
ligne 32 : on active le mode d'authentification par entte HTTP. Le client devra envoyer l'entte HTTP suivant :
Authorization:Basic code

o code est le codage de la chane login:password par l'algorithme Base64. Par exemple, le codage Base64 de la chane
admin:admin est YWRtaW46YWRtaW4=. Donc l'utilisateur de login [admin] et de mot de passe [admin] enverra l'entte
HTTP suivant pour s'authentifier :
Authorization:Basic YWRtaW46YWRtaW4=

lignes 34-36 : indiquent que toutes les URL du service web sont accessibles aux utilisateurs ayant le rle [ROLE_ADMIN].
Cela veut dire qu'un utilisateur n'ayant pas ce rle ne peut accder au service web ;

La classe [AppConfig] qui configure l'ensemble de l'application volue comme suit :

1.
2.
3.
4.
5.
6.
7.
8.
9.

package rdvmedecins.web.config;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import rdvmedecins.config.DomainAndPersistenceConfig;
@EnableAutoConfiguration

http://tahe.developpez.com

132/325

10.
11.
12.
13.
14.

@ComponentScan(basePackages = { "rdvmedecins.web" })
@Import({ DomainAndPersistenceConfig.class, SecurityConfig.class })
public class AppConfig {

la modification a lieu ligne 11 : on indique qu'il y a maintennat deux fichiers de configuration exploiter
[DomainAndPersistenceConfig] et [SecurityConfig].

2.14.9

Tests du service web

Nous allons tester le service web avec le client Chrome [Advanced Rest Client]. Nous allons avoir besoin de prciser l'entte HTTP
d'authentification :
Authorization:Basic code

o [code] est le code Base64 de la chane [login:password]. Pour gnrer ce code, on peut utiliser le programme suivant :

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.

package rdvmedecins.helpers;
import org.springframework.security.crypto.codec.Base64;
public class Base64Encoder {
public static void main(String[] args) {
// on attend deux arguments : login password
if (args.length != 2) {
System.out.println("Syntaxe : login password");
System.exit(0);
}
// on rcupre les deux arguments
String chane = String.format("%s:%s", args[0], args[1]);
// on encode la chane
byte[] data = Base64.encode(chane.getBytes());
// on affiche son encodage Base64
System.out.println(new String(data));
}
}

Si nous excutons ce programme avec les deux arguments [admin admin] :

nous obtenons le rsultat suivant :


YWRtaW46YWRtaW4=

http://tahe.developpez.com

133/325

Maintenant que nous savons gnrer l'entte HTTP d'authentification, nous lanons le service web maintenant scuris. Puis avec le
client Chrome [Advanced Rest Client], nous demandons la liste des tous les mdecins :

1
2
3

en [1], nous demandons l'URL des mdecins ;


en [2], avec une mthode GET ;
en [3], nous donnons l'entte HTTP de l'authentification. Le code [YWRtaW46YWRtaW4=] est le codage Base64 de la
chane [admin:admin] ;
en [4], nous envoyons la commande HTTP ;

La rponse du serveur est la suivante :

http://tahe.developpez.com

134/325

en [1], l'entte HTTP d'authentification ;


en [2], le serveur renvoie une rponse JSON ;
en [3], la liste des mdecins.

Tentons maintenant une requte HTTP avec un entte d'authentification incorrect. La rponse est alors la suivante :

http://tahe.developpez.com

135/325

en [1] et [3] : l'entte HTTP d'authentification ;


en [2] : la rponse du service web ;

Maintenant, essayons l'utilisateur user / user. Il existe mais n'a pas accs au service web. Si nous excutons le programme
d'encodage Base64 avec les deux arguments [user user] :

nous obtenons le rsultat suivant :


dXNlcjp1c2Vy

http://tahe.developpez.com

136/325

2.15

en [1] et [3] : l'entte HTTP d'authentification ;


en [2] : la rponse du service web. Elle est diffrente de la prcdente qui tait [401 Unauthorized]. Cette fois-ci,
l'utilisateur s'est authentifi correctement mais n'a pas les droits suffisants pour accder l'URL ;

Conclusion

Rapelons l'architecture globale de notre application client / serveur :

http://tahe.developpez.com

137/325

Un service web scuris est maintenant oprationnel. On verra qu'il devra tre modifi suite des problmes qui vont se rvler la
construction du client Angular JS. Mais nous attendrons de rencontrer le problme pour le rsoudre. Nous allons maintenant
construire le client Angular qui va offrir une interface web pour grer les rendez-vous des mdecins.

http://tahe.developpez.com

138/325

3 Le client Angular JS
3.1

Rfrences du framework Angular JS

Deux rfrences ont t donnes pour le framework Angular JS au dbut de ce document. Nous les redonnons ici :

[ref1] : le livre " Pro AngularJS " crit par Adam Freeman aux ditions Apress. C'est un excellent livre. Les codes
source des exemples de ce livre sont disponibles gratuitement l'URL
[http://www.apress.com/downloadable/download/sample/sample_id/1527/];
[ref2] : la documentation officielle d'Angular JS [https://docs.angularjs.org/guide];

Angular JS mrite un livre lui tout seul. Celui d'Adam Freeman a plus de 600 pages et elles ne sont pas gaspilles. Nous allons
dcrire une application Angular et au cours de cette description nous serons amens parler des fondamentaux de ce framework.
Nanmoins, nous nous en tiendrons aux seules explications ncessaires la comprhension de la solution propose. Angular est un
framework extrmement riche et il existe de nombreuses solutions pour arriver au mme rsultat. C'est une difficult car lorsqu'on
dbute, on ne sait pas si on utilise une solution moins bonne ou meilleure qu'une autre. C'est le cas de la solution propose ici. Elle
pourrait tre crite diffremment et peut-tre avec de meilleures pratiques.

3.2

Architecture du client Angular

L'architecture du client Angular ressemble celle d'une application web MVC classique avec quelques diffrences. Une application
web Spring MVC a par exemple l'architecture suivante :

Application web / serveur


couche [web]
2b
2a
Dispatcher
Servlet
Contrleurs/

Navigateur
4b

Vue1
Vue2
Vuen

Modles

Actions

couches
[mtier, DAO, JPA]

Donnes

2c

Le traitement d'une demande d'un client se droule de la faon suivante :


1. demande - les URL demandes sont de la forme http://machine:port/contexte/Action/param1/param2/....?p1=v1&p2=v2&... La
[Dispatcher Servlet] est la classe de Spring qui traite les URL entrantes. Elle "route" l'URL vers l'action qui doit la traiter. Ces
actions sont des mthodes de classes particulires appeles [Contrleurs]. Le C de MVC est ici la chane [Dispatcher Servlet,
Contrleur, Action]. Si aucune action n'a t configure pour traiter l'URL entrante, la servlet [Dispatcher Servlet] rpondra que
l'URL demande n'a pas t trouve (erreur 404 NOT FOUND) ;
2. traitement

l'action choisie peut exploiter les paramtres parami que la servlet [Dispatcher Servlet] lui a transmis. Ceux-ci peuvent
provenir de plusieurs sources :

du chemin [/param1/param2/...] de l'URL,

des paramtres [p1=v1&p2=v2] de l'URL,

de paramtres posts par le navigateur avec sa demande ;

dans le traitement de la demande de l'utilisateur, l'action peut avoir besoin de la couche [metier] [2b]. Une fois la
demande du client traite, celle-ci peut appeler diverses rponses. Un exemple classique est :

une page d'erreur si la demande n'a pu tre traite correctement

une page de confirmation sinon

l'action demande une certaine vue de s'afficher [3]. Cette vue va afficher des donnes qu'on appelle le modle de la
vue. C'est le M de MVC. L'action va crer ce modle M [2c] et demander une vue V de s'afficher [3] ;
3. rponse - la vue V choisie utilise le modle M construit par l'action pour initialiser les parties dynamiques de la rponse HTML
qu'elle doit envoyer au client puis envoie cette rponse.
L'architecture de notre client Angular sera analogue avec une terminologie un peu diffrente. Tout d'abord les applications Angular
sont gnralement des applications web page unique (APU) ou Single Page Application (SPA) :

http://tahe.developpez.com

139/325

l'utilisateur demande l'URL initiale de l'application sous la forme : http://machine:port/contexte. Le navigateur va interroger
un serveur web pour obtenir le document demand. Celui-ci est une page HTML stylise par du CSS et rendue dynamique
par du Javascript ;
ensuite l'utilisateur va interagir avec les vues qui lui sont prsentes. On peut distinguer diverses sortes d'interactions :
celles qui ne ncessitent aucune interaction avec l'extrieur, par exemple cacher / montrer des lments de la vue. Elle
sont traites par le Javascript embarqu ;
celles qui ncessitent des donnes provenant d'un service web distant. Elles vont tre rcupres par un appel AJAX
(Asynchronous Javascript And Xml), un modle va tre construit et une vue affiche ;
celles qui ncessitent une autre vue que la vue initiale. Elle va tre demande par un appel Ajax au serveur qui a
dlivr la page initiale. Puis le processus prcdent va se rpter. La page obtenue va tre mise en cache dans le
navigateur. Au prochain appel, elle ne sera pas demande au serveur HTML distant ;

Au final, le navigateur ne fait qu'un seul appel HTTP, celui qui obtient la page initiale. Les appels HTTP suivants, vers le serveur de
pages HTML ou des services web distants, sont faits par le Javascript embarqu dans les pages.
Nous prsentons maintenant l'architecture de l'application au sein du navigateur. Nous oublions le serveur HTML qui dlivre les
pages HTML de l'application. Pour l'explication, on peut considrer qu'elles sont toutes prsentes au sein du cache du navigateur.

Application web / navigateur


couche [prsentation]

couche [services]

Routeur
10

Utilisateur
2

V1

C1

6
8

Service 1
Service 2

M1

Donnes
rseau

11

Vn

Cn
Mn

DAO

Tout d'abord, il faut situer cette architecture :

en [1], on est dans un navigateur ;

en [2], un utilisateur interagit avec les vues affiches par celui-ci ;

en [3], les donnes sont cherches sur le rseau, souvent auprs de services web ;

http://tahe.developpez.com

140/325

L'utilisateur interagit avec des vues : il remplit des formulaires et les valide. Explicitons ce processus avec la vue V1 ci-dessus. On
supposera que c'est la vue initiale de l'application. Elle a t obtenue de la faon suivante :

l'utilisateur demande l'URL initiale de l'application sous la forme : http://machine:port/contexte ;


le navigateur a demand le document associ cette URL. Il a reu la page HTML / CSS / JS de la vue V1 ;
le Javascript embarqu dans la page a alors pris la main et a donn le contrle au contrleur C1 [5] ;
celui-ci a construit le modle M1 [8] [9] de la vue V1. La construction de ce modle a pu ncessiter l'utilisation de services
internes [6] et l'interrogation de services externes [7] ;

L'utilisateur a maintenant une vue V1 devant lui. Imaginons que c'est un formulaire. Il le remplit puis le valide :

en [4], l'utilisateur valide le formulaire ;

en [5], cet vnement va tre trait par l'une des mthodes du contrleur C1 ;
Si l'vnement n'entrane qu'un simple changement de la vue V1 (cacher / montrer des zones), le contrleur C1 va modifier le
modle M1 de la vue V1 puis afficher de nouveau la vue V1. Il peut pour ce faire avoir besoin de l'un des services de la couche
[services] [6].
Si l'vnement ncessite des donnes externes :

en [6], le contrleur C1 va demander la couche [DAO] de les obtenir ;

en [7], celle-ci va faire un ou plusieurs appels AJAX pour les obtenir ;

en [8] et [9], le modle M1 va tre modifi et la vue V1 affiche ;


Si l'vnement entrane un changement de vue, dans les deux cas prcdents, au lieu d'afficher la vue V1, le contrleur C1 va
demander une nouvelle URL [10]. C'est une URL interne au navigateur. Elle ne se traduit pas immdiatement par un appel HTTP
au serveur de pages HTML. Ce changement d'URL est trait par un routeur configur de telle sorte qu' chaque URL interne
correspond une vue V et son contrleur C. Le routeur fait alors afficher la nouvelle vue Vn. Avant l'affichage, son contrleur Cn
prend la main, contruit le modle Mn puis fait afficher la vue Vn [11]. Si la page HTML de la vue Vn n'tait pas en cache dans le
navigateur, alors elle sera demande au serveur de pages HTML.
La couche [Prsentation] de cette achitecture est proche de l'architecture JSF (Java Server Faces) :

la vue V correspond la vue de type Facelet de JSF ;


le contrleur C correspond au bean JSF, une classe Java qui contient la fois le modle M de la vue V et les gestionnaires
des vnements de celle-ci ;

La couche [Services] est diffrente des couches [Services] auxquelles on est habitu. En dveloppement web, ct serveur, on a le
plus souvent l'architecture en couches suivante :

Couche
[web]

Couche
[metier]

Couche
[DAO]

Couche
[JPA]

Pilote
[JDBC]

SGBD

Ci-dessus, la couche [web] ne communique avec la couche [DAO] qu'au travers de la couche [mtier]. Rien ne nous empcherait
d'injecter dans la couche [web] une rfrence sur la couche [DAO] qui permettrait cette communication. Mais on se l'interdit.
Avec Angular, on ne se l'interdit pas. L'architecture devient alors la suivante :

http://tahe.developpez.com

141/325

Application web / navigateur


couche [prsentation]

couche [services]

Service 1
Service ...
1

Mtier

Donnes
rseau

DAO

3.3

en [1], la couche [prsentation] peut communiquer directement avec n'importe quel service ;
en [2], les services se connaissent entre-eux. Un service peut en utiliser un ou plusieurs autres.

Les vues du client Angular

Les vues du client Angular ont dj t prsentes au paragraphe 1.3.3, page 11. Pour faciliter la lecture de ce nouveau chapitre,
nous les redonnons ici. La premire vue est la suivante :

9
10

11

12

13

14

en [6], la page d'entre de l'application. Il s'agit d'une application de prise de rendez-vous pour des mdecins ;
en [7], une case cocher qui permet d'tre ou non en mode [debug]. Ce dernier se caractrise par la prsence du cadre [8]
qui affiche le modle de la vue courante ;
en [9], une dure d'attente artificielle en millisecondes. Elle vaut 0 par dfaut (pas d'attente). Si N est la valeur de ce temps
d'attente, toute action de l'utilisateur sera excute aprs un temps d'attente de N millisecondes. Cela permet de voir la
gestion de l'attente mise en place par l'application ;
en [10], l'URL du serveur Spring 4. Si on suit ce qui a prcd, c'est [http://localhost:8080];
en [11] et [12], l'identifiant et le mot de passe de celui qui veut utiliser l'application. Il y a deux utilisateurs : admin/admin
(login/password) avec un rle (ADMIN) et user/user avec un rle (USER). Seul le rle ADMIN a le droit d'utiliser
l'application. Le rle USER n'est l que pour montrer ce que rpond le serveur dans ce cas d'utilisation ;

http://tahe.developpez.com

142/325

en [13], le bouton qui permet de se connecter au serveur ;


en [14], la langue de l'application. Il y en a deux : le franais par dfaut et l'anglais.

en [1], on se connecte ;

une fois connect, on peut choisir le mdecin avec lequel on veut un rendez-vous [2] et le jour de celui-ci [3] ;
on demande en [4] voir l'agenda du mdecin choisi pour le jour choisi ;

http://tahe.developpez.com

143/325

une fois obtenu l'agenda du mdecin, on peut rserver un crneau [5] ;

en [6], on choisit le patient pour le rendez-vous et on valide ce choix en [7] ;

http://tahe.developpez.com

144/325

Une fois le rendez-vous valid, on est ramen automatiquement l'agenda o le nouveau rendez-vous est dsormais inscrit. Ce
rendez-vous pourra tre ultrieurement supprim [7].
Les principales fonctionnalits ont t dcrites. Elles sont simples. Celles qui n'ont pas t dcrites sont des fonctions de navigation
pour revenir une vue prcdente. Terminons par la gestion de la langue :

http://tahe.developpez.com

145/325

en [1], on passe du franais l'anglais ;

3.4

en [2], la vue est passe en anglais, y-compris le calendrier ;

Configuration du projet Angular

Nous allons construire notre client Angular de faon progressive. Nous utilisons l'IDE Webstorm.
Crons un dossier vide [rdvmedecins-angular-v1] puis ouvrons-le avec Webstorm :
3

en [1], on ouvre un dossier ;


en [2], on dsigne le dossier que nous avons cr ;
en [3], nous obtenons un projet Webstorm vide ;

http://tahe.developpez.com

146/325

7
5

en [4], la configuration du projet se fait par l'option [File / Settings] ;


en [5] et [6], on configure la proprit [Spelling] qui gre la vrification orthographique. Par dfaut, celle-ci est active.
Comme le logiciel tlcharg est en langue anglaise, nos commentaires en franais des programmes vont tre souligns
comme de possibles erreurs d'orthographe. On dsactive donc cette vrification orthographique [7] ;

10

9
12

11

en [8], on cre un nouveau fichier ;


en [9], on choisit de crer le fichier [package.json] qui dcrit l'application avec une syntaxe JSON ;
en [10], le fichier gnr qu'on modifie comme montr en [11] ;
en [12], on sauvegarde ce fichier la fois dans [package.json] et [bower.json] ;

http://tahe.developpez.com

147/325

13

en [13], on configure de nouveau le projet ;

15

14

en [14], on configure la proprit [Javascript / Bower] qui va nous permettre de dclarer les bibliothques Javascript dont
nous avons besoin ;
en [15], dsigner le fichier [bower.json] que nous venons de crer ;
18
16
19
17

20

en [16], ajoutons une bibliothque Javascript ;


en [17] sont affiches toutes les bibliothques Javascript tlchargeables ;
en [18], nous pouvons mettre un terme pour filtrer la liste [17]. Ici, nous indiquons que nous voulons la bibliothque
[Angular JS] ;
en [19], apparaissent les caractristiques de la bibliothque. On voit ici que la version 1.2.18 d'Angular va tre tlcharge ;

http://tahe.developpez.com

148/325

en [20], on la tlcharge ;

21

22

23

en [21], on voit qu'elle a t tlcharge ;


en [22], on voit la version tlcharge. C'est donc en ralit la 1.2.19 ;
en [23], on voit la dernire version disponible ;

24

en [24], suivant la mme dmarche que prcdemment, on tlcharge les bibliothques suivantes :

angular-base64

pour encoder la chane " user:password " en Base64 ; https://github.com/ninjatronic/angular-base64

angular-i18n

pour internationaliser le calendrier

angular-route

pour router les URL internes de l'application vers le https://docs.angularjs.org/api/ngRoute


bon contrleur et la bonne vue ;

angulartranslate

permet l'internationalisation des vues. C'est un projet https://github.com/angular-translate/angularindpendant d'Angular. Ici, deux langues seront translate
utilises : le franais et l'anglais ;

angular-uibootstrap-bower

fournit des composants visuels compatibles https://github.com/angular-ui/bootstrap


Bootstrap. On utilisera ici son calendrier ;

bootstrap

le framework CSS Bootstrap. Sera utilis pour http://getbootstrap.com/


construire les vues ;

footable

fournit un composant visuel de type " tableau ". Il http://themergency.com/footable/


est " responsive " en ce sens qu'il peut s'adapter la
taille de l'cran ;

bootstrap-select

fournit un composant de type " liste droulante " ;

http://tahe.developpez.com

https://code.angularjs.org/1.2.18/i18n/

http://silviomoreto.github.io/bootstrap-select/

149/325

25

26

en [25], les bibliothques tlcharges ont t installes dans le dossier [bower_components] ;


en [26], on voit que la bibliothque JQuery a t tlcharge. C'est parce que Bootstrap l'utilise. Le systme d'installation
des dpendances Javascript d'un projet est analogue celui de Maven pour le monde Java : si une bibliothque tlcharge
a elle-mme des dpendances, celles-ci sont automatiquement tlcharges ;

Le fichier [bower.json] a volu :


1. {
2.
"name": "rdvmedecins-angular",
3.
"version": "0.0.1",
4.
"dependencies": {
5.
"angular": "~1.2.18",
6.
"angular-base64": "~2.0.2",
7.
"angular-route": "~1.2.18",
8.
"angular-translate": "~2.2.0",
9.
"bootstrap": "~3.1.1",
10.
"footable": "~2.0.1",
11.
"angular-ui-bootstrap-bower": "~0.11.0",
12.
"bootstrap-select": "~1.5.2"
13. }
14. }

Toutes les dpendances tlcharges ont t inscrites dans le fichier.

3.5

La page initiale du client Angular

Nous crons une premire version de la page initiale du client Angular :

http://tahe.developpez.com

150/325

en [1] et [2], nous crons un fichier HTML nomm [app-01] [3] et [4] ;

Le fichier [app-01.html] va tre notre page principale pendant un moment. Nous allons y configurer l'importation des fichiers CSS
et JS dont l'application a besoin :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.

<!DOCTYPE html>
<html>
<head>
<title>RdvMedecins</title>
<!-- META -->
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Angular client for RdvMedecins">
<meta name="author" content="Serge Tah">
<!-- le CSS -->
<link href="bower_components/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="bower_components/bootstrap/dist/css/bootstrap-theme.min.css" rel="stylesheet"/>
<link href="bower_components/bootstrap-select/bootstrap-select.min.css" rel="stylesheet"/>
<link href="bower_components/footable/css/footable.core.min.css" rel="stylesheet"/>
</head>
<body>
<div class="container">
<h1>Rdvmedecins - v1</h1>
</div>
<!-- Bootstrap core JavaScript ================================================== -->
<script type="text/javascript" src="bower_components/jquery/dist/jquery.min.js"></script>
<script type="text/javascript" src="bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
<script type="text/javascript" src="bower_components/bootstrap-select/bootstrap-select.min.js"></script>
<script type="text/javascript" src="bower_components/footable/dist/footable.min.js"></script>
<!-- angular js -->
<script type="text/javascript" src="bower_components/angular/angular.min.js"></script>
<script type="text/javascript" src="bower_components/angular-ui-bootstrap-bower/ui-bootstraptpls.min.js"></script>
<script type="text/javascript" src="bower_components/angular-route/angular-route.min.js"></script>
<script type="text/javascript" src="bower_components/angular-translate/angular-translate.min.js"></script>
<script type="text/javascript" src="bower_components/angular-base64/angular-base64.min.js"></script>
</body>
</html>

lignes 11-12 : les fichiers CSS pour Bootstrap ;


ligne 13 : le fichier CSS pour le composant [boostrap-select] ;
ligne 14 : le fichier CSS pour le composant [footable] ;
lignes 21-24 : les fichiers JS des composants Bootstrap ;
ligne 21 : les composants Bootstrap sont propulss par JQuery ;
ligne 22 : le fichier JS de Bootstrap ;
ligne 23 : le fichier JS pour le composant [boostrap-select] ;
ligne 24 : le fichier JS pour le composant [footable] ;
lignes 26-30 : les fichiers JS d'Angular et des projets qui s'y raccrochent ;
ligne 26 : le fichier JS d'Angular. Il doit tre charg aprs JQuery si cette bibliothque est utilise ;
ligne 27 : le fichier JS du projet [angular-ui-bootstrap] ;
ligne 28 : le fichier JS du routeur [angular-route] ;
ligne 29 : le fichier JS du module d'internationalisation des applications Angular ;
ligne 30 : le fichier JS du module [angular-base64] ;

La validit du fichier [app-01.html] peut tre vrifie :

http://tahe.developpez.com

151/325

en [1], on demande l'inspection du code ;


en [2], le rsultat lorsque tout va bien ;

Cette inspection systmatique du code avant son excution est conseille. Ici, cette dtection permet de dtecter toute erreur de
rfrence des fichiers CSS et JS. Si un chemin est incorrect, l'inspecteur de code le signalera.

en [3], la page peut tre charge dans un navigateur par un dbogueur. On obtient le rsultat suivant dans le navigateur :

4
5

en [4], la page [app-01.html] a t dlivre par un serveur interne Webstorm oprant ici sur le port 63342 ;
en [5], la console du dbogueur. Si des erreurs s'taient produites, elles seraient apparues ici. C'est galement l que vont
les affichages cran produits par l'instruction [console.log(expression)] du Javascript. Nous utiliserons abondamment
cette possibilit ;

Le mode dbogage permet de modifier la page dans Webstorm et de voir les rsultats de ces modifications dans le navigateur sans
avoir recharger la page. Ainsi si nous ajoutons la ligne 3 ci-dessous :
1. <div class="container">
2.
<h1>Rdvmedecins - v1</h1>
3.
<h2>Version 1</h2>
4. </div>

et que nous revenons au navigateur, nous constatons que la page a chang :

http://tahe.developpez.com

152/325

3.6

Dcouverte de Bootstrap

Nous allons illustrer maintenant certaines des caractristiques de Bootstrap utilises dans l'application. Je n'ai qu'une connaissance
limite de ce framework, obtenue par des copier / coller de codes trouvs sur Internet. J'expliquerai le rle des classes CSS que je
crois comprendre. Je m'abstiendrai de commenter les autres.

3.6.1

Exemple 1

Dans Angular, les oprations qui vont chercher de l'information l'extrieur sont asynchrones. Cela signifie que l'opration est
lance et qu'il y a retour immdiat la vue avec laquelle l'utilisateur peut continuer interagir. L'application est avertie de la fin de
l'opration par un vnement. Cet vnement est trait par une fonction JS qui peut alors enrichir la vue actuelle ou en changer. Si
l'opration est susceptible d'tre longue, il est utile d'offrir l'utilisateur la possibilit de l'annuler. Nous la lui offrirons
systmatiquement. Pour cela, nous utiliserons un bandeau Bootstrap :

Pour obtenir ce rsultat, nous dupliquons [app-01.html] dans [app-02.html] et nous modifions les ligne suivantes :
1. <div class="container">
2.
<h1>Rdvmedecins - v1</h1>
3.
<div class="alert alert-warning">
4.
<h1>Opration en cours. Veuillez patienter...
5.
<button class="btn btn-primary pull-right">Annuler</button>
6.
<img src="assets/images/waiting.gif" alt=""/>
7.
</h1>
8.
</div>
9. </div>

3.6.2

ligne 1 : la classe CSS [container] dfinit une zone d'affichage l'intrieur du navigateur ;
ligne 3 : la classe CSS [alert] affiche une zone colore. La classe [alert-warning] utilise une couleur prdfinie ;
ligne 5 : la classe [btn] habille un bouton. La classe [btn-primary] lui donne une certaine couleur. La classe [pull-right]
l'envoie sur la droite du bandeau d'alerte ;
ligne 6 : une image anime d'attente ;

Exemple 2

Les diffrentes vues de l'application auront un titre commun :

http://tahe.developpez.com

153/325

Pour obtenir ce rsultat, nous dupliquons [app-01.html] dans [app-03.html] et nous modifions les ligne suivantes :
1. <div class="container">
2.
<h1>Rdvmedecins - v1</h1>
3.
<!-- Bootstrap Jumbotron -->
4.
<div class="jumbotron">
5.
<div class="row">
6.
<div class="col-md-2">
7.
<img src="assets/images/caduceus.jpg" alt="RvMedecins"/>
8.
</div>
9.
<div class="col-md-10">
10.
<h1>Les Mdecins associs</h1>
11.
</div>
12.
</div>
13. </div>
14. </div>

3.6.3

la zone colore est obtenue avec la classe [jumbotron] de la ligne 4 ;


ligne 5 : la classe [row] dfinit une ligne 12 colonnes ;
ligne 6 : la classe [col-md-2] dfinit une zone de deux colonnes dans la ligne ;
ligne 7 : dans ces deux colonnes on met une image ;
lignes 9-11 : dans les 10 autres colonnes, on met le texte ;

Exemple 3

Les vues auront un bandeau haut de commande. On y trouvera des options de commande, liens ou boutons. On y trouvera
gelement des lments de formulaire. Par exemple :

Pour obtenir ce rsultat, nous dupliquons [app-01.html] dans [app-04.html] et nous modifions les ligne suivantes :
1. <div class="container">
2.
<h1>Rdvmedecins - v1</h1>
3.
4.
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
5.
<div class="container">

http://tahe.developpez.com

154/325

6.
7.

<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbarcollapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">RdvMedecins</a>
</div>

8.
9.
10.
11.
12.
13.
14.
15.
<div class="navbar-collapse collapse">
16.
<form class="navbar-form navbar-right">
17.
<!-- mode debug -->
18.
<label style="width: 100px">
19.
<input type="checkbox">
20.
<span style="color: white">Debug</span>
21.
</label>
22.
<!-- formulaire d'identification -->
23.
<div class="form-group">
24.
<input type="text" class="form-control" placeholder="Temps d'attente"
25.
style="width: 150px"/>
26.
<input type="text" class="form-control" placeholder="URL du service web"
27.
style="width: 200px"/>
28.
<input type="text" class="form-control" placeholder="Login"
29.
style="width: 100px"/>
30.
<input type="password" class="form-control" placeholder="Mot de passe"
31.
style="width: 100px"/>
32.
</div>
33.
<button class="btn btn-success">
34.
Connexion
35.
</button>
36.
</form>
37.
</div>
38.
<button class="btn btn-success">
39.
Connexion
40.
</button>
41.
</form>
42.
</div>
43.
</div>
44. </div>
45. </div>

3.6.4

ligne 4 : la classe [navbar] va styler la barre de navigation. La classe [navbar-inverse] lui donne le fond noir. La classe
[navbar-fixed-top] va faire en sorte que lorsqu'on 'scrolle' la page affiche par le navigateur, la barre de navigation va
rester en haut de l'cran ;
lignes 6-14 : dfinissent la zone [1]. C'est typiquement une srie de classes que je ne comprends pas. J'utilise le composant
tel quel ;
ligne 15 : dfinissent une zone 'responsive' de la barre de commande. Sur un smartphone, cette zone disparat dans une
zone de menu ;
ligne 16 : la classe [navbar-form] habille un formulaire de la barre de commande. La classe [navbar-right] le rejette
droite de celle-ci ;
lignes 23-32 : les quatre zones de saisie du formulaire de la ligne 17 [3]. Elles sont l'intrieur d'une classe [ form-group]
qui habille les lments d'un formulaire et chacune d'elles a la classe [form-control] ;
ligne 33 : la classe [btn] qu'on a dj rencontre, enrichie de la classe [btn-success] qui lui donne sa couleur verte ;

Exemple 4

Le bandeau de commande permettra de changer de langue grce une liste droulante :

http://tahe.developpez.com

155/325

Pour obtenir ce rsultat, nous dupliquons [app-01.html] dans [app-05.html] et nous ajoutons les ligne suivantes la barre de
commande :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22. </form>

<button class="btn btn-success">


Connexion
</button>
<!-- langues -->
<div class="btn-group">
<button type="button" class="btn btn-danger">
Langues
</button>
<button type="button" class="btn btn-danger dropdown-toggle" data-toggle="dropdown">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu" role="menu">
<li>
<a href="">Franais</a>
</li>
<li>
<a href="">English</a>
</li>
</ul>
</div>

Les lignes rajoutes sont les lignes 4-21.

3.6.5

ligne 5 : la classe [btn-group] habille un groupe de boutons. Il y en a deux aux lignes 6 et 9 ;


lignes 6-8 : le premier bouton dfinit le libell de la liste droulante. La classe [btn-danger] lui donne sa couleur rouge ;
lignes 9-12 : le second bouton est celui de la liste droulante. Il est accol au premier, ce qui donne l'impression d'un
composant unique ;
ligne 10 : affiche la flche descendante indiquant que le bouton est une liste droulante ;
ligne 11 : pour des 'screen readers' ;
lignes 13-20 : les lments de la liste droulante sont les lments d'une liste non ordonne ;

Exemple 5

Pour valider un formulaire ou pour naviguer, l'utilisateur disposera dans la barre de commande d'options ou de boutons comme cidessous :

1
Des options de menu ont t installes en [1]. Pour obtenir ce rsultat, nous dupliquons [app-01.html] dans [app-06.html] et nous
ajoutons les ligne suivantes :
1. <div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
2.
<div class="container">

http://tahe.developpez.com

156/325

3.
<div class="navbar-header">
4. ...
5.
</div>
6.
<!-- options de menu -->
7.
<div class="collapse navbar-collapse">
8.
<ul class="nav navbar-nav">
9.
<li class="active">
10.
<a href="">
11.
<span>Home</span>
12.
</a>
13.
</li>
14.
<li class="active">
15.
<a href="">
16.
<span>Agenda</span>
17.
</a>
18.
</li>
19.
<li class="active">
20.
<a href="">
21.
<span>Valider</span>
22.
</a>
23.
</li>
24.
<li class="active">
25.
<a href="">
26.
<span>Annuler</span>
27.
</a>
28.
</li>
29.
</ul>
30.
<!-- boutons de droite -->
31.
<form class="navbar-form navbar-right" role="form">
32. ...
33.
</form>
34.
</div>
35.
</div>
36. </div>
37. </div>

3.6.6

les options de menu sont obtenues par les lignes 8-29. Ce sont l encore des lments d'une liste <ul>. La classe [active]
fait que le texte est brillant indiquant par l qu'on peut cliquer sur l'option.

Exemple 6

Nous prsenterons les mdecins et les clients dans des listes droulantes comme ci-dessous :

La liste droulante utilise n'est pas un composant natif Bootstrap. C'est le composant [bootstrap-select]
(http://silviomoreto.github.io/bootstrap-select/). Pour obtenir ce rsultat, nous dupliquons [app-01.html] dans [app-07.html] et
nous ajoutons les ligne suivantes :

http://tahe.developpez.com

157/325

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.

<!DOCTYPE html>
<html>
<head>
...
<link href="bower_components/bootstrap-select/bootstrap-select.min.css" rel="stylesheet"/>
</head>
<body>
<div class="container">
<h1>Rdvmedecins - v1</h1>
<h2><label for="medecins">Mdecins</label></h2>
<select id="medecins" data-style="btn btn-primary" class="selectpicker">
<option value="1">Mme Marie PELISSIER</option>
<option value="1">Mr Jacques BROMARD</option>
<option value="1">Mr Philippe JANDOT</option>
<option value="1">Mme Justine JACQUEMOT</option>
</select>
</div>
<!-- Bootstrap core JavaScript ================================================== -->
...
<script type="text/javascript" src="bower_components/bootstrap-select/bootstrapselect.min.js"></script>
<!-- script local -->
<script>
$('.selectpicker').selectpicker();
</script>
</body>

23.
24.
25.
26.
27.
28. </html>

3.6.7

ligne 5 : il faut importer la feuille de style de [bootstrap-select] ;


ligne 13 : l'attribut [data-style] est exploit par [bootstrap-select]. Il sert donner un style la liste droulante. Ici, on lui
donne la forme d'un bouton bleu [btn-primary] ;
ligne 13 : l'attribut [class] est exploit ligne 23. Peut tre quelconque ;
lignes 14-17 : les lments de la liste droulante. On a l les balises HTML classiques ;
ligne 22 : il faut importer le JS de [bootstrap-select] ;
lignes 24-26 : un script JS excut la fin du chargement de la page ;
ligne 25 : une instruction JQuery. On applique la mthode [selectpicker] (selectpicker()) tous les lments ayant la classe
[selectpicker] ($('.selectpicker')). Il n'y en a qu'un, la balise <select> de la ligne 13. La mthode [selectpicker] vient du
fichier JS rfrenc ligne 22 ;

Exemple 7

Pour afficher l'agenda d'un mdecin, nous allons utiliser un tableau 'responsive' fourni par la bibliothque JS [footable] :

http://tahe.developpez.com

158/325

en [1] : le tableau avec un affichage normal ;


en [2] : le tableau lorsqu'on rduit la taille de la fentre du navigateur. La colonne [Action] passe automatiquement la
ligne suivante. C'est ce qu'on appelle un composant 'responsive' ou simplement adaptable.

Nous dupliquons [app-01.html] dans [app-08.html] et nous ajoutons les ligne suivantes :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.

...
<link href="bower_components/footable/css/footable.core.min.css" rel="stylesheet"/>
<link href="assets/css/rdvmedecins.css" rel="stylesheet"/>
...
<div class="container">
<h1>Rdvmedecins - v1</h1>
<div class="row alert alert-warning">
<div class="col-md-6">
<table id="creneaux" class="table">
<thead>
<tr>
<th data-toggle="true">
<span>Crneau horaire</span>
</th>
<th>
<span>Client</span>
</th>
<th data-hide="phone">
<span>Action</span>
</th>
</thead>
<tbody>
<tr>
<td>
<span class='status-metro status-active'>
9h00-9h20
</span>
</td>
<td>
<span></span>
</td>
<td>
<a href="" class="status-metro status-active">
Rserver
</a>
</td>
</tr>
<tr>
<td>
<span class='status-metro status-suspended'>
9h20-9h40
</span>
</td>
<td>
<span>Mme Paule MARTIN</span>
</td>
<td>
<a href="" class="status-metro status-suspended">
Supprimer
</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
...
<script src="bower_components/footable/dist/footable.min.js" type="text/javascript"></script>

http://tahe.developpez.com

159/325

les lignes 2 et 60 sont dj prsentes dans [app-01.html]. Ce sont les fichiers CSS et JS fournis par la bibliothque
[footable] ;
la ligne 3 rfrence le fichier CSS suivant :
@CHARSET "UTF-8";
#creneaux th {
text-align: center;
}
#creneaux td {
text-align: center;
font-weight: bold;
}
.status-metro {
display: inline-block;
padding: 2px 5px;
color:#fff;
}
.status-metro.status-active {
background: #43c83c;
}
.status-metro.status-suspended {
background: #fa3031;
}

Les styles [status-*] proviennent d'un exemple d'utilisation de la table [footable] trouv sur le site de la bibliothque.

3.6.8

ligne 8 : installe la table dans une ligne [row] et un encadr color [alert alert-warning] ;
ligne 9 : la table va occuper 6 colonnes [col-md-6] ;
ligne 10 : la table HTML est formate par Bootstrap [class='table'] ;
ligne 13 : l'attribut [data-toggle] indique la colonne qui hberge le symbole [+/-] qui dplie / replie la ligne ;
ligne 19 : l'attribut [data-hide='phone'] indique que la colonne doit tre cache si l'cran a la taille d'un cran de tlphone.
On peut galement utiliser la valeur 'tablet' ;

Exemple 8

Pour aider l'utilisateur, nous allons crer des bulles d'aide (tooltip) autour des principaux composants des vues :

Pour obtenir ce rsultat, nous dupliquons [app-01.html] dans [app-09.html] et nous ajoutons les ligne suivantes :
1.
2.
3.
4.
5.
6.
7.

<!DOCTYPE html>
<html ng-app="rdvmedecins">
<head>
...
</head>
<body>
<div class="container">

http://tahe.developpez.com

160/325

8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.

<h1>Rdvmedecins - v1</h1>
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbarcollapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">RdvMedecins</a>
</div>
<!-- options de menu -->
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="active">
<a href="">
<span tooltip="Retourne la page d'accueil" tooltipplacement="bottom">Home</span>
</a>
</li>
<li class="active">
<a href="">
<span tooltip="Affiche l'agenda" tooltip-placement="top">Agenda</span>
</a>
</li>
<li class="active">
<a href="">
<span tooltip="Valide le rendez-vous" tooltip-placement="right">Valider</span>
</a>
</li>
<li class="active">
<a href="">
<span tooltip="Annule l'opration en cours" tooltipplacement="left">Annuler</span>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Bootstrap core JavaScript ================================================== -->
<...
<script type="text/javascript" src="bower_components/angular-ui-bootstrap-bower/ui-bootstraptpls.min.js"></script>
<!-- script local -->
<script>
// --------------------- module Angular
angular.module("rdvmedecins", ['ui.bootstrap']);
</script>
</body>
</html>

Les bulles d'aide sont fournis par la bibliothque [angular-ui-bootstrap] qui s'appuie elle-mme sur la bibliothque [angular]. La ligne
50 importe la bibliothque [angular-ui-bootstrap]. Pour mettre en oeuvre les composants de la bibliothque [angular-ui-bootstrap], il
nous faut crer un module Angular. Ceci est fait aux lignes 52-55. Ces lignes dfinissent un module Angular nomm [rdvmedecins]
(1er paramtre). Un module Angular peut utiliser d'autres modules Angular. C'est ce qu'on appelle des dpendances du module.
Elles sont fournies dans un tableau comme second paramtre de la fonction [angular.module]. Ici, le module nomm [ui.bootstrap]
est fourni par la bibliothque [angular-ui-bootstrap]. C'est ce module qui va nous fournir les bulles d'aide.
La ligne 54 dfinit un module Angular. Par dfaut, cela n'a aucun effet sur la page. On indique que la page doit tre gre par
Angular, en la rattachant un module Angular. C'est ce qui est fait, ligne 2. L'attribut [ng-app='rdvmedecins'] rattache la page au
module cr ligne 54. La page va alors tre analyse par Angular. Les attributs [tooltip] vont tre dcouverts et traits par le module
[ui.bootstrap].

http://tahe.developpez.com

161/325

La syntaxe de la bulle d'aide est la suivante :


<span tooltip="Retourne la page d'accueil" tooltip-placement="bottom">Home</span>

Ci-dessus, on ajoute une bulle d'aide au texte [Home] :

[tooltip] : dfinit le texte de la bulle d'aide ;

[tooltip-placement] : dfinit sa position (bottom, top, left, right) ;


Angular JS permet d'ajouter de nouvelles balises ou nouveaux attributs ceux existant dj dans le langage HTML. Cette extension
du langage HTML est faite au moyen de directives Angular. Ici les attributs [tooltip] et [tooltip-placement] sont des attributs crs
par [angular-ui-bootstrap].

3.6.9

Exemple 9

Pour aider l'utilisateur choisir le jour d'un rendez-vous, nous allons lui proposer un calendrier :

Comme pour les bulles d'aide, ce calendrier est fourni par la bibliothque [angular-ui-bootstrap]. Pour obtenir ce rsultat, nous
dupliquons [app-01.html] dans [app-10.html] et nous ajoutons les ligne suivantes :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.

<!DOCTYPE html>
<html ng-app="rdvmedecins">
<head>
...
<body>
<div class="container">
<h1>Rdvmedecins - v1</h1>
<div>
<pre>Date <em>{{jour | date:'fullDate'}}</em></pre>
<div class="row">
<div class="col-md-2">
<h4>Calendrier</h4>

http://tahe.developpez.com

162/325

15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.

<div style="display:inline-block; min-height:290px;">


<datepicker ng-model="jour" show-weeks="true" class="well"></datepicker>
</div>
</div>
</div>
</div>
</div>
</div>
...
<!-- script local -->
<script>
// --------------------- module Angular
angular.module("rdvmedecins", ['ui.bootstrap'])
</script>
</body>
</html>

Comme prcdemment, la page est associe un module Angular (lignes 2 et 28). Le calendrier est dfini par la balise
<datepicker> de la ligne 16 dfinie par la bibliothque [angular-ui-bootstrap] :

[show-weeks='true'] : pour afficher les ns des semaines ;

[class='well'] : pour entourer le calendrier d'une zone grise coins arrondis ;

[ng-model='jour'] : les attrinuts [ng-*] sont des attributs Angular. L'attribut [ng-model] dsigne une donne qui va tre
place dans le modle de la vue. Lorsque l'utilisateur va cliquer sur une date, celle-ci sera place dans la variable [jour] du
modle. Cette variable est utilise ligne 10. La syntaxe {{expression}} permet d'valuer une expression compose
d'lments du modle. Ici {{jour}} va afficher la valeur de la variable [jour] du modle. Une caractristique forte
d'Angular est que la vue va suivre automatiquement les changements de la variable [jour]. Ainsi, lorsque l'utilisateur va
changer les dates, ces changements seront immdiatement affichs ligne 10. De faon gnrale, le fonctionnement est le
suivant :
une vue V est associe un modle M ;
Angular observe le modle M et met automatiquement jour la vue V lorsqu'il y a un changement de son modle
M;
La syntaxe {{jour|date}} est appele un filtre. Ce n'est pas la valeur de [jour] qui est affiche mais la valeur de [jour]
filtre par un filtre appel [date]. Ce filtre est prdfini dans Angular. Il sert formater des dates. Il admet des paramtres
prcisant le format dsir. Ainsi l'expression {{jour | date:'fullDate'}} indique qu'on veut le format complet de la date,
ici [Friday, June 20, 2014] parce que le calendrier est en anglais par dfaut. Nous allons aborder son
internationalisation prochainement.

3.6.10

Conclusion

Nous avons prsent les lments du framework CSS Bootstrap que nous serons amens utiliser. C'taient des composants
passifs : leurs vnements n'taient pas grs. Ainsi un clic sur les boutons ou les liens ne faisait rien. Ces vnements seront grs
en Javascript. Il est possible d'utiliser ce langage sans l'aide de frameworks mais comme ce fut le cas ct serveur, certains
frameworks s'imposent ct client. C'est le cas du framework Angular JS qui amne avec lui une nouvelle faon d'aborder le
dveloppement des applications Javascript excutes par un navigateur. Nous le prsentons maintenant.

3.7

Dcouverte d'Angular JS

Nous allons illustrer maintenant certaines des caractristiques du framework Angular JS utilises dans l'application. Nous en avons
dj rencontr quelques unes :

une page HTML est propulse par Angular JS si on lui rattache un module :
<html ng-app="rdvmedecins">

Angular permet de crer de nouvelles balises et de nouveaux attributs HTML via des directives :
attributs : ng-app, ng-model, tooltip-placement, tooltip
balises : datepicker

Angular permet de crer des filtres :

http://tahe.developpez.com

163/325

{{jour|date:'fullDate'}}

une vue V affiche un modle M. Angular observe le modle M et met automatiquement jour la vue V lorsqu'il y a un
changement de son modle M. La valeur d'une variable du modle M est affiche dans la vue V par :
{{variable}}

Nous allons commencer par approfondir l'implmentation du Design Pattern Modle Vue Contrleur dans Angular. Rappelons
les liens qui existent entre-eux d'un point de vue architecture :

Application web / navigateur


couche [prsentation]

couche [services]

Routeur
10

Utilisateur
2

V1

C1

6
8

Service 1
Service 2

M1

Donnes
rseau

11

Vn

Cn
Mn

3.7.1

DAO

la vue V1 affiche le modle M1 construit par le contrleur C1. Ce dernier contient non seulement le modle M1 mais
galement les gestionnaires des vnements de la vue V1. On est dans le cycle 5, 8, 9 :
[5] : un vnement se produit dans la vue V1. Il est trait par le contrleur C1 ;
celui-ci fait son travail [6-7] puis construit le modle M1 [8] ;
[9] : la vue V1 affiche le nouveau modle M1. Comme nous l'avons dit, cette dernire tape est automatique. Il n'y
pas comme dans d'autres frameworks MVC, un push explicite (C1 pousse le modle M1 dans V1) ou un pull
explicite (la vue V1 va chercher le modle M1 dans C1). Il y a un push implicite que le dveloppeur ne voit pas ;
puis le cycle 5, 8, 9 reprend ;

Exemple 1 : le modle MVC d'Angular

Nous allons reprendre l'exemple du calendrier. Nous avons vu la directive qui le gnre :
<datepicker ng-model="jour" show-weeks="true" class="well"></datepicker>

Cette directive admet d'autres attributs que ceux prsents ci-dessus, entre-autres l'attribut [min-date] qui fixe la date minimale
qu'on peut choisir dans le calendrier. Ce sera utile pour nous. Lorsque l'utilisateur choisit une date de rendez-vous, celle-ci doit tre
gale ou suprieure celle du jour courant. Nous crirons alors :
<datepicker ng-model="jour" ... min-date="dateMin"></datepicker>

o [dateMin] sera une variable du modle de la page qui aura pour valeur la date du jour. Cela donnera la page suivante :

http://tahe.developpez.com

164/325

2
1

en [1], nous sommes le 19 Juin 2014. Le curseur indique qu'on peut slectionner le 19 juin ;
en [2], le curseur indique qu'on ne peut pas slectionner le 18 juin ;

Nous dupliquons [app-10.html] dans [app-11.html] et nous faisons les modifications suivantes :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.

<!DOCTYPE html>
<html ng-app="rdvmedecins">
<head>
...
</head>
<body ng-controller="rdvMedecinsCtrl">
<div class="container">
<h1>Rdvmedecins - v1</h1>
<div>
<pre>Date <em>{{jour | date:'fullDate' }}</em></pre>
<div class="row">
<div class="col-md-2">
<h4>Calendrier</h4>
<div style="display:inline-block; min-height:290px;">
<datepicker ng-model="jour" show-weeks="true" class="well" mindate="minDate"></datepicker>
</div>
</div>
</div>
</div>
</div>
<!-- Bootstrap core JavaScript ================================================== -->
...
<!-- script local -->
<script>
// --------------------- module Angular
angular.module("rdvmedecins", ['ui.bootstrap']);
// contrleur
angular.module("rdvmedecins")
.controller('rdvMedecinsCtrl', ['$scope',
function ($scope) {
// date minimale
$scope.minDate = new Date();
}]);

http://tahe.developpez.com

165/325

36.
37. </script>
38.
39. </body>
40. </html>

Examinons d'abord le script local des lignes 26-37 :

ligne 28 : cration du module [rdvmedecins] avec sa dpendance sur le module [ui.bootstrap] qui fournit le calendrier ;

lignes 30-35 : cration d'un contrleur. C'est lui qui va dtenir le modle de notre page. Il n'y aura pas de gestionnaire
d'vnement ici ;

lignes 30-31 : le contrleur [rdvMedecinsCtrl] appartient au module [rdvmedecins]. On peut ajouter autant de contrleurs
que l'on veut un module. Dans notre application on aura :
un module de gestion de l'application ;
un contrleur par vue ;

le second paramtre de la fonction [controller] est un tableau de la forme ['O1', 'O2', ..., 'On', function(O1, O2, ...,
On)]. Le dernier paramtre est la fonction qui implmente le contrleur. Ses paramtres sont des objets que Angular JS
va fournir la fonction.
Revenons l'architecture d'une application Angular :

Application web / navigateur


couche [prsentation]

couche [services]

Routeur
10

Utilisateur
2

V1

C1

6
8

Service 1
Service 2

M1

Donnes
rseau

11

Vn

Cn
Mn

DAO

Ci-dessus, le contrleur C1 contient l'ensemble des gestionnaires d'vnement de la vue V1 ainsi que le modle M1 de cette
dernire. Les gestionnaires d'vnement peuvent avoir besoin d'un ou plusieurs services [6] pour faire leur travail. On passe
l'ensemble de ceux-ci comme paramtres de la fonction de construction du contrleur :
['S1', 'S2', ..., 'Sn', function(S1, S2, ..., Sn)]

Les services Si sont des singletons. Angular les cre en un unique exemplaire. Ils sont identifis par un nom Si. Pourquoi sont-ils
prsents deux fois dans le tableau ci-dessus ? En exploitation, les scripts JS sont minifis. Dans ce processus de minification, le
tableau ci-dessus devient :
['S1', 'S2', ..., 'Sn', function(a1, a2, ..., an)]

Les paramtres perdent leur nom. Or c'est le nom de services. Il est donc important de garder ces noms. C'est pourquoi ils sont
passs en tant que chanes de caractres comme paramtres prcdant la fonction. Les chanes de caractres ne sont pas changes
dans le processus de minification. Lorsqu'Angular va construire le contrleur avec le nouveau tableau, il va remplacer a1 par S1, a2
par S2, ... L'ordre des paramtres est donc important. Il doit correspondre l'ordre des services qui prcdent la dfinition de la
fonction.
Revenons la dfinition du contrleur [rdvMedecinsCtrl] :
1.
2.

// contrleur
angular.module("rdvmedecins")

http://tahe.developpez.com

166/325

3.
.controller('rdvMedecinsCtrl', ['$scope',
4.
function ($scope) {
5.
// date minimale
6.
$scope.minDate = new Date();
7. }]);

lignes 3-4 : l'unique objet inject dans le contrleur est l'objet $scope. C'est un objet prdfini qui reprsente le modle M
des vues associes au contrleur. Pour enrichir le modle d'une vue, il suffit d'ajouter des champs l'objet $scope ;
c'est ce qui est fait ligne 6. On cre le champ [minDate] avec pour valeur la date du jour ;

La vue V exploite ce modle M de la faon suivante :


1. <body ng-controller="rdvMedecinsCtrl">
2. <div class="container">
3. ...
4.
<div style="display:inline-block; min-height:290px;">
5.
<datepicker ng-model="jour" show-weeks="true" class="well" mindate="minDate"></datepicker>
6.
</div>
7. ...
8. </div>
9. ...

ligne 1 : le corps de la page est associ au contrleur [rdvMedecinsCtrl] grce l'attribut [ng-controller]. Cela signifie que
tout ce qui est dans la balise <body> va utiliser le contrleur [rdvMedecinsCtrl] pour grer ses vnements et obtenir son
modle M. Une page HTML peut dpendre de plusieurs contrleurs imbriqus ou pas les uns dans les autres :
1. <div id='div1' ng-controller='c1'>
2.
...
3.
<div id='div11' ng-controller='c11'>
4.
...
5.
</div>
6.
...
7.
<div id='div12' ng-controller='c12'>
8.
...
9.
</div>
10. </div>

Ci-dessus :

le contenu de [div1] (lignes 1-10) affiche le modle M1 gr par le contrleur c1. Les balises de cette zone
peuvent rfrencer des gestionnaires d'vnement du contrleur c1 ;

le contenu de [div11] (lignes 3-4) affiche le modle M11 gr par le contrleur c11 mais galement le modle M1.
Il y a hritage des modles. Les balises de cette zone peuvent rfrencer aussi bien des gestionnaires d'vnement
du contrleur c11 que des gestionnaires d'vnement du contrleur c1. Elles ne peuvent rfrencer ni le modle
M12 du contrleur c12 ni les gestionnaires d'vnement de celui-ci. Le contrleur c12 n'est en effet pas connu
entre les lignes 3-5 ;

lignes 7-9 : on peut tenir un raisonnement analogue celui tenu prcdemment ;


Revenons au code du calendrier :
<datepicker ng-model="jour" show-weeks="true" class="well" min-date="minDate"></datepicker>

L'attribut [min-date] est initialis avec la valeur [minDate] du modle. Implicitement [$scope.minDate]. Le champ est toujours
cherch dans l'objet $scope.

3.7.2

Exemple 2 : localisation des dates

Pour l'instant le calendrier ne nous est gure utile puisque c'est un calendrier anglais. Il est possible de le localiser :

http://tahe.developpez.com

167/325

en [1], nous avons un calendrier en franais ;


en [2], on le passe en anglais ;
en [3], le calendrier anglais ;

Nous dupliquons la page [app-11.html] dans [app-12.html] puis nous modifions cette dernire de la faon suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.

<!DOCTYPE html>
<html ng-app="rdvmedecins">
<head>
...
</head>
<body ng-controller="rdvMedecinsCtrl">
<div class="container">
<h1>Rdvmedecins - v1</h1>
<pre>Date <em>{{jour | date:'fullDate' }}</em></pre>
<div class="row">
<!-- le calendrier-->
<div class="col-md-4">
<h4>Calendrier</h4>
<div style="display:inline-block; min-height:290px;">
<datepicker ng-model="jour" show-weeks="true" class="well" mindate="minDate"></datepicker>
</div>
</div>
<!-- les langues -->
<div class="col-md-2">
<div class="btn-group" dropdown is-open="isopen">
<button type="button" class="btn btn-primary dropdown-toggle" style="margin-top: 30px">
Langues<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="" ng-click="setLang('fr')">Franais</a></li>
<li><a href="" ng-click="setLang('en')">English</a></li>
</ul>
</div>
</div>
</div>
</div>
...
<script type="text/javascript" src="rdvmedecins.js"></script>
</body>

18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37. </html>

http://tahe.developpez.com

168/325

Il y a peu de modifications. Il y a simplement l'ajout lignes 21-31 de la liste droulante des langues. Pour la premire fois, nous
rencontrons un gestionnaire d'vnement aux lignes 27-28 :

ligne 27 : l'attribut [ng-click] est un attribut Angular qui indique le gestionnaire d'vnement excuter lorsqu'on clique sur
l'lment ayant cet attribut. Ici, la fonction [$scope.setLang('fr')] sera excute. Elle mettra le calendrier en franais ;

ligne 28 : ici, on met le calendrier en anglais ;

ligne 35 : le Javascript du contrleur tant assez consquent, nous le plaons dans un fichier [rdvmedecins.js] ;
Angular gre la localisation des vues avec un module appel [ngLocale]. La dfinition de notre module [rdvmedecins] sera donc la
suivante :
1.
// --------------------- module Angular
2. angular.module("rdvmedecins", ['ui.bootstrap', 'ngLocale']);

Ligne 2, il ne faut pas oublier les dpendances car Angular est parfois peu prcis dans ses messages d'erreur. L'oubli d'une
dpendance est ainsi particulirement difficile dtecter. Ici on a une nouvelle dpendance sur le module [ngLocale].
Par dfaut, Angular ne gre que la localisation des dates, nombres, ... qui ont des variantes locales. Il ne gre pas l'internationalisation
de textes. On utilisera pour cela la bibliothque [angular-translate]. La gestion de la localisation est faite par la bibliothque [angulari18n]. Cette bibliothque amne avec elle autant de fichiers qu'il y a de variantes pour les dates, nombres, ...

Pour le calendrier franais, nous utiliserons le fichier [angular-locale_fr-fr.js] et pour le calendrier anglais le fichier [angularlocale_en-us.js]. Regardons ce qu'il y a par exemple dans le fichier [angular-locale_fr-fr.js] :
1. 'use strict';
2. angular.module("ngLocale", [], ["$provide", function($provide) {
3. var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many",
OTHER: "other"};
4. $provide.value("$locale", {
5.
"DATETIME_FORMATS": {
6.
"AMPMS": [
7.
"AM",
8.
"PM"
9.
],
10.
"DAY": [
11.
"dimanche",
12.
"lundi",
13.
"mardi",
14.
"mercredi",
15.
"jeudi",
16.
"vendredi",

http://tahe.developpez.com

169/325

17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.

"samedi"
],
"MONTH": [
"janvier",
"f\u00e9vrier",
"mars",
"avril",
"mai",
"juin",
"juillet",
"ao\u00fbt",
"septembre",
"octobre",
"novembre",
"d\u00e9cembre"
],
"SHORTDAY": [
"dim.",
"lun.",
"mar.",
"mer.",
"jeu.",
"ven.",
"sam."
],
"SHORTMONTH": [
"janv.",
"f\u00e9vr.",
"mars",
"avr.",
"mai",
"juin",
"juil.",
"ao\u00fbt",
"sept.",
"oct.",
"nov.",
"d\u00e9c."
],
"fullDate": "EEEE d MMMM y",
"longDate": "d MMMM y",
"medium": "d MMM y HH:mm:ss",
"mediumDate": "d MMM y",
"mediumTime": "HH:mm:ss",
"short": "dd/MM/yy HH:mm",
"shortDate": "dd/MM/yy",
"shortTime": "HH:mm"
},
"NUMBER_FORMATS": {
"CURRENCY_SYM": "\u20ac",
"DECIMAL_SEP": ",",
"GROUP_SEP": "\u00a0",
"PATTERNS": [
{
"gSize": 3,
"lgSize": 3,
"macFrac": 0,
"maxFrac": 3,
"minFrac": 0,
"minInt": 1,
"negPre": "-",
"negSuf": "",
"posPre": "",

http://tahe.developpez.com

170/325

80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.

"posSuf": ""
},
{
"gSize": 3,
"lgSize": 3,
"macFrac": 0,
"maxFrac": 2,
"minFrac": 2,
"minInt": 1,
"negPre": "(",
"negSuf": "\u00a0\u00a4)",
"posPre": "",
"posSuf": "\u00a0\u00a4"
}

]
},
"id": "fr-fr",
"pluralCat": function (n) { if (n >= 0 && n <= 2 && n != 2) {
PLURAL_CATEGORY.ONE; } return PLURAL_CATEGORY.OTHER;}
98. });
99. }]);

return

On y voit les lments qui permettent de crer un calendrier franais :

lignes 10-18 : le tableau des jours de la semaine ;


lignes 19-32 : le tableau des mois de l'anne ;
lignes 33-41 : le tableau des jours de la semaine en abrg ;
lignes 42-55 : le tableau des mois de l'anne en abrg ;
lignes 56-63 : des formats de date et d'heure. On reconnat ligne 62 le format 'jj/mm/aa' des dates franaises ;
lignes 65-95 : des informations pour le formatage des nombres. Cela ne nous intresse pas ici ;
ligne 96 : l'identifiant 'fr-fr' de la locale du fichier (fr-fr : franais de France, fr-ca : franais du Canada, ...)

Dans le fichier [angular-locale_en-us.js], on a exactement la mme chose mais cette fois ci pour l'anglais des USA (en-us).
Le code ci-dessus n'est pas trs simple lire. En lisant attentivement, on dcouvre que tout ce code dfinit la variable [$ locale] de la
ligne 4. C'est en changeant la valeur de cette variable qu'on obtient l'internationalisation des dates, nombres, monnaie, ...
Curieusement, Angular n'a pas prvu qu'on change la variable [$locale] en cours d'excution. On la dfinit une bonne fois pour
toutes en important le fichier de la locale dsire :
<script type="text/javascript" src="bower_components/angular-i18n/angular-locale_fr-fr.js"></script>

Cela ne sert rien d'importer tous les fichiers des locales dsires, car chaque fichier, on l'a vu, ne fait qu'une chose : dfinir la
variable [$locale]. C'est le dernier fichier import qui gagne et il n'y a ensuite plus moyen de changer la locale.
En naviguant sur la toile la recherche d'une solution ce problme, je n'en ai pas trouv. J'en propose une ici
[https://github.com/stahe/angular-ui-bootstrap-datepicker-with-locale-updated-on-the-fly]. L'ide est de mettre les diffrentes
locales dont nous avons besoin dans un dictionnaire. C'est l que nous irons les chercher lorsqu'il faudra en changer. Le code
Javascript de [rdvmedecins.js] a l'architecture suivante :

http://tahe.developpez.com

171/325

Si on enlve la dfinition des locales qui prend 200 lignes (lignes 15-215 ci-dessus), le code est simple :

ligne 6 : dfinit le module [rdvmedecins] et ses dpendances ;


lignes 8-10 : dfinit le contrleur [rdvMedecinsCtrl] de la page ;
ligne 9 : la fonction de construction du contrleur reoit deux paramtres :
$scope : pour crer le modle de la vue ;
$locale : qui est la variable qui gre la localisation du calendrier. C'est elle qu'il faut changer lorsqu'on change de
langue ;
ligne 13 : la variable [minDate] du modle est initialise avec la date du jour ;
ligne 15 : dfinit le dictionnaire [locales]. Notez qu'on n'a pas crit [$scope.locales]. La variable [locales] ne fait en effet
pas partie du modle expos la vue ;
lignes 15-215 : dfinissent un dictionnaire {'fr':locale-fr-fr, 'en':locale-en-us}. Les valeurs [locale-fr-fr] et [locale-en-us]
sont prises respectivement dans les fichiers JS [angular-locale_fr-fr.js] et [angular-locale_en-us.js]. Le plus dur, c'est de ne
pas se tromper dans les trs nombreuses parenthses de ce dictionnaire...
ligne 217 : on initialise la variable $locale avec locales['fr'], --d la version franaise de la locale. On ne peut pas crire
simplement [$locale=locales['fr']] qui affecte $locale, l'adresse de locales['fr']. Il faut faire une copie de valeur. Celle-ci peut
se faire avec la fonction prdfinie [angular.copy] ;
ligne 219 : la variable [jour] du modle est initialise avec la date du jour. Cela entrane que le calendrier sera affich
positionn sur cette date ;
lignes 223-230 : dfinissent le gestionnaire d'vnement qui est appel lors du changement de langue. On notera la
syntaxe :
$scope.nom_fonction=function(param1, param2, ...){...}

pour dfinir un gestionnaire d'vnement qui s'appellerait [nom_fonction] et qui admettrait les paramtres [param1,
param2, ...] ;
Rappelons le code HTML de la liste droulante :
1.
<!-- les langues -->
2.
<div class="col-md-2">
3.
<div class="btn-group" dropdown is-open="isopen">
4.
<button type="button" class="btn btn-primary dropdown-toggle" style="margin-top:
30px">
5.
Langues<span class="caret"></span>
6.
</button>
7.
<ul class="dropdown-menu" role="menu">
8.
<li><a href="" ng-click="setLang('fr')">Franais</a></li>

http://tahe.developpez.com

172/325

9.
10.
11.
12.

<li><a href="" ng-click="setLang('en')">English</a></li>


</ul>
</div>
</div>

ligne 8 : la slection du franais entrane l'appel de [setLang('fr')] ;


ligne 9 : la slection de l'anglais entrane l'appel de [setLang('en')] ;
ligne 3 : l'attribut [is-open] est un boolen qui contrle l'ouverture (true) ou la fermeture (false) de la liste
droulante. Il est initialis avec la variable [isopen] du modle de la vue ;

Revenons au code de [rdvmedecins.js] :

ligne 225 : on change la valeur de la variable [$locale] avec la valeur du dictionnaire [locales] qui convient ;
ligne 227 : on a dit que lorsque le modle M d'une vue V change, la vue V est automatiquement rafrachie avec le nouveau
modle. Ligne 225, on a chang la valeur de la variable [$locale] qui ne fait pas partie du modle M affich par la vue V. Il
faut trouver un moyen de changer ce modle M afin que le calendrier se rafrachisse et utilise sa nouvelle locale. Ici, on
change la variable [jour] du modle du calendrier. On l'initialise avec un nouveau pointeur (new) qui pointe sur une date
identique celle qui est affiche. [$scope.jour.getTime()] est le nombre de millisecondes coule entre le 1er janvier 1970 et
la date affiche par le calendrier. Avec ce nombre, on reconstruit une nouvelle date. On va bien sr retrouver la mme date
et le calendrier restera positionn sur la date qu'il affichait. Mais la valeur de [$scope.jour] qui est en ralit un pointeur
aura elle chang et le calendrier va se rafrachir ;
ligne 229 : on positionne false la valeur de la variable [isopen] du modle. Cette variable contrle un des attributs de la
liste droulante :
1. <div class="btn-group" dropdown is-open="isopen">
2.
<button type="button" class="btn btn-primary dropdown-toggle" style="margin-top: 30px">
3.
Langues<span class="caret"></span>
4.
</button>
5. ...
6. </div>

Ligne 1 ci-dessus, l'attribut [is-open] va passer false, ce qui va avoir pour effet de fermer la liste droulante.

3.7.3

Exemple 3 : internationalisation des textes

Revenons sur la localisation du calendrier :

En [3], nous voyons que le calendrier est en anglais mais pas les textes [Calendrier, Langues]. Par dfaut, Angular n'offre pas d'outil
pour l'internationalisation des messages. Nous allons utiliser ici la bibliothque [angular-translate] (https://github.com/angulartranslate/angular-translate).
Nous allons dvelopper l'exemple suivant :

http://tahe.developpez.com

173/325

http://tahe.developpez.com

174/325

2
1

en [1], la vue en franais ;


en [2], la vue en anglais ;

Voyons la configuration ncessaire l'internationalisation. Le script [rdvmedecins.js] est modifi de la faon suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.

// --------------------- module Angular


angular.module("rdvmedecins", ['ui.bootstrap', 'ngLocale', 'pascalprecht.translate']);
// configuration i18n
angular.module("rdvmedecins")
.config(['$translateProvider', function ($translateProvider) {
// messages franais
$translateProvider.translations("fr", {
'msg_header': 'Cabinet Mdical<br/>Les Mdecins Associs',
'msg_langues': 'Langues',
'msg_agenda': 'Agenda de {{titre}} {{prenom}} {{nom}}<br/>le {{jour}}',
'msg_calendrier': 'Calendrier',
'msg_jour': 'Jour slectionn : ',
'msg_meteo': "Aujourd'hui, il va pleuvoir..."
});
// messages anglais
$translateProvider.translations("en", {
'msg_header': 'The Associated Doctors',
'msg_langues': 'Languages',
'msg_agenda': "{{titre}} {{prenom}} {{nom}}'s Diary<br/> on {{jour}}",
'msg_calendrier': 'Calendar',
'msg_jour': 'Selected day: ',
'msg_meteo': 'Today, it will be raining...'
});
// langue par dfaut
$translateProvider.preferredLanguage("fr");
}]);

http://tahe.developpez.com

175/325

ligne 2 : la premire modification est l'ajout d'une nouvelle dpendance. L'internationalisation de l'application ncessite le
module Angular [pascalprecht.translate] ;
lignes 5-26 : dfinissent la fonction [config] du module [rdvmedecins]. Au dmarrage d'une application Angular, le
framework instancie tous les services ncessaires l'application, ceux prdfinis d'Angular et ceux dfinis par l'utilisateur.
Pour l'instant, nous n'avons pas dfini de services. La fonction [config] du module d'une application est excute avant
toute instanciation de service. Elle peut tre utilise pour dfinir des informations de configuration des services qui vont
tre ensuite instancis. Ici, la fonction [config] va tre utilise pour dfinir les messages internationaliss de l'application ;
ligne 5 : le paramtre de la fonction [config] est un tableau ['O1', 'O2', ..., 'On', function(O1, O2, ..., On)] o Oi est un
objet connu et fourni par Angular. Ici, l'objet [$translateProvider] est fourni par le module [pascalprecht.translate].
[function] est la fonction excute pour configurer l'application ;
lignes 7-14 : la fonction [$translateProvider.translations] admet deux paramtres :
le premier paramtre est la cl d'une langue. On peut mettre ce qu'on veut. Ici, on a mis 'fr' pour les traductions
franaises (ligne 7) et 'en' pour les traductions anglaises (ligne 16),
le second est la liste des traductions sous la forme d'un dictionnaire {'cle1':'msg1', 'cle2':'msg2', ...} ;
lignes 7-14 : les messages franais ;
lignes 16-23 : les messages anglais ;
ligne 25 : la mthode [preferredLanguage] fixe la langue par dfaut. Son paramtre est l'un des arguments utiliss comme
premier paramtre de la fonction [$translateProvider.translations] donc ici soit 'fr' (ligne 7), soit 'en' (ligne 16) ;
notons qu'il y a trois sortes de messages :
des messages sans paramtres ni lments HTML (lignes 9, 11, 12, ...),
des messages avec des lments HTML (lignes 8, 10, ...),
des messages avec des paramtres (lignes 10, 19) ;

Nous dupliquons maintenant [app-11.html] dans [app-12.html] et nous faisons les modifications suivantes :
1. <div class="container">
2.
<!-- un premier texte avec des lments HTML dedans -->
3.
<h3 class="alert alert-info" translate="{{'msg_header'}}"></h3>
4.
<!-- un second texte avec paramtres -->
5.
<h3 class="alert alert-warning" translate="{{msg.text}}" translatevalues="{{msg.model}}"></h3>
6.
<!-- un troisime texte traduit par le contrleur -->
7.
<h3 class="alert alert-danger">{{msg2}}</h3>
8.
9.
<pre>{{'msg_jour'|translate}}<em>{{jour | date:'fullDate' }}</em></pre>
10. <div class="row">
11.
<!-- le calendrier-->
12.
<div class="col-md-4">
13.
<h4>{{'msg_calendrier'|translate}}</h4>
14.
15.
<div style="display:inline-block; min-height:290px;">
16.
<datepicker ng-model="jour" show-weeks="true" class="well" mindate="minDate"></datepicker>
17.
</div>
18.
</div>
19.
<!-- les langues -->
20.
<div class="col-md-2">
21.
<div class="btn-group" dropdown is-open="isopen">
22.
<button type="button" class="btn btn-primary dropdown-toggle" style="margin-top: 30px">
23.
{{'msg_langues'|translate}}<span class="caret"></span>
24.
</button>
25.
<ul class="dropdown-menu" role="menu">
26.
<li><a href="" ng-click="setLang('fr')">Franais</a></li>
27.
<li><a href="" ng-click="setLang('en')">English</a></li>
28.
</ul>
29.
</div>
30.
</div>
31. </div>
32. </div>

les traductions ont lieu aux lignes 3, 5, 9, 13, 23 ;


on peut distinguer trois syntaxes :
la syntaxe [translate={{'msg_key'}}] (ligne 3), o [msg_key] est une des cls d'un dictionnaire de traduction. Cette
syntaxe convient aux messages avec ou sans lments HTML mais pas ceux avec paramtres ;

http://tahe.developpez.com

176/325

la syntaxe [translate={{'msg_key'}} translate-values={{dictionnaire]}}] (ligne 5), convient aux messages avec ou


sans lments HTML et avec paramtres ;
la syntaxe [{{'msg_key'|translate}}] (lignes 9, 13, 23), convient aux messages sans paramtres et sans lments
HTML ;

Regardons les diffrents messages de cette vue :


ligne franais

anglais

Cabinet Mdical<br/>Les Mdecins Associs The Associated Doctors

13

Calendrier

Calendar

23

Langues

Languages

Jour slectionn :

Selected day:

Examinons maintenant la ligne 5 :


<h3 class="alert alert-warning" translate="{{msg.text}}" translate-values="{{msg.model}}"></h3>

On notera que [msg.text] et [msg.model] ne sont pas entours d'apostrophes. Ce ne sont pas des chanes de caractres mais des
lments du modle :

msg.text : dfinit la cl du message paramtr utiliser ;

msg.model : est le dictionnaire fournissant les valeurs des paramtres ;


Les noms des champs [text, model] peuvent tre quelconques. Dans le contrleur [rdvMedecinsCtrl] de la vue, l'objet [msg] est
dfini de la faon suivante :

ligne 245 : la dfinition de l'objet [msg] ;


ligne 245 : le champ [text] a pour valeur la cl [msg_agenda] qui est associ deux valeurs :
Agenda de {{titre}} {{prenom}} {{nom}}<br/>le {{jour}} dans le dictionnaire franais ;
{{titre}} {{prenom}} {{nom}}'s Diary<br/> on {{jour}} dans le dictionnaire anglais ;
Le message afficher a donc quatre paramtres [titre, prenom, nom, jour] ;
ligne 245 : le champ [model] est un dictionnaire donnant une valeur ces quatre paramtres. Il y a une difficult pour le
paramtre [jour]. On veut afficher le nom complet du jour. Il est diffrent selon qu'il est en franais ou en anglais. On
utilise alors le filtre [date] dj utilis dans la vue sous la forme {{ jour | date:'fullDate'}}. Il est possible d'utiliser tout
filtre dans le code Javascript sous la forme $filter('filter')(valeur, complments) o $filter est un objet prdfini d'Angular et
'filter' le nom du filtre ;
lignes 33-34 : l'objet prdfini $filter est pass comme paramtre au contrleur, ce qui permet de l'utiliser la ligne 245 ;

Revenons une autre ligne de la vue affiche :

http://tahe.developpez.com

177/325

1.
<!-- un troisime texte traduit par le contrleur -->
2. <h3 class="alert alert-danger">{{msg2}}</h3>

Toutes les traductions prcdentes se sont faites dans la vue au moyen d'attributs du module [pascalprecht.translate]. On peut
dcider galement de faire cette traduction ct serveur. C'est ce qui est fait ici. On a dans le contrleur (ligne 247 dans la copie
d'cran ci-dessus) le code suivant :
$scope.msg2 = $filter('translate')('msg_meteo');

On utilise la mme syntaxe que pour le filtre 'date' car 'translate' est lui aussi un filtre. On demande ici le message de cl
'msg_meteo'.
Examinons le mcanisme des changements de langues. On a vu que la fonction [config] de configuration du module [rdvmedecins]
avait dsign le franais comme langue par dfaut (ligne 9 ci-dessous) :
1. // configuration i18n
2. angular.module("rdvmedecins")
3.
.config(['$translateProvider', function ($translateProvider) {
4.
// messages franais
5.
$translateProvider.translations("fr", {...});
6.
// messages anglais
7.
$translateProvider.translations("en", {...});
8.
// langue par dfaut
9.
$translateProvider.preferredLanguage("fr");
10. }]);

On rappelle galement que la locale par dfaut tait galement le franais. Dans l'initialisation du contrleur [rdvmedecins] on a
crit :
1. // on met la locale en franais
2. angular.copy(locales['fr'], $locale);

ligne 2 : [locales] est un dictionnaire que nous avons construit ;

Il n'y a aucun lien entre l'internationalisation des messages amene par le module [pascalprecht.translate] et la localisation des dates
que nous avons mise en place. Cette dernire utilise une variable $locale qui n'est pas utilise par le module [pascalprecht.translate].
Ce sont deux processus qui s'ignorent.
Il est maintenant temps de regarder ce qui se passe lorsque l'utilisateur change de langue :

ligne 251 : lors d'un changement de langue, la fonction [setLang] est appele avec l'un des deux paramtres ['fr','en'] ;
lignes 252-257 : ont t dj expliques elles changent la variable [$locale] du calendrier. Cela n'a aucune incidence sur la
langue des traductions ;
ligne 259 : on change la langue des traductions. On utilise l'objet [$translate] fourni par le module [ pascalprecht.translate].
Pour cela, il faut l'injecter dans le contrleur :
1. // contrleur
2. angular.module("rdvmedecins")
3.
.controller('rdvMedecinsCtrl', ['$scope', '$locale', '$translate', '$filter',
4. function ($scope, $locale, $translate, $filter) {

http://tahe.developpez.com

178/325

Lignes 3 et 4 ci-dessus, on injecte l'objet $translate ;


le paramtre lang de la fonction [$translate.use(lang)] doit avoir pour valeur l'une des cls utilises dans la
configuration comme 1er paramtre de la fonction [$translateProvider.translations], --d soit 'fr', soit 'en'.
C'est bien le cas ;
ligne 261 : on recalcule la valeur de msg2. Pourquoi ? Dans la vue, aprs le changement de langue opr par la ligne 259,
tous les attributs [translate] prsents vont tre rvalus. Ce ne sera pas le cas de l'expression {{msg2}} qui n'a pas cet
attribut. Donc on calcule sa nouvelle valeur dans le contrleur. Cela doit tre fait aprs le changement de langue de la ligne
259 pour que la nouvelle langue soit utilise pour le calcul de [msg2] ;

Si on s'en tient-l, on observe deux anomalies :

1.
2.

en [1], le jour est rest en franais alors que le reste de la vue est en anglais ;
en [2] et [3], le jour slectionn est le 24 juin alors qu'en [1], le jour reste fix sur le 20 juin ;

Tentons des explications avant de trouver des solutions. Le message [1] est construit dans le contrleur avec le code suivant :
$scope.msg = {'text': 'msg_agenda', 'model': {'titre': 'Mme', 'prenom': 'Laure', 'nom':
'PELISSIER', 'jour': $filter('date')($scope.jour, 'fullDate')}};

et affich dans la vue avec le code suivant :


<h3 class="alert alert-warning" translate="{{msg.text}}" translate-values="{{msg.model}}"></h3>

L'anomalie [1] (le jour est rest en franais alors que le reste de la vue est en anglais) semble montr que si l'attribut [translate] est
rvalu lors d'un changement de langue, ce n'a pas t le cas de l'attribut [translate-values]. On peut alors forcer cette valuation
dans le contrleur :
1.
2.
3.
4. ...
5.
6.
7.
8.
9. };

// ------------------- gestionnaire d'evts


// changement de langue
$scope.setLang = function (lang) {

http://tahe.developpez.com

// on met jour msg2


$scope.msg2 = $filter('translate')('msg_meteo');
// et le jour de msg
$scope.msg.model.jour = $filter('date')($scope.jour, 'fullDate');

179/325

A chaque changement de langue, la ligne 8 ci-dessus rvalue le jour affich. Cela rgle effectivement le premier problme mais pas
le second (le jour affich dans le message ne change pas lorsqu'on slectionne un autre jour dans le calendrier). La raison de ce
comportement est la suivante. Le message est affich dans la vue avec le code suivant :
<h3 class="alert alert-warning" translate="{{msg.text}}" translate-values="{{msg.model}}"></h3>

La vue affiche V ne change que si son modle M change. Or ici, le choix d'un nouveau jour dans le calendrier dclenche un
vnement qui n'est pas gr, ce qui fait que le modle [msg] ne change pas et que la vue donc ne change pas. Nous faisons voluer
dans la vue, la dfinition du calendrier :
<datepicker ng-model="jour" show-weeks="true" class="well" min-date="minDate"
ng-click="calendarClick()"></datepicker>

Ci-dessus, nous indiquons que le clic sur le calendrier doit tre gr par la fonction [$scope.calendarClick]. Celle-ci est la suivante :

3.7.4

ligne 267 : le gestionnaire du clic sur le calendrier ;


ligne 269 : on force la mise jour du jour affich par le message [msg] ;

Exemple 4 : un service de configuration

Revenons sur l'architecture d'une application Angular JS :

Application web / navigateur


couche [prsentation]

couche [services]

Routeur
10

Utilisateur
2

V1

C1

6
8

Service 1
Service 2

M1

Donnes
rseau

11

Vn

Cn
Mn

DAO

Nous allons nous intresser ici la notion de service. C'est une notion assez large. Si ci-dessus, la couche [DAO] est clairement un
service, tout objet Angular peut devenir un service :

un service suit une syntaxe particulire. Il a un nom et Angular le connat via ce nom ;

un service peut tre inject par Angular dans les contrleurs et les autres services ;
Certains des services que nous allons configurer dans le module [rdvmedecins] auront besoin d'tre configurs. Comme un service
peut tre inject dans un autre service, il est tentant de faire la configuration dans un service que nous nommerons [config] et
d'injecter celui-ci dans les services et contrleurs configurer. Nous dcrivons maintenant ce processus.

http://tahe.developpez.com

180/325

Nous dupliquons [app-13.html] dans [app-14.html] et faisons les modifications suivantes :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.

<div class="container">
<!-- contrle du msg d'attente -->
<label>
<input type="checkbox" ng-model="waiting.visible">
<span>Voir le message d'attente</span>
</label>

lignes 3-6 : une case cocher qui contrle l'affichage ou non du message d'attente des lignes 9-15. La valeur de la case
cocher est place dans la variable [waiting.visible] du modle M de la vue V. Cette valeur est true si la case est coche et false
sinon. Cela marche dans les deux sens. Si nous donnons la valeur true variable [waiting.visible], la case sera coche. On a
une association bi-directionnelle entre la vue V et son modle M ;
ligne 9-15 : un message d'attente avec un bouton d'annulation de l'attente (ligne 11) ;
ligne 9 : le message n'est visible que si la variable [waiting.visible] a la valeur true. Ainsi lorsqu'on va cocher la case de la
ligne 4 :
la valeur true est affecte la variable [waiting.visible] (ng-model, ligne 4) ;
comme il y a eu changement du modle M, la vue V est automatiquement rvalue. Le message d'attente sera alors
rendu visible (ng-show, ligne 9) ;
le raisonnement est analogue lorsqu'on dcoche la case de la ligne 4 : le message d'attente est cach ;
ligne 10 : le message d'attente est l'objet d'une traduction (filtre translate) ;
ligne 11 : lorsqu'on clique sur le bouton, la mthode [waiting.cancel()] est excute (atribut ng-click) ;
ligne 12 : le libell du bouton fait l'objet d'une traduction ;
ligne 19 : on met le code Javascript de l'application dans un nouveau fichier JS [rdvmedecins-02] pour ne pas perdre le
code dj crit et qui doit tre maintenant rorganis ;

<!-- le message d'attente -->


<div class="alert alert-warning" ng-show="waiting.visible">
<h1>{{ waiting.text | translate}}
<button class="btn btn-primary pull-right" ng-click="waiting.cancel()">
{{'msg_cancel'|translate}}</button>
<img src="assets/images/waiting.gif" alt=""/>
</h1>
</div>
...
</div>
...
<script type="text/javascript" src="rdvmedecins-02.js"></script>

Cela donne la vue suivante :


1

http://tahe.developpez.com

181/325

en [1], case non coche ;


en [2], case coche ;

Le script [rdvmedecins-02] est une rorganisation du script [rdvmedecins] :

ligne 6 : le module [rdvmedecins] de l'application ;


lignes 9-10 : la fonction de configuration de l'application ;
lignes 38-39 : le service [config] ;
lignes 283-284 : le contrleur [rdvMedecinsCtrl] ;

Prcdemment, nous avions dfini dans le contrleur le dictionnaire locales={'fr':..., 'en': ...} qui faisait 200 lignes. Ce dictionnaire
est clairement un lment de configuration, aussi le migre-t-on dans le service [config] des lignes 38-39. Ce service est dfini de la
faon suivante :

http://tahe.developpez.com

182/325

lignes 38-39 : un service est cr avec la fonction [factory] de l'objet [angular.module]. La syntaxe de cette fonction est
comme pour les prcdentes factory('nom_service',['O1','O2', ...., 'On', function (O1, O2, ..., On){...}]) o les Oi
sont les noms d'objets connus d'Angular (prdfinis ou crs par le dveloppeur) et qu'Angular injecte comme paramtre
de la fonction factory. Comme ici, la fonction n'a pas de paramtres, on a utilis une syntaxe plus courte galement
accepte factory('nom_service', function (){...}]) ;
ligne 40 : la fonction [factory] doit implmenter le service au moyen d'un objet qu'elle rend. C'est cet objet qui est le
service. C'est pourquoi la fonction est-elle appele factory (usine de cration d'objets) ;

En gnral le code d'un service est de la forme :


1. Angular.module('nom_module')
2.
.factory('nom_service',['O1','O2', ...., 'On', function (O1, O2, ..., On){
3.
// prparation du service
4.
...
5.
// on rend l'objet implmentant le service
6.
return {
7.
// champs
8.
...
9.
// mthodes
10.
...
11.
}
12. });

ligne 6 : on rend un objet JS qui peut contenir la fois des champs et des mthodes. Ce sont ces dernires qui assurent le
service ;

Ici le service [config] ne dfinit que des champs et aucune mthode. On y mettra tout ce qui peut tre paramtr dans l'application :

http://tahe.developpez.com

183/325

lignes 42-47 : les cls des messages traduire ;


lignes 59-62 : les URL de l'application ;
lignes 64-69 : les URL du service web distant ;
ligne 71 : un appel HTTP vers un service web qui ne rpond pas, peut tre long. On fixe ici 1 seconde, le temps d'attente
maximum de la rponse du service web. Pass ce dlai, l'appel HTTP choue et une exception JS est lance ;
ligne 73 : avant chaque appel au serveur, on va simuler une attente dont la dure est fixe ici en millisecondes. Une attente
de 0 fait qu'il n'y a pas d'attente. L'application va tre construite de telle faon que l'utilisateur puisse annuler une opration
qu'il a lance. Pour qu'elle puisse tre annule il faut qu'elle dure au moins quelques secondes. On utilisera cette attente
artificielle pour simuler des oprations longues ;
ligne 75 : en mode [debug=true], des informations complmentaires sont affiches dans la vue courante. Par dfaut, ce
mode est activ. En production, on mettrait ce champ false ;
lignes 77-278 : le dictionnaire des deux locales 'fr' et 'en'. Il tait auparavant dans le contrleur [rdvMedecinsCtrl] ;

Avec ce service, le contrleur [rdvMedecinsCtrl] volue de la faon suivante :

lignes 284-285 : le service [config] est inject dans le contrleur ;


ligne 290 : le dictionnaire [locales] est dsormais trouv dans le service [config] et non plus dans le contrleur ;
ligne 294 : l'objet [waiting] qui contrle l'affichage du message d'attente. La cl du message d'attente est trouve dans le
service [config] (champ text). Par dfaut le message d'attente est cach (champ visible). Le champ cancel a pour valeur, le
nom de la fonction ligne 316. Ce champ est donc une mthode ou fonction ;
ligne 316 : la fonction [cancel] est prive (on n'a pas crit $scope.cancel=function(){}). Revenons sur le code du bouton
d'annulation :
<button class="btn btn-primary pull-right" ng-click="waiting.cancel()">

Lorsque l'utilisateur clique sur le bouton d'annulation, la mthode [$scope.waiting.cancel()] est appele. C'est au final la
fonction prive cancel de la ligne 316 qui est excute. Elle se contente de cacher le message d'attente en mettant false, la
varible du modle [waiting.visible] (ligne 318) ;

3.7.5

Exemple 5 : programmation asynchrone

Nous prsentons maintenant un nouveau service avec une nouvelle notion, celle de la programmation asynchrone.

http://tahe.developpez.com

184/325

Application web / navigateur


couche [prsentation]

couche [services]

Routeur

Utilisateur

config
utils

Donnes
rseau

dao

Notre application aura trois services :

[config] : le service de configuration que nous venons de prsenter ;

[utils] : un service de mthodes utilitaires. Nous allons en prsenter deux ;

[dao] : le service d'accs au service web de prise de rendez-vous. Nous allons le prsenter prochainement ;
Nous allons crire l'application suivante :

il s'agit de faire apparatre le bandeau [2] pendant un temps fix par [1]. L'attente peut tre annule par [3].

Nous dupliquons [app-01.html] dans [app-15.html] et modifions le code de la faon suivante :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.

<!DOCTYPE html>
<html ng-app="rdvmedecins">
<head>
<title>RdvMedecins</title>
...
</head>
<body ng-controller="rdvMedecinsCtrl">
<div class="container">

<!-- le message d'attente -->


<div class="alert alert-warning" ng-show="waiting.visible" ng-cloak="">
<h1>{{ waiting.text | translate}}
<button class="btn btn-primary pull-right" ng-click="waiting.cancel()">{{'msg_cancel'|
translate}}</button>
14.
<img src="assets/images/waiting.gif" alt=""/>
15.
</h1>
16. </div>
17.
18. <!-- le formulaire -->
19. <div class="alert alert-info" ng-hide="waiting.visible">

http://tahe.developpez.com

185/325

20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.

<div class="form-group">
<label for="waitingTime">{{waitingTimeText | translate}}</label>
<input type="text" id="waitingTime" ng-model="waiting.time"/>
</div>
<button class="btn btn-primary" ng-click="execute()">Excuter</button>
</div>
</div>
..
<script type="text/javascript" src="rdvmedecins-03.js"></script>
</body>

ligne 11 : l'attribut [ng-cloak] empche l'affichage de la zone avant que les expressions Angular de celle-ci n'aient t
calcules. Cela vite un affichage bref de la zone avant l'valuation de l'attribut [ng-show] qui va en fait provoquer sa
dissimulation ;
ligne 22 : la saisie de l'utilisateur (temps d'attente) va tre mmorise dans le modle [waiting.time] (attribut ng-model) ;
ligne 28 : la page utilise un nouveau script [rdvmedecins-03] ;

</html>

Le script [rdvmedecins-03] est le suivant :

ligne 6 : le module Angular qui gre l'application ;


ligne 10 : la fonction [config] utilise pour internationaliser les messages ;
ligne 41 : le service [config] que nous avons dcrit ;
ligne 286 : le service [utils] que nous allons construire ;
ligne 315 : le contrleur [rdvmedecinsCtrl] que nous allons construire ;

Nous ajoutons la fonction [config], une nouvelle cl de message (lignes 6, 11) :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.

angular.module("rdvmedecins")
.config(['$translateProvider', function ($translateProvider) {
// messages franais
$translateProvider.translations("fr", {
...
'msg_waiting_time_text': "Temps d'attente : "
});
// messages anglais
$translateProvider.translations("en", {
...
'msg_waiting_time_text': "Waiting time:"
});
// langue par dfaut
$translateProvider.preferredLanguage("fr");
}]);

Nous ajoutons au service [config] une nouvelle ligne (ligne 6) pour cette cl de message :
1. angular.module("rdvmedecins")

http://tahe.developpez.com

186/325

2.
.factory('config', function () {
3.
return {
4.
// messages internationaliser
5.
...
6. waitingTimeText: 'msg_waiting_time_text',

Le service [utils] contient deux mthodes (lignes 4, 12) :


1. angular.module("rdvmedecins")
2.
.factory('utils', ['config', '$timeout', '$q', function (config, $timeout, $q) {
3.
// affichage de la reprsentation Json d'un objet
4.
function debug(message, data) {
5.
if (config.debug) {
6.
var text = data ? message + " : " + angular.toJson(data) : message;
7.
console.log(text);
8.
}
9.
}
10.
11.
// attente
12.
function waitForSomeTime(milliseconds) {
13.
// attente asynchrone de milliseconds milli-secondes
14.
var task = $q.defer();
15.
$timeout(function () {
16.
task.resolve();
17.
}, milliseconds);
18.
// on retourne la tche
19.
return task;
20.
};
21.
22.
// instance du service
23.
return {
24.
debug: debug,
25.
waitForSomeTime: waitForSomeTime
26.
}
27. }]);

ligne 2 : le service s'appelle [utils] (1er paramtre). Il a des dpendances sur trois services, deux services Angular prdfinis
$timeout, $q et le service config. Le service [$timeout] permet d'excuter une fonction aprs qu'un certain temps se soit
coul. Le service [$q] permet de crer des tches asynchrones ;
ligne 4 : une fonction locale [debug] ;
ligne 12 : une fonction locale [waitForSomeTime] ;
lignes 23-26 : l'instance du service [utils]. C'est un objet qui expose deux mthodes, celles des lignes 4 et 12. Notez que les
champs de l'objet peuvent porter des noms quelconques. Par cohrence, on leur a donn les noms des fonctions qu'ils
rfrencent ;
lignes 4-9 : la mthode [debug] crit sur la console un message [message] et ventuellement la reprsentation JSON d'un
objet [data]. Cela permet d'afficher des objets de n'importe quelle complexit ;
lignes 12-20 : la mthode [waitForSomeTime] cre une tche asynchrone qui dure [milliseconds] milli-secondes ;
ligne 14 : cration d'une tche grce l'objet prdfini [$q] (https://docs.angularjs.org/api/ng/service/$q). Ci-dessous,
l'API de la tche appele [deferred] dans la documentation Angular :

http://tahe.developpez.com

187/325

une tche asynchrone [task] est cre par l'instruction [$q.defer()] ;


on la termine l'aide d'une des deux mthodes :
[task.resolve(value)] : qui termine la tche avec succs et renvoie la valeur [value] ceux qui attendent la fin de la
tche ;
[task.reject(value)] : qui termine la tche avec chec et renvoie la valeur [value] ceux qui attendent la fin de la
tche ;
La tche [task] peut rgulirement donner des informations ceux qui attendent sa fin :
[task.notify(value)] : envoie la valeur [value] ceux qui attendent la fin de la tche. La tche continue s'excuter ;

Ceux qui veulent attendre la fin de la tche utilisent le champ [promise] de celle-ci :
var promise=[task].promise ;

L'objet [promise] a l'API suivante (http://www.frangular.com/2012/12/api-promise-angularjs.html) :

Pour grer la fois le succs et l'chec de la tche, on crira :


1. var promise=[task].promise;
2. promise.then(successCallback, errorCallBack);
3. promise['finally'](finallyCallback);

ligne 1 : on rcupre la promesse de la tche ;


ligne 2 : on dfinit les fonctions excuter en cas de succs ou en cas d'chec. On peut ne pas mettre de fonction d'chec.
La fonction [successCallback] ne sera excute qu' la fin de la tche [task] avec succs [task.resolve()]. La fonction
[errorCallBack] ne sera excute qu' la fin de la tche [task] avec chec [task.reject()].

http://tahe.developpez.com

188/325

ligne 3 : on dfinit la fonction excuter aprs que l'une des deux fonction prcdentes se soit excute. On met ici, le
code commun aux deux fonctions [successCallback, errorCallBack].

Revenons au code de la fonction [waitForSomeTime] :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10. };

// attente
function waitForSomeTime(milliseconds) {
// attente asynchrone de milliseconds millisecondes
var task = $q.defer();
$timeout(function () {
task.resolve();
}, milliseconds);
// on retourne la tche
return task;

ligne 4 : une tche est cre ;


lignes 5-7 : l'objet [$timeout] permet de dfinir une fonction (1er paramtre) qui s'excute aprs un certain dlai exprim
en millisecondes (2me paramtre). Ici le second paramtre de la fonction [$timeout] est le paramtre de la mthode (ligne
1) ;
ligne 6 : au bout du dlai [milliseconds], la tche est termine avec succs ;
ligne 9 : on retourne la tche [task]. Il faut comprendre ici que la ligne 9 est excute immdiatement aprs la dfinition de
l'objet [$timeout]. On n'attend pas que le dlai [milliseconds] se soit coul. Le code des lignes 2-10 est donc excut
deux moments diffrents :
une premire fois qui dfinit l'objet [$timeout] ;
une seconde fois lorsque le dlai [milliseconds] est coul ;
On a l, une fonction asynchrone : son rsultat est obtenu un moment ultrieur celui de son excution.

Le code du contrleur qui utilise le service [config] est le suivant :


1. // contrleur
2. angular.module("rdvmedecins")
3.
.controller('rdvMedecinsCtrl', ['$scope', 'utils', 'config', '$filter',
4.
function ($scope, utils, config, $filter) {
5.
// ------------------- initialisation modle
6.
// message d'attente
7.
$scope.waiting = {text: config.msgWaiting, visible: false, cancel: cancel, time:
undefined};
8.
$scope.waitingTimeText = config.waitingTimeText;
9.
// tche d'attente
10.
var task;
11.
// logs
12.
utils.debug("libell temps d'attente", $filter('translate')($scope.waitingTimeText));
13.
utils.debug("locales['fr']=", config.locales['fr']);
14.
15.
// excution action
16.
$scope.execute = function () {
17.
// log
18.
utils.debug('dbut', new Date());
19.
// on affiche le msg d'attente
20.
$scope.waiting.visible = true;
21.
// attente simule
22.
task = utils.waitForSomeTime($scope.waiting.time);
23.
// fin d'attente
24.
task.promise.then(function () {
25.
// succs
26.
utils.debug('fin', new Date());
27.
}, function () {
28.
// chec
29.
utils.debug('Opration annule')
30.
});
31.
task.promise['finally'](function () {
32.
// fin d'attente dans tous les cas
33.
$scope.waiting.visible = false;
34.
});

http://tahe.developpez.com

189/325

35.
36.
37.
38.
39.
40.
41.
42.
43.

};
// annulation attente
function cancel() {
// on termine la tche
task.reject();
}
}]);

ligne 3 : le contrleur utilise le service [config] ;


ligne 7 : on a ajout le champ [time] l'objet [$scope.waiting]. L'objet [$scope.waiting.time] reoit la valeur du dlai
d'attente fix par l'utilisateur ;
ligne 8 : la cl du message d'attente affich par la vue est place dans le modle [$scope.waitingTimeText]. De faon
gnrale tout ce qui est affich par une vue V doit tre plac dans l'objet [$scope] ;
ligne 10 : une variable locale. Elle n'est pas expose la vue V ;
lignes 12-13 : utilisation de la mthode [debug] du service [config]. On obtient le rsultat suivant sur la console :

1. libell temps d'attente : "Temps d'attente : "


2. locales['fr']= : {"DATETIME_FORMATS":{"AMPMS":["AM","PM"],"DAY":
["dimanche","lundi","mardi","mercredi","jeudi","vendredi","samedi"],"MONTH":
["janvier","fvrier","mars","avril","mai","juin","juillet","aot","septembre","octobre","novembr
e","dcembre"],"SHORTDAY":["dim.","lun.","mar.","mer.","jeu.","ven.","sam."],"SHORTMONTH":
["janv.","fvr.","mars","avr.","mai","juin","juil.","aot","sept.","oct.","nov.","dc."],"fullDa
te":"EEEE d MMMM y","longDate":"d MMMM y","medium":"d MMM y HH:mm:ss","mediumDate":"d MMM
y","mediumTime":"HH:mm:ss","short":"dd/MM/yy
HH:mm","shortDate":"dd/MM/yy","shortTime":"HH:mm"},"NUMBER_FORMATS":
{"CURRENCY_SYM":"","DECIMAL_SEP":",","GROUP_SEP":" ","PATTERNS":
[{"gSize":3,"lgSize":3,"macFrac":0,"maxFrac":3,"minFrac":0,"minInt":1,"negPre":"-","negSuf":"","
posPre":"","posSuf":""},
{"gSize":3,"lgSize":3,"macFrac":0,"maxFrac":2,"minFrac":2,"minInt":1,"negPre":"(","negSuf":" )"
,"posPre":"","posSuf":" "}]},"id":"fr-fr"}

Ligne 2, on obtient la notation JSON de l'objet locales['fr'].

ligne 16 : la mthode excute lorsque l'utilisateur clique sur le bouton [Executer] ;


ligne 18 : affiche l'heure de dbut d'excution de la mthode ;
ligne 22 : on lance la tche [waitForSomeTime]. On n'attend pas sa fin. L'excution continue avec la ligne 24 suivante ;
lignes 24-30 : on dfinit les fonctions excuter lorsque la tche se termine avec succs (ligne 26) et en cas d'erreur (ligne
29) ;
ligne 26 : affiche l'heure de fin d'excution de la mthode ;
ligne 29 : affiche que l'opration a t annule. Cela est provoqu uniquement lorsque l'utilisateur clique sur le bouton
[Annuler]. L'instruction de la ligne 41, arrte alors la tche asynchrone avec un code d'chec ;
lignes 31-34 : on dfinit la fonction excuter aprs l'excution d'une des deux fonctions prcdentes ;

Il est important de comprendre les squences d'excution de ce code. Dans le cas o l'utilisateur met un dlai de 3 secondes et
n'annule pas l'attente :

lorsqu'il clique sur le bouton [Excuter], la fonction [$scope.execute] s'excute. Les lignes 16-34 sont excutes sans attente
des 3 secondes. A la fin de cette excution, la vue V est synchronise avec le modle M. Le message d'attente est affich
(ng-show=$scope.waiting.visible=true, ligne 20) et le formulaire est cach (ng-hide=$scope.waiting.visible=true, ligne 20) ;

partir de ce moment l'utilisateur peut interagir de nouveau avec la vue. Il peut notamment cliquer sur le bouton
[Annuler] ;

s'il ne le fait pas, au bout de 3 secondes, la fonction du [$timeout] (cf lignes 5-7 ci-dessous) s'excute :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.

http://tahe.developpez.com

// attente
function waitForSomeTime(milliseconds) {
// attente asynchrone de milliseconds millisecondes
var task = $q.defer();
$timeout(function () {
task.resolve();
}, milliseconds);
// on retourne la tche
return task;
};

190/325

au bout de 3 secondes donc, du code est excut. Ce code termine la tche [task] avec un code de succs (resolve). Cela va
dclencher l'excution de tous les codes qui attendaient cette fin (ligne 4 ci-dessous) :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.

// attente simule
task = utils.waitForSomeTime($scope.waiting.time);
// fin d'attente
task.promise.then(function () {
// succs
utils.debug('fin', new Date());
}, function () {
// chec
utils.debug('Opration annule')
});
task.promise['finally'](function () {
// fin d'attente dans tous les cas
$scope.waiting.visible = false;
});

la ligne 6 ci-dessus (fin avec succs) va donc tre excute. Puis ce sera le tour des lignes 11-14. Une fois ce code excut,
on revient la vue V qui va alors tre synchronise avec son modle M. Le message d'attente est cach (ngshow=$scope.waiting.visible=false, ligne 13) et le formulaire est affich (ng-hide=$scope.waiting.visible=false, ligne 13) ;

Les affichages cran sont alors les suivants :


dbut : "2014-06-23T15:05:58.480Z"
fin : "2014-06-23T15:06:01.481Z"

On voit ci-dessus, le dlai de 3 secondes (06:01-05:58) entre le dbut et la fin de l'attente. Si on contraire, l'utilisateur annule l'attente
avant les 3 secondes, on a l'affichage suivant :
dbut : "2014-06-23T15:08:09.564Z"
Opration annule

Pour terminer, il est important de comprendre qu' tout moment il n'y a qu'un tread d'excution appel le thread de l'UI (User
Interface). La fin d'une tche asynchrone est signale par un vnement exactement comme l'est le clic sur un bouton. Cet
vnement n'est pas trait immdiatement. Il est mis dans la file d'attente des vnements qui attendent leur excution. Lorsque
vient son tour, il est trait. Ce traitement utilise le thread de l'UI et donc pendant ce temps, l'interface est gele. Elle ne ragit pas
aux sollicitations de l'utilisateur. Pour cela, il est important que le traitement d'un vnement soit rapide. Parce que chaque
vnement est trait par le thread de l'UI, on n'a jamais rgler des problmes de synchronisation entre threads s'excutant en
mme temps. Il n'y a, chaque instant, que le thread de l'UI qui s'excute.

3.7.6

Exemple 6 : les services HTTP

Nous prsentons maintenant le service [dao] qui communique avec le serveur web :

Application web / navigateur


couche [prsentation]

couche [services]

Routeur

Utilisateur

config
utils

http://tahe.developpez.com

Donnes
rseau

dao

191/325

3.7.6.1

La vue V
Application web / navigateur
couche [prsentation]

couche [services]

Routeur

Utilisateur

config
utils

Donnes
rseau

dao

Nous allons crire un formulaire pour demander la liste des mdecins :

Nous dupliquons [app-01.html] dans [app-16.html] que nous modifions ensuite de la faon suivante :
1. <div class="container" ng-cloak="">
2.
<h1>Rdvmedecins - v1</h1>
3.
4.
<!-- le message d'attente -->
5.
<div class="alert alert-warning" ng-show="waiting.visible" ng-cloak="">
6.
<h1>{{ waiting.text | translate}}
7.
<button class="btn btn-primary pull-right" ng-click="waiting.cancel()">{{'msg_cancel'|
translate}}</button>
8.
<img src="assets/images/waiting.gif" alt=""/>
9.
</h1>
10. </div>
11.
12. <!-- la demande -->
13. <div class="alert alert-info" ng-hide="waiting.visible">
14.
<div class="form-group">
15.
<label for="waitingTime">{{waitingTimeText | translate}}</label>
16.
<input type="text" id="waitingTime" ng-model="waiting.time"/>
17.
</div>
18.
<div class="form-group">
19.
<label for="urlServer">{{urlServerLabel | translate}}</label>

http://tahe.developpez.com

192/325

20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.

<input type="text" id="urlServer" ng-model="server.url"/>


</div>
<div class="form-group">
<label for="login">{{loginLabel | translate}}</label>
<input type="text" id="login" ng-model="server.login"/>
</div>
<div class="form-group">
<label for="password">{{passwordLabel | translate}}</label>
<input type="password" id="password" ng-model="server.password"/>
</div>
<button class="btn btn-primary" ng-click="execute()">{{medecins.title|
translate:medecins.model}}</button>
31. </div>
32.
33. <!-- la liste des mdecins -->
34. <div class="alert alert-success" ng-show="medecins.show">
35.
{{medecins.title|translate:medecins.model}}
36.
<ul>
37.
<li ng-repeat="medecin in medecins.data">{{medecin.titre}}{{medecin.prenom}}
{{medecin.nom}}</li>
38.
</ul>
39. </div>
40.
41. <!-- la liste d'erreurs -->
42. <div class="alert alert-danger" ng-show="errors.show">
43.
{{errors.title|translate:errors.model}}
44.
<ul>
45.
<li ng-repeat="message in errors.messages">{{message|translate}}</li>
46.
</ul>
47. </div>
48.
49. </div>
50. ...
51. <script type="text/javascript" src="rdvmedecins-04.js"></script>

3.7.6.2

lignes 13-31 : implmentent le formulaire. Celui-ci n'est pas visible lorsque le message d'attente est affich ( nghide="waiting.visible"). On retiendra que les quatre saisies sont mmorises dans (attributs ng-model) [waiting.time (ligne
16), server.url (ligne 20), server.login (ligne 24), server.password (ligne 28)] ;
lignes 34-39 : affichent la liste des mdecins. Cette liste n'est pas toujours visible (ng-show="medecins.show").
ligne 35 : une alternative la syntaxe <div ... translate="{{medecins.title}}" translate-values="{{medecins.model}}">
dj rencontre ;
ligne 36 : une liste non ordonne ;
ligne 37 : la liste des mdecins sera trouve dans le modle [medecins.data]. La directive Angular [ng-repeat] permet de
parcourir une liste. La syntaxe ng-repeat="medecin in medecins.data" demande ce que la balise <li> soit rpte pour chaque
lment de la liste [medecins.data]. L'lment courant de la liste est appele [medecin] ;
ligne 37 : pour chaque <li>, on crit le titre, le prnom et le nom du mdecin courant dsign par la variable [medecin] ;
lignes 42-47 : affichent la liste des erreurs. Cette liste n'est pas toujours visible (ng-show="errors.show"). Cet affichage suit le
mme modle que l'affichage de la liste des mdecins. De faon gnrale, pour afficher une liste d'objets, on utilise la
directive Angular [ng-repeat] ;
ligne 51 : le code Javascript est maintenant dans le fichier [rdvmedecins-04]

Le contrleur C et le modle M

http://tahe.developpez.com

193/325

Application web / navigateur


couche [prsentation]

couche [services]

Routeur

Utilisateur

config
utils

Donnes
rseau

dao

Le code Javascript volue de la faon suivante :

3.7.6.3

lignes 6-9 : le module [rdvmedecins] dclare une dpendance sur le module [base64] fourni par la bibliothque [angularbase64] qui est l'une des dpendances du projet. Ce module sert coder en Base64, la chane [login:password] envoye au
service web pour s'authentifier ;
lignes 12-13 : la fonction d'initialisation qui contient nos messages internationaliss. De nouveaux messages apparaissent.
Nous ne les prsenterons plus ;
lignes 69-70 : le service [config] qui paramtre notre application. De nouvelles cls de message y sont ajoutes. Nous ne les
prsenterons plus ;
lignes 318-319 : le service [utils] qui contient des mthodes utilitaires. De nouvelles y sont rajoutes. Nous les
prsenterons ;
lignes 385-386 : le service [dao] charg des changes avec le service web. C'est sur lui que nous allons nous concentrer ;
lignes 467-468 : le contrleur C de la vue V que nous venons de prsenter. Nous allons le prsenter maintenant car c'est lui
le chef d'orchestre qui ragit aux demandes de l'utilisateur ;

Le contrleur C

Le code du contrleur est le suivant :


1. angular.module("rdvmedecins")
2.
.controller('rdvMedecinsCtrl', ['$scope', 'utils', 'config', 'dao', '$translate',
3.
function ($scope, utils, config, dao, $translate) {
4.
// ------------------- initialisation modle
5.
// modle

http://tahe.developpez.com

194/325

6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.

$scope.waiting = {text: config.msgWaiting, visible: false, cancel: cancel, time:


undefined};
$scope.waitingTimeText = config.waitingTimeText;
$scope.server = {url: undefined, login: undefined, password: undefined};
$scope.medecins = {title: config.listMedecins, show: false, model: {}};
$scope.errors = {show: false, model: {}};
$scope.urlServerLabel = config.urlServerLabel;
$scope.loginLabel = config.loginLabel;
$scope.passwordLabel = config.passwordLabel;
// tche asynchrone
var task;
// excution action
$scope.execute = function () {
// on met jour l'UI
$scope.waiting.visible = true;
$scope.medecins.show = false;
$scope.errors.show = false;
// attente simule
task = utils.waitForSomeTime($scope.waiting.time);
var promise = task.promise;
// attente
promise = promise.then(function () {
// on demande la liste des mdecins;
task = dao.getData($scope.server.url, $scope.server.login, $scope.server.password,
config.urlSvrMedecins);
return task.promise;
});
// on analyse le rsultat de l'appel prcdent
promise.then(function (result) {
// result={err: 0, data: [med1, med2, ...]}
// result={err: n, messages: [msg1, msg2, ...]}
if (result.err == 0) {
// on met les donnes acquises dans le modle
$scope.medecins.data = result.data;
// on met jour l'UI
$scope.medecins.show = true;
$scope.waiting.visible = false;
} else {
// il y a eu des erreurs pour obtenir la liste des mdecins
$scope.errors = { title: config.getMedecinsErrors, messages:
utils.getErrors(result), show: true, model: {}};
// on met jour l'UI
$scope.waiting.visible = false;
}
});
};

46.
47.
48.
49.
50.
51.
52.
// annulation attente
53.
function cancel() {
54.
// on termine la tche
55.
task.reject();
56.
// on met jour l'UI
57.
$scope.waiting.visible = false;
58.
$scope.medecins.show = false;
59.
$scope.errors.show = false;
60.
}
61.
62.
}
63. ])
64. ;

ligne 2 : le contrleur a une nouvelle dpendance, celle sur le service [dao] ;


lignes 6-13 : le modle M de la vue V est initialis pour le 1er affichage de celle-ci ;
ligne 8 : [$scope.server] va tre utilis pour rcuprer trois des quatre informations du formulaire V, la quatrime tant
mmorise dans [$scope.waiting.time] (ligne 6) ;

http://tahe.developpez.com

195/325

ligne 9 : [$scope.medecins] va rassembler les informations ncessaires l'affichage de la liste des mdecins :
1.
<!-- la liste des mdecins -->
2.
<div class="alert alert-success" ng-show="medecins.show">
3.
{{medecins.title|translate:medecins.model}}
4.
<ul>
5.
<li ng-repeat="medecin in medecins.data">{{medecin.titre}}{{medecin.prenom}}
{{medecin.nom}}</li>
6.
</ul>
7. </div>

L'attribut [medecins.title] sera le titre du bandeau. Il est dfini dans le service [config]. L'attribut [medecins.show] va
contrler l'affichage ou non du bandeau (attribut ng-show="medecins.show"). L'attribut [medecins.model] est un
dictionnaire vide et le restera. Il sert simplement illustrer l'utilisation de la variante de traduction utilise ligne 3. Non
dfini encore, l'attribut [medecins.data] qui contiendra la liste des mdecins (ligne 5).

ligne 10 : [$scope.errors] va rassembler les informations ncessaires l'affichage de la liste des erreurs :
1.
<!-- la liste d'erreurs -->
2.
<div class="alert alert-danger" ng-show="errors.show">
3.
{{errors.title|translate:errors.model}}
4.
<ul>
5.
<li ng-repeat="message in errors.messages">{{message|translate}}</li>
6.
</ul>
7. </div>

L'attribut [errors.title] sera le titre du bandeau. Il est dfini dans le service [config]. L'attribut [errors.show] va contrler
l'affichage ou non du bandeau (attribut ng-show="errors .show"). L'attribut [errors.model] est un dictionnaire vide
et le restera. Il sert simplement illustrer l'utilisation de la variante de traduction utilise ligne 3. Non dfini encore,
l'attribut [errors.messages] qui contiendra la liste des messages d'erreur afficher (ligne 5).

ligne 16 : la tche asynchrone. Le contrleur va lancer successivement deux tches asynchrones. Les rfrences sur ces
tches successives seront places dans la variable [task]. Cela permettra de les annuler (ligne 55) ;
ligne 19 : la mthode excute lorsque l'utilisateur clique sur le bouton [Liste des mdecins] :
<button class="btn btn-primary" ng-click="execute()">Liste des mdecins</button>

lignes 21-23 : l'interface visuelle est mise jour : le message d'attente est affich, tout le reste est cach ;
ligne 25 : on cre la tche asynchrone de l'attente. On recevra un signal (tche ralise) au bout du temps saisi par
l'utilisateur dans le formulaire ;
ligne 26 : on rcupre la promesse de la tche asynchrone. C'est avec elle que le programme qui lance la tche travaille. Il
faut cependant avoir la rfrence de la tche elle-mme afin de pouvoir l'annuler (ligne 55) ;
lignes 28-32 : on dfinit le travail faire lorsque l'attente sera termine ;
ligne 30 : on utilise la mthode [dao.getData] pour lancer une nouvelle tche asynchrone. On lui passe les informations
dont elle a besoin :
l'URL racine du service web [$scope.server.url], par exemple [http://localhost:8080];
le login [$scope.server.login] pour s'identifier, par exemple [admin];
le mot de passe [$scope.server.password] pour s'identifier, par exemple [admin];
l'URL qui rend le service demand [config.urlSvrMedecins], ici [/getAllMedecins]. Au total l'URL complte sera
[http://localhost:8080/getAllMedecins] ;
La mthode [dao.getData] rend un rsultat qui a deux formes possibles :

{err: 0, data: [med1, med2, ...]} o [medi] est un objet reprsentant un mdecin (titre, prenom, nom),

{err: n, messages: [msg1, msg2, ...]} o [msgi] est un message d'erreur et n est diffrent de 0 ;

ligne 31 : on retourne la promesse de la tche. L il y a quelque chose comprendre. On a deux promesses :


promise.then() : rend une premire promesse [promise1] ;
return task.promise : rend une seconde promesse [promise2] ;
au final promise=promise.then(... ;return task.promise) est une chane de deux promesses [promise2.promise1].
[promise1] ne sera value que lorsque la promesse [promise2] sera obtenue, --d lorsque la tche [dao.getData] sera
termine. La promesse [promise1] ne dpend d'aucune tche asynchrone. Elle sera donc obtenue immdiatement ;

http://tahe.developpez.com

196/325

lignes 34-50 : de l'explication prcdente, il dcoule que ces lignes se seront excute que lorsque la tche [dao.getData]
sera termine. Le paramtre [result] pass la fonction de la ligne 34 est construit par la mthode [dao.getData] et transmis
au code appelant par l'opration [task.resolve(result)] o [result] est de la forme suivante :
{err: 0, data: [med1, med2, ...]} o [medi] est un objet reprsentant un mdecin (titre, prenom, nom),
{err: n, messages: [msg1, msg2, ...]} o [msgi] est un message d'erreur et n est diffrent de 0 ;
ligne 37 : on regarde le code d'erreur [result.err] ;
lignes 38-42 : s'il n'y a pas d'erreur (result.err==0), alors on rcupre la liste des mdecins et on l'affiche ;
lignes 44-47 : si au contraire il y a erreur (result.err !=0), alors on rcupre la liste des messages d'erreur et on l'affiche ;
lignes 53-56 : le message d'attente avec son bouton d'annulation est prsent tant que les deux oprations asynchrones ne
sont pas termines. Voyons ce qui se passe selon le moment de l'annulation :
il faut tout d'abord comprendre que les lignes 19-50 sont excutes d'une traite. Une seule tche asynchrone a alors
t lance, celle de la ligne 25,
aprs cette premire excution, la vue V est mise jour et donc le bandeau d'attente et son bouton d'annulation est
visible. Si l'utilisateur annule l'attente avant que la tche de la ligne 25 ne soit termine, la mthode de la ligne 53 est
alors excute et la tche est annule avec chec (ligne 55) ;
lignes 56-59 : l'interface est mise jour : on raffiche le formulaire et tout le reste est cach,
il a alors retour la vue V et le navigateur va traiter l'vnement suivant. Puisqu'il y a eu fin de tche, la promesse de
cette tche est obtenue, ce qui cre un vnement. Il est alors trait ;
les lignes 28-32 sont ensuite excutes. Il n'y a pas de fonction dfinie pour le cas d'chec, donc aucun code n'est
excut. On obtient une nouvelle promesse, celle toujours rendue par [promise.then] et toujours obtenue,
l'venement ayant t trait, il y a retour la vue V et le navigateur va traiter l'vnement suivant. Puisque la [promise]
de la ligne 28 a t traite, celle de la ligne 34 va tre rsolue, ce qui va provoquer un nouvel vnement. Il est alors
trait ;
les lignes 34-49 vont alors tre excutes leur tour, car la promesse utilise ligne 34 a t obtenue. De nouveau, parce
qu'il n'y a pas de fonction dfinie pour le cas d'chec, aucun code n'est excut,
on arrive ainsi la ligne 50. Il n'y a plus d'attente de tche et la nouvelle vue V est affiche ;
supposons maintenant que l'annulation intervient pendant que la seconde tche asynchrone [dao.getData] est en cours
d'excution. Le raisonnement prcdent peut tre tenu de nouveau. La fin de la tche va provoquer l'excution des
lignes 34-50 avec une fin de tche avec chec. On va dcouvrir bientt que la mthode [dao.getData] ralise un appel
HTTP asynchrone vers le service web. Cet appel ne sera pas annul mais son rsultat ne sera pas exploit.

Il est important de comprendre ce va et vient constant entre l'affichage de la vue V et le traitement des vnements du navigateur.
Les vnements sont provoqus par l'utilisateur (un clic) ou par des oprations systme telles que la fin d'une opration asynchrone.
L'tat de repos du navigateur est l'affichage de la vue V. Il est tir de ce repos par un vnement qui se produit et qu'il traite alors.
Ds que l'vnement a t trait, il revient son tat de repos. La vue V est alors mise jour si l'vnement trait a modifi son
modle M. Le navigateur est tir de son tat de repos par l'vnement suivant.
Tout se passe dans un unique thread. Deux vnements ne sont jamais traits simultanment . Leur excution est
squentielle. Le navigateur ne passe l'vnement suivant que lorsque le prcdent lui laisse la main, en gnral parce qu'il a t
trait totalement.
Il nous reste un point expliquer. Pour afficher les messages d'erreur, nous crivons :
$scope.errors = { title: config.getMedecinsErrors, messages: utils.getErrors(result), show: true,
model: {}};

La liste des messages est fournie par la mthode [utils.getErrors] dfinie dans le service [utils]. Cette mthode est la suivante :
1. // analyse des erreurs dans la rponse du serveur JSON
2.
function getErrors(data) {
3.
// data {err:n, messages:[]}, err!=0
4.
// erreurs
5.
var errors = [];
6.
// code d'erreur
7.
var err = data.err;
8.
switch (err) {
9.
case 2 :
10.
// not authorized
11.
errors.push('not_authorized');
12.
break;
13.
case 3 :
14.
// forbidden
15.
errors.push('forbidden');
16.
break;

http://tahe.developpez.com

197/325

17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.

3.7.6.4

case 4 :
// erreur locale
errors.push('not_http_error');
break;
case 6 :
// document non trouv
errors.push('not_found');
break;
default :
// autres cas
errors = data.messages;
break;
}
// si pas de msg, on en met un
if (! errors || errors.length == 0) {
errors=['error_unknown'];
}
// on rend la liste des erreurs
return errors;
}

lignes 2-3 : le paramtre [data] reu est un objet avec deux attributs :
[err] : un code d'erreur ;
[messages] : une liste de messages ;
ligne 5 : on va consruire un tableau de messages d'erreur. Ces messages sont internationaliss. Pour cette raison, ce ne sont
pas les messages eux-mmes qu'on met dans le tableau, mais leurs cls d'internationalisation sauf la ligne 27. Dans ce cas,
on utilise l'attribut [messages] du paramtre [data]. Ces messages sont de vrais messages et non des cls de message. La vue
V va cependant les traiter comme des cls de message qui ne seront alors pas trouves. Dans ce cas, le module [translate]
affiche la cl de message qu'il n'a pas trouve, donc ici un vrai message. C'est le rsultat souhait ;
lignes 32-34 : traitent le cas ou [data.messages] ligne 27 vaut null. Cela arrive avec le service web crit. Il aurait fallu viter
ce cas.

Le service [dao]
Application web / navigateur
couche [prsentation]

couche [services]

Routeur

Utilisateur

config
utils

Donnes
rseau

dao

Le service [dao] assure les changes HTTP avec le service web / JSON. Son code est le suivant :
1. angular.module("rdvmedecins")
2.
.factory('dao', ['$http', '$q', 'config', '$base64', 'utils',
3.
function ($http, $q, config, $base64, utils) {
4.
5.
// logs
6.
utils.debug("[dao] init");
7.
8.
// ----------------------------------mthodes prives
9.
// obtenir des donnes auprs du service web
10.
function getData(serverUrl, username, password, urlAction, info) {

http://tahe.developpez.com

198/325

11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.

// opration asynchrone
var task = $q.defer();
// url requte HTTP
var url = serverUrl + urlAction;
// authentification basique
var basic = "Basic " + $base64.encode(username + ":" + password);
// la rponse
var rponse;
// les requtes http doivent tre toutes authentifies
var headers = $http.defaults.headers.common;
headers.Authorization = basic;
// on fait la requte HTTP
var promise;
if (info) {
promise = $http.post(url, info, {timeout: config.timeout});
} else {
promise = $http.get(url, {timeout: config.timeout});
}
promise.then(success, failure);
// on retourne la tche elle-mme afin qu'elle puisse tre annule
return task;
// success
function success(response) {
// response.data={status:0, data:[med1, med2, ...]} ou {status:x, data:[msg1,
msg2, ...]
utils.debug("[dao] getData[" + urlAction + "] success rponse", response);
// rponse
var payLoad = response.data;
rponse = payLoad.status == 0 ? {err: 0, data: payLoad.data} : {err: 1, messages:
payLoad.data};
// on rend la rponse
task.resolve(rponse);
}

http://tahe.developpez.com

// failure
function failure(response) {
utils.debug("[dao] getData[" + urlAction + "] error rponse", response);
// on analyse le status
var status = response.status;
var error;
switch (status) {
case 401 :
// unauthorized
error = 2;
break;
case 403:
// forbidden
error = 3;
break;
case 404:
// not found
error = 6;
break;
case 0:
// erreur locale
error = 4;
break;
default:
// autre chose
error = 5;
}
// on rend la rponse
task.resolve({err: error, messages: [response.statusText]});
}

199/325

76.
77.
78.
79.
80. }]);

// --------------------- instance du service [dao]


return {
getData: getData
}

lignes 77-79 : le service n'a qu'un unique champ : la mthode [getData] qui permet d'obtenir des informations auprs du
service web / JSON ;
ligne 2 : apparat une dpendance [$http] que nous n'avions pas encore rencontre. C'est un service prdfini d'Angular qui
permet le dialogue HTTP avec une entit distante ;
ligne 6 : un log pour voir quel moment de la vie de l'application, le code est excut ;
ligne 10 : la mthode [getData] admet cinq paramtres :
[serverUrl] : l'URL racine du service web (http://localhost:8080) ;
[urlAction] : l'URL du service particulier demand (/getAllMedecins) ;
[username] : le login de l'utilisateur ;
[password] : son mot de passe ;
[info] : objet rassemblant des informations complmentaires lorsque l'URL du service particulier demand est
demand via une opration POST. Dans le cas de l'URL (/getAllMedecins), ce paramtre n'a pas t pass. Il est donc
[undefined] ;
ligne 12 : on cre une tche asynchrone ;
ligne 14 : l'URL complte du service demand (http://localhost:8080/getAllMedecins);
ligne 16 : l'authentification se fait en envoyant l'entte HTTP suivant :
Authorization:Basic code

o [code] est le code Base64 de la chaine [username:password] ;


La ligne 16 construit la partie [Basic code] de l'entte HTTP ;

ligne 18 : la rponse du service web ;


ligne 20 : les enttes HTTP envoys par dfaut par Angular dans une requte HTTP sont dfinis dans l'objet
[$http.defaults.headers.common]. L'entte [Authorization:Basic code] n'en fait pas partie ;
ligne 21 : on l'ajoute aux enttes HTTP envoyer systmatiquement. A gauche de l'affectation, on a l'entte
[Authorization] initialiser et droite la valeur de l'entte, ici la valeur dfinie ligne 16. Ainsi si on crit :
headers.Authorization = 'x';

Angular enverra l'entte HTTP :


Authorization : x

ligne 23 : les mthodes du service [$http] renvoient des promesses. Elle seront mmorises dans la variable [promise] ;
ligne 27 : parce qu'ici, le paramtre [info] a la valeur [undefined], c'est la ligne 27 qui est excute. L'URL
(http://localhost:8080/getAllMedecins) est demande avec un GET. Pour ne pas attendre trop longtemps, on fixe un dlai
d'attente maximum (timeout) pour obtenir la rponse du serveur. Par dfaut, ce dlai est d'une seconde ;
ligne 29 : on dfinit les deux mthodes excuter lorsque la promesse est obtenue :
[success] : dfinie ligne 34, est la mthode excuter lorsque la promesse est obtenue sur un succs de la tche ;
[failure] : dfinie ligne 45, est la mthode excuter lorsque la promesse est obtenue sur un chec de la tche ;
les deux mthodes (on devrait dire fonctions) sont dfinies l'intrieur de la fonction [getData]. C'est possible en
Javascript. Les variables dfines dans [getData] sont connues dans les deux fonctions internes [success, failure] ;
ligne 31 : on retourne la tche cre ligne 12. Il faut se rappeler ici le code appelant :

1.
2.
3.

promise = promise.then(function () {
// on demande la liste des mdecins;
task = dao.getData($scope.server.url, $scope.server.login, $scope.server.password,
config.urlSvrMedecins);
4.
return task.promise;
5. });

Ligne 3 ci-dessus, on rcupre bien une tche.


ligne 34 : la fonction [success] est excute plus tard dans le temps, lorsque l'appel HTTP se termine avec succs. Cette
notion de succs est lie la premire ligne d'une rponse HTTP. Celle-ci a la forme :
HTTP/1.1 code texte

http://tahe.developpez.com

200/325

Le code est un texte de trois chiffres qui indique si l'appel a russi ou non. Grossirement, on peut dire que les codes 2xx
et 3xx sont des codes de russite, les autres tant des codes d'chec. Le texte est un court texte d'explication. Voici deux
rponses possibles, l'un en cas de russite, l'autre en cas d'chec :
HTTP/1.1 200 OK
HTTP/1.1 404 Not Found

ligne 36 : on affiche sur la console la rponse du serveur. Dans l'erreur [404 Not Found], on obtient quelque chose
comme :
[dao] getData[/getAllMedecins] error rponse : {"data":"...","status":404,"config":
{...},"statusText":"Not Found"}

Dans cette rponse, nous n'utiliserons que les champs [data], [status] et [statusText].

ligne 38 : on rcupre le champ [data] de la rponse. Il aura l'une des formes suivantes :

{status: 0, data: [med1, med2, ...]} o [medi] est un objet reprsentant un mdecin (titre, prenom, nom),

{status: n, data: [msg1, msg2, ...]} o [msgi] est un message d'erreur et n est diffrent de 0 ;

ligne 39 : on construit la rponse {0,data} ou {n,messages}. La premire rponse contient les mdecins dans le champ
[data]. La seconde signale une erreur qui s'est produite ct serveur. Celui a gr celle-ci, gnr un code d'erreur dans
[err] et une liste de messages d'erreur dans [data]. Dans les deux cas, il renvoie un code HTTP 200 indiquant que l'ordre
HTTP a t trait compltement. C'est pour cela que les deux cas sont traits dans la mme fonction [success] ;
ligne 41 : la tche est termine [task.resolve] et on rend l'une des deux rponses :
{err: 0, data: [med1, med2, ...]} o [medi] est un objet reprsentant un mdecin (titre, prenom, nom),
{err: n, messages: [msg1, msg2, ...]} o [msgi] est un message d'erreur et n est diffrent de 0 ;

Il faut relier ce code la faon dont cette rponse est rcupre dans le code appelant du contrleur :
1.
2.
3.
4.

http://tahe.developpez.com

// on analyse le rsultat de l'appel prcdent


promise.then(function (result) {
// result={err: 0, data: [med1, med2, ...]}
// result={err: n, messages: [msg1, msg2, ...]}

201/325

5.
6.

...
}

La rponse de [task.resolve(rponse)] se retrouve ci-dessus dans la variable [result].

ligne 45 : la fonction [failure] lorsque la tche asynchrone se termine sur un chec. Il y a deux cas possibles :
le serveur signale cet chec en renvoyant un code qui n'est ni 2xx ni 3xx,
Angular annule l'appel HTTP. Il n'y a alors pas d'appel. Il y a une exception Angular mais pas de code d'erreur HTTP
renvoy par le serveur. C'est par exemple le cas, si on fournit une URL invalide qui ne peut tre appele ;
ligne 46 : on affiche la rponse sur la console ;
ligne 48 : on se rappelle que la rponse du serveur a la forme :
{"data":"...","status":404,"config":{...},"statusText":"Not Found"}

Ligne 48, on rcupre l'attribut [status] ci-dessus ;


lignes 50-70 : partir du code d'erreur HTTP, on va gnrer un nouveau code d'erreur pour cacher aux codes appelants, la
nature HTTP de la mthode [dao.getData]. On peut vrifier que dans le contrleur qui utilise cette mthode, rien ne laisse
supposer qu'il y a un appel HTTP dans la mthode ;
ligne 51 : l'erreur [401] correspond un chec de l'authentification (mot de passe incorrect par exemple),
ligne 55 : l'erreur [403] correspond un appel non autoris. L'utilisateur s'est correctement authentifi mais il n'a pas
les droits suffisants pour demander l'URL qu'il a demande. Cela arrivera avec l'utilisateur [user / user]. Celui-ci existe
bien en base de donnes mais n'a pas le droit d'utiliser l'application. Seul l'utilisateur [admin / admin] a ce droit ;
ligne 59 : l'erreur [404] correspond une URL non trouve. L'erreur peut avoir plusieurs causes :
l'utilisateur a fait une erreur de saisie dans l'URL du service ;
le service web n'a pas t lanc ;
le service web n'a pas rpondu assez vite (dlai d'une seconde par dfaut) ;
ligne 63 : le code d'erreur HTTP 0 n'existe pas. On est dans le cas o Angular n'a pas fait l'appel HTTP demand
parce que l'URL saisie par l'utilisateur est invalide et ne peut tre appele. Nous allons par la suite rencontrer d'autres
cas o Angular est amen ne pas excuter l'appel HTTP demand ;
ligne 72 : on termine la tche avec succs (task.resolve) en renvoyant une rponse du type {err, messages} o le tableau
[messages] n'est form que du seul message [response.statusText]. Dans le cas o Angular n'a pas fait l'appel HTTP
demand, on aura une chane vide ;

Maintenant qu'on a une vue la fois globale et dtaille de l'application, nous pouvons commencer les tests.

3.7.6.5

Tests de l'application - 1

Commenons avec des saisies valides :

http://tahe.developpez.com

202/325

en [1], on met 0 pour ne pas avoir d'attente ;


en [2], on a un message d'erreur alors que les saisies sont correctes. Nous n'avons pas prsent les diffrents messages
d'erreur. Celui affich en [2] est un message gnrique associ l'erreur 0 qui correspond une exception Angular.
Angular a rencotr un problme qui l'a empch de faire un appel HTTP. Dans ces cas l, il faut regarder les logs de la
console Javascript. Il y a deux faons de faire cela :
faire [F12] dans le navigateur Chrome ;
utiliser la console de Webstorm ;

Dans la console de Webstorm, nous trouvons divers messages dont celui-ci :


1. XMLHttpRequest cannot load http://localhost:8080/getAllMedecins. No 'Access-Control-AllowOrigin' header is present on the requested resource. Origin 'http://localhost:63342' is
therefore not allowed access.
2. [dao] getData[/getAllMedecins] error rponse : {"data":"","status":0,"config":
{"method":"GET","transformRequest":[null],"transformResponse":
[null],"timeout":1000,"url":"http://localhost:8080/getAllMedecins","headers":
{"Accept":"application/json, text/plain, */*","Authorization":"Basic
YWRtaW46YWRtaW4="}},"statusText":""}

ligne 1 : Angular signale une erreur sur laquelle nous allons revenir ;
ligne 2 : le log de la mthode [dao.getData]. On y trouve des choses intressantes :
[status] vaut 0, indiquant par l, qu'il n'y a pas eu d'appel HTTP. En consquence [statusText] est vide,
[url] vaut [http://localhost:8080/getAllMedecins] ce qui est correct ;
l'entte HTTP d'authentification [Authorization":"Basic YWRtaW46YWRtaW4=] est lui aussi correct ;

Bon alors pourquoi a n'a pas march ? La phrase cl des logs est [No 'Access-Control-Allow-Origin' header is present]. Pour la
comprendre, il faut faire une longue explication. Commenons par revenir sur l'architecture gnrale de l'application client /
serveur :

http://tahe.developpez.com

203/325

les pages HTML / CSS / JS de l'application Angular viennent du serveur [1] ;


en [2], le service [dao] fait une requte un autre serveur, le serveur [2]. Et bien, a c'est interdit par le navigateur qui
excute l'application Angular parce que c'est une faille de scurit. L'application ne peut interroger que le serveur d'o elle
vient, --d le serveur [1] ;

En fait, il est inexact de dire que le navigateur interdit l'application Angular d'interroger le serveur [2]. Elle l'interroge en fait pour
lui demander s'il autorise un client qui ne vient pas de chez lui l'interroger. On appelle cette technique de partage, le CORS
(Cross-Origin Resource Sharing). Le serveur [2] donne son accord en envoyant des enttes HTTP prcis. C'est parce qu'ici, notre
serveur [2] ne les a pas envoys que le navigateur a refus de faire l'appel HTTP demand par l'application.
Entrons maintenant dans les dtails. Examinons les changes rseau qui ont eu lieu lors de l'appel HTTP. Pour cela, dans le
navigateur Chrome, nous faisons [F12] pour obtenir les outils du dveloppeur et nous slectionnons l'onglet [Network] pour voir les
changes rseau :

http://tahe.developpez.com

204/325

en [1], nous slectionnons l'onglet [network] ;


en [2], nous demandons la liste des mdecins ;

Nous obtenons les informations suivantes dans l'onglet [network] :

en [1], les informations envoyes au serveur ;


en [2], la rponse de celui-ci ;

On peut voir dans [1], que le navigateur a envoy une requte HTTP [OPTIONS] sur l'URL demande. [OPTIONS] est une des
commandes HTTP possibles avec [GET] et [POST] plus connues. Elle permet de demander des informations un serveur
notamment sur les options HTTP qu'il supporte, d'o le nom de la commande. Le serveur fait sa rponse en [2]. Pour indiquer qu'il
accepte des requtes de clients qui ne sont pas dans son domaine, il doit renvoyer un entte particulier appel [Access-ControlAllow-Origin]. Et c'est parce qu'il ne l'a pas renvoy qu'Angular n'a pas excut l'appel HTTP demand et a renvoy l'erreur :
XMLHttpRequest cannot load http://localhost:8080/getAllMedecins. No 'Access-Control-Allow-Origin'
header is present on the requested resource. Origin 'http://localhost:63342' is therefore not allowed
access.

Nous devons donc modifier notre serveur pour qu'il envoie l'entte HTTP attendu.

3.7.6.6

Modification du serveur web / JSON

http://tahe.developpez.com

205/325

Nous revenons sous Eclipse. Afin de conserver l'acquis, nous dupliquons la version actuelle du serveur web / JSON [rdvmedecinswebapi-v2] dans [rdvmedecins-webapi-v3] [1] :

2
1

Nous faisons une premire modification dans [ApplicationModel] qui est l'un des lments de configuration du service web :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.

package rdvmedecins.web.models;

ligne 17 : nous crons un boolen qui indique si on accepte ou non les clients trangers au domaine du serveur ;
lignes 21-23 : la mthode d'accs cette information ;

...
@Component
public class ApplicationModel implements IMetier {
// la couche [mtier]
@Autowired
private IMetier mtier;
// donnes provenant de la couche [mtier]
private List<Medecin> mdecins;
private List<Client> clients;
private List<String> messages;
// donnes de configuration
private boolean CORSneeded = true;
...
public boolean isCORSneeded() {
return CORSneeded;
}
}

Puis nous crons un nouveau contrleur Spring MVC [3] :

La classe [RdvMedecinsCorsController] est la suivante :


1. package rdvmedecins.web.controllers;
2.

http://tahe.developpez.com

206/325

3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.

import javax.servlet.http.HttpServletResponse;

lignes 28-31 : dfinissent un contrleur pour l'URL [/getAllMedecins] lorsqu'elle est demande avec la commande HTTP
[OPTIONS] ;
ligne 29 : la mthode [getAllMedecins] admet pour paramtre l'objet [HttpServletResponse] qui va tre envoy au client
qui a fait la demande. Cet objet est inject par Spring ;
ligne 30 : on dlgue le traitement de la demande la mthode prive des lignes 19-25 ;
lignes 15-16 : l'objet [ApplicationModel] est inject ;
lignes 20-23 : si le serveur est configur pour accepter les clients trangers son domaine, alors on envoie l'entte HTTP :

import
import
import
import

org.springframework.beans.factory.annotation.Autowired;
org.springframework.stereotype.Controller;
org.springframework.web.bind.annotation.RequestMapping;
org.springframework.web.bind.annotation.RequestMethod;

import rdvmedecins.web.models.ApplicationModel;
@Controller
public class RdvMedecinsCorsController {
@Autowired
private ApplicationModel application;
// envoi des options au client
private void sendOptions(HttpServletResponse response) {
if (application.isCORSneeded()) {
// on fixe le header CORS
response.addHeader("Access-Control-Allow-Origin", "*");
}
}

// liste des mdecins


@RequestMapping(value = "/getAllMedecins", method = RequestMethod.OPTIONS)
public void getAllMedecins(HttpServletResponse response) {
sendOptions(response);
}

Access-Control-Allow-Origin: *

qui signifie que le serveur accepte les clients de tout domaine (*).
Nous sommes dsormais prts pour de nouveaux tests. Nous lanons la nouvelle version du service web et nous dcouvrons que le
problme reste entier. Rien n'a chang. Si ligne 30 ci-dessus, on met un affichage console, celui-ci n'est jamais affich montrant par
l que la mthode [getAllMedecins] de la ligne 29 n'est jamais appele.
Aprs quelques recherches, on dcouvre que Spring MVC traite lui-mme les commandes HTTP [OPTIONS] avec un traitement
par dfaut. Aussi c'est toujours Spring qui rpond et jamais la mthode [getAllMedecins] de la ligne 29. Ce comportement par
dfaut de Spring MVC peut tre chang. Nous introduisons une nouvelle classe de configuration, pour configurer le nouveau
comportement :

La nouvelle classe de configuration [WebConfig] est la suivante :

http://tahe.developpez.com

207/325

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.

package rdvmedecins.web.config;

ligne 8 : la classe est une classe de configuration Spring. Elle dclare des beans qui seront placs dans le contexte de
Spring ;
ligne 12 : le bean [dispatcherServlet] sert dfinir la servlet qui gre les demandes des clients. Elle est de type
[DispatcherServlet]. Cette servlet est normalement cre par dfaut. Si on la cre nous-mmes, on peut alors la
configurer ;
ligne 14 : on cre une instance de type [DispatcherServlet] ;
ligne 15 : on demande ce que la servlet fasse suivre l'application les commandes HTTP [OPTIONS] ;
ligne 16 : on rend la servlet ainsi configure ;

import
import
import
import

org.springframework.boot.autoconfigure.EnableAutoConfiguration;
org.springframework.context.annotation.Bean;
org.springframework.web.servlet.DispatcherServlet;
org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

// configuration dispatcherservlet pour les headers CORS


@Bean
public DispatcherServlet dispatcherServlet() {
DispatcherServlet servlet = new DispatcherServlet();
servlet.setDispatchOptionsRequest(true);
return servlet;
}

Il nous reste modifier la classe [AppConfig] :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.

package rdvmedecins.web.config;

ligne 11 : la nouvelle classe de configuration [WebConfig] est importe ;

3.7.6.7

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import rdvmedecins.config.DomainAndPersistenceConfig;
@EnableAutoConfiguration
@ComponentScan(basePackages = { "rdvmedecins.web" })
@Import({ DomainAndPersistenceConfig.class, SecurityConfig.class, WebConfig.class })
public class AppConfig {
}

Tests de l'application - 2

Nous lanons la nouvelle version du service web / JSON et essayons d'obtenir la liste des mdecins avec notre client Angular. Nous
examinons les changes rseau dans l'onglet [Network] :

http://tahe.developpez.com

208/325

en [1], on peut constater que l'entte HTTP [Access-Control-Allow-Origin: *] est dsormais prsent dans la rponse du
serveur. Et pourtant a ne marche toujours pas. Nous examinons en [2], les logs de la console. On y trouve le log suivant :

XMLHttpRequest cannot load http://localhost:8080/getAllMedecins. Request header field Authorization is


not allowed by Access-Control-Allow-Headers

On voit que le navigateur attend un nouvel entte HTTP [Access-Control-Allow-Headers] qui lui dirait qu'on a le droit de
lui envoyer l'entte d'authentification :
Authorization:Basic code

Cela peut tre bon signe. Angular a peut tre voulu envoyer la commande HTTP GET. Mais comme celle-ci est accompagne d'un
entte d'authentification, il demande si le serveur accepte celui-ci.
Nous modifions notre serveur web / JSON pour envoyer cet entte. La classe [RdvMedecinsCorsController] volue comme
suit :
1.
2.
3.
4.
5.
6.
7.
8. }

// envoi des options au client


private void sendOptions(HttpServletResponse response) {
if (application.isCORSneeded()) {
// on fixe le header CORS
response.addHeader("Access-Control-Allow-Origin", "*");
// on autorise le header [Authorization]
response.addHeader("Access-Control-Allow-Headers", "Authorization");

les lignes 6-7 ajoutent l'entte manquant.

Nous relanons le serveur et redemandons la liste des mdecins avec le client Angular :

http://tahe.developpez.com

209/325

Cette fois, c'est bon. Les logs console montrent la rponse reue par la mthode [dao.getData] :
1. [dao] getData[/getAllMedecins] success rponse : {"data":{"status":0,"data":
[{"id":1,"version":1,"titre":"Mme","nom":"PELISSIER","prenom":"Marie"},
{"id":2,"version":1,"titre":"Mr","nom":"BROMARD","prenom":"Jacques"},
{"id":3,"version":1,"titre":"Mr","nom":"JANDOT","prenom":"Philippe"},
{"id":4,"version":1,"titre":"Melle","nom":"JACQUEMOT","prenom":"Justine"}]},"status":200,"config
":{"method":"GET","transformRequest":[null],"transformResponse":
[null],"timeout":1000,"url":"http://localhost:8080/getAllMedecins","headers":
{"Accept":"application/json, text/plain, */*","Authorization":"Basic
YWRtaW46YWRtaW4="}},"statusText":"OK"}

On voit que :

le serveur a renvoy un code d'erreur [status=200] avec le message [statusText=OK]. C'est pourquoi on est dans la
fonction [success] ;

le serveur a renvoy un objet [data] avec deux champs :


[status] : ( ne pas confondre avce le code d'erreur HTTP [status]). Ici [status=0] indique que l'URL
[/getAllMedecins] a t traite sans erreur ;
[data] : qui contient la liste JSON des mdecins ;
Montrons maintenant d'autres cas intressants :
On se trompe dans les identifiants [login, password] :

http://tahe.developpez.com

210/325

On se connecte sous l'identit [user / user] qui n'a pas accs l'application (seul [admin] y a accs) :

Cette fois-ci, l'erreur n'est plus [Erreur d'authentification] mais [Accs refus].

3.7.7

Exemple 7 : liste des clients

Nous reprenons l'application prcdente pour cette fois prsenter la liste des clients dans une liste droulante de type [Bootstrap
select]) (cf paragraphe 3.6.6, page 157).

3.7.7.1

La vue V

La vue initiale sera la suivante :

http://tahe.developpez.com

211/325

Pour obtenir la vue V, nous dupliquons le code [app-16.html] dans [app-17.html] et le modifions de la faon suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.

<div class="container" >


<h1>Rdvmedecins - v1</h1>

lignes 5-7 : le bandeau d'attente ne change pas ;


lignes 10-13 : le formulaire ne change pas, si ce n'est le libell du bouton (ligne 12) ;
lignes 28-30 : le bandeau des erreurs ne change pas ;
lignes 16-25 : l'affichage des clients se fait dans une liste droulante style par le composant [Bootstrap-selectpicker]
(attributs data-style, class, ligne 19) ;
ligne 20 : on utilise la directive [ng-repeat] pour gnrer les diffrentes options de la liste droulante. On notera que le
libell d'une option est de type [Mme Julienne Tatou] et que la valeur de l'option est de type [100] o 100 est l'identifiant
id du client affich ;
ligne 34 : le code Javascript migre dans un nouveau fichier [rdvmedecins-05] ;

<!-- le message d'attente -->


<div class="alert alert-warning" ng-show="waiting.visible" >
...
</div>
<!-- la demande -->
<div class="alert alert-info" ng-hide="waiting.visible" >
...
<button class="btn btn-primary" ng-click="execute()">{{clients.title|translate}}</button>
</div>
<!-- la liste des clients -->
<div class="row" style="margin-top: 20px" ng-show="clients.show">
<div class="col-md-3">
<h2 translate="{{clients.title}}"></h2>
<select data-style="btn-primary" class="selectpicker">
<option ng-repeat="client in clients.data" value="{{client.id}}">
{{client.titre}} {{client.prenom}} {{client.nom}}
</option>
</select>
</div>
</div>
<!-- la liste d'erreurs -->
<div class="alert alert-danger"
...
</div>

ng-show="errors.show">

</div>
....
<script type="text/javascript" src="rdvmedecins-05.js"></script>

http://tahe.developpez.com

212/325

3.7.7.2

Le contrleur C et le modle M

Le code Javascript du fichier [rdvmedecins-05] est obtenu par recopie du fichier [rdvmedecins-04] :

Quasiment rien ne change, sauf dans le contrleur qui est dsormais adapt pour fournir la liste des clients :
1. angular.module("rdvmedecins")
2.
.controller('rdvMedecinsCtrl', ['$scope', 'utils', 'config', 'dao', '$translate',
3.
function ($scope, utils, config, dao, $translate) {
4.
// ------------------- initialisation modle
5.
// modle
6.
$scope.waiting = {text: config.msgWaiting, visible: false, cancel: cancel, time:
undefined};
7.
$scope.waitingTimeText = config.waitingTimeText;
8.
$scope.server = {url: undefined, login: undefined, password: undefined};
9.
$scope.clients = {title: config.listClients, show: false, model: {}};
10.
$scope.errors = {show: false, model: {}};
11.
$scope.urlServerLabel = config.urlServerLabel;
12.
$scope.loginLabel = config.loginLabel;
13.
$scope.passwordLabel = config.passwordLabel;
14.
15.
// tche asynchrone
16.
var task;
17.
18.
// excution action
19.
$scope.execute = function () {
20.
// on met jour l'UI
21.
$scope.waiting.visible = true;
22.
$scope.clients.show = false;
23.
$scope.errors.show = false;
24.
// attente simule
25.
task = utils.waitForSomeTime($scope.waiting.time);
26.
var promise = task.promise;
27.
// attente
28.
promise = promise.then(function () {
29.
// on demande la liste des clients;
30.
task = dao.getData($scope.server.url, $scope.server.login, $scope.server.password,
config.urlSvrClients);
31.
return task.promise;
32.
});
33.
// on analyse le rsultat de l'appel prcdent
34.
promise.then(function (result) {
35.
// result={err: 0, data: [client1, client2, ...]}

http://tahe.developpez.com

213/325

36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.

// result={err: n, messages: [msg1, msg2, ...]}


if (result.err == 0) {
// on met les donnes acquises dans le modle
$scope.clients.data = result.data;
// on met jour l'UI
$scope.clients.show = true;
$scope.waiting.visible = false;
// on style la liste droulante
$('.selectpicker').selectpicker();
} else {
// il y a eu des erreurs pour obtenir la liste des clients
$scope.errors = { title: config.getClientsErrors, messages: utils.getErrors(result),
show: true, model: {}};
48.
// on met jour l'UI
49.
$scope.waiting.visible = false;
50.
}
51.
});
52.
};
53.
54.
// annulation attente
55.
function cancel() {
56.
// on termine la tche
57.
task.reject();
58.
// on met jour l'UI
59.
$scope.waiting.visible = false;
60.
$scope.clients.show = false;
61.
$scope.errors.show = false;
62.
}
63.
}
64. ])
65. ;

trs peu de choses changent dans le contrleur. Il fournissait une liste de mdecins. Il fournit dsormais une liste de
clients ;
ligne 9 : [$scope.clients] sera le modle du bandeau des clients dans la vue V ;
ligne 30 : c'est l'URL [/getAllClients] qui est dsormais utilise ;
lignes 35-36 : les deux formes de rponse rendue par la mthode [dao.getData]. On a maintenant des clients au lieu de
mdecins ;
ligne 44 : une instruction assez rare dans un code Angular. On manipule directement le DOM (Document Object Model).
Ici on veut appliquer la mthode [selectpicker] (fait partie de [bootstrap-select.min.js]) aux lments du DOM qui ont la
classe [selectpicker] [$('.selectpicker')]. Il n'y en a qu'un, la liste droulante :
<select data-style="btn-primary" class="selectpicker" select-enable="">
....

</select>

Au paragraphe 3.6.6, page 157, il a t montr que cela stylisait la liste droulante de la faon suivante :

Comme il a t fait pour les mdecins, nous sommes amens modifier le service web galement.

3.7.7.3

Modification du service web - 1

http://tahe.developpez.com

214/325

La classe [RdvMedecinsController] s'enrichit d'une nouvelle mthode :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.

package rdvmedecins.web.controllers;

lignes 29-32 : la mthode [getAllClients] va grer la demande HTTP [OPTIONS] que va lui envoyer le navigateur ;

3.7.7.4

...
@Controller
public class RdvMedecinsCorsController {
@Autowired
private ApplicationModel application;
// envoi des options au client
private void sendOptions(HttpServletResponse response) {
if (application.isCORSneeded()) {
// on fixe le header CORS
response.addHeader("Access-Control-Allow-Origin", "*");
// on autorise le header [Authorization]
response.addHeader("Access-Control-Allow-Headers", "Authorization");
}
}
// liste des mdecins
@RequestMapping(value = "/getAllMedecins", method = RequestMethod.OPTIONS)
public void getAllMedecins(HttpServletResponse response) {
sendOptions(response);
}

// liste des clients


@RequestMapping(value = "/getAllClients", method = RequestMethod.OPTIONS)
public void getAllClients(HttpServletResponse response) {
sendOptions(response);
}

Tests de l'application 1

Nous sommes dsormais prts pour un test. Nous lanons le serveur web puis entrons des valeurs valides dans le formulaire
Angular. Nous obtenons la rponse suivante :

http://tahe.developpez.com

215/325

Ce message d'erreur est affich lorsqu'Angular n'a pu faire la requte HTTP demande. Il faut en chercher alors les causes dans les
logs de la console. On y trouve le message suivant :
XMLHttpRequest cannot load http://localhost:8080/getAllClients. No 'Access-Control-Allow-Origin' header
is present on the requested resource. Origin 'http://localhost:63342' is therefore not allowed access.

Un problme qu'on croyait rsolu. On va alors voir les changes rseau qui se sont produits :

On voit que l'opration [getAllClients] avec la mthode HTTP [OPTIONS] s'est bien passe mais que l'opration [getAllClients]
avec la mthode HTTP [GET] a t annule. La rponse la demande [OPTIONS] a t la suivante :

Les enttes HTTP du CORS sont bien l. Examinons maintenant les changes HTTP lors du GET :

http://tahe.developpez.com

216/325

La requte HTTP semble correcte. On voit notamment l'entte d'authentification.


Outre le message d'erreur prcdent, on trouve dans les logs console, le message suivant :
[dao] getData[/getAllClients] error rponse : {"data":"","status":0,"config":
{"method":"GET","transformRequest":[null],"transformResponse":
[null],"timeout":1000,"url":"http://localhost:8080/getAllClients","headers":
{"Accept":"application/json, text/plain, */*","Authorization":"Basic
YWRtaW46YWRtaW4="}},"statusText":""}

C'est le log que fait systmatiquement la mthode [dao.getData] la rception de la rponse sa demande HTTP. On peut
remarquer deux choses :

[status=0] : cela veut dire que c'est Angular qui a annul la requte HTTP ;

[method=GET] : et c'est la requte GET qui a t annule ;


Mis bout bout avec le premier message, cela veut dire que pour la requte GET galement, Angular attend ici des enttes CORS.
Or actuellement, notre service web ne les envoie que pour la requte HTTP [OPTIONS]. Il est trs trange de rencontrer cette
erreur maintenant et pas pour la liste des mdecins. Je n'ai pas d'explications.
Il faut donc modifier de nouveau le service web.

3.7.7.5

Modification du service web 2

Les mthodes [GET] et [POST] sont traites dans la classe [RdvMedecinsController]. Nous devons la modifier pour que ces
mthodes envoient les enttes CORS. Nous le faisons de la faon suivante :
1. @RestController
2. public class RdvMedecinsController {
3.
4.
@Autowired
5.
private ApplicationModel application;
6.
7.
@Autowired
8.
private RdvMedecinsCorsController rdvMedecinsCorsController;
9.
10. ...
11.
12.
// liste des clients
13.
@RequestMapping(value = "/getAllClients", method = RequestMethod.GET)
14.
public Reponse getAllClients(HttpServletResponse response) {
15.
// enttes CORS
16.
rdvMedecinsCorsController.getAllClients(response);

http://tahe.developpez.com

217/325

17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
}
28. ...

3.7.7.6

// tat de l'application
if (messages != null) {
return new Reponse(-1, messages);
}
// liste des clients
try {
return new Reponse(0, application.getAllClients());
} catch (Exception e) {
return new Reponse(1, Static.getErreursForException(e));
}

ligne 8 : nous voulons rutiliser le code que nous avons plac dans le contrleur [RdvMedecinsCorsController]. Aussi
injectons-nous celui-ci ici ;
ligne 14 : la mthode qui traite la demande [GET /getAllClients]. Nous faisons deux modifications :
ligne 14 : nous injectons l'objet [HttpServletResponse] dans les paramtres de la mthode,
ligne 16 : nous utilisons les mthodes de la classe [RdvMedecinsCorsController] pour mettre dans cet objet les enttes
CORS ;

Tests de l'application 2

Nous lanons la nouvelle version du service web et redemandons la liste des clients. Nous obtenons la rponse suivante :
2

en [1], on a bien une rponse mais elle est vide [2] ;


en [3] : les changes rseau se sont bien passs ;

Dans les logs console, la mthode [dao.getData] a affich la rponse qu'elle a reue :
[dao] getData[/getAllClients] success rponse : {"data":{"status":0,"data":
[{"id":1,"version":1,"titre":"Mr","nom":"MARTIN","prenom":"Jules"},
{"id":2,"version":1,"titre":"Mme","nom":"GERMAN","prenom":"Christine"},
{"id":3,"version":1,"titre":"Mr","nom":"JACQUARD","prenom":"Jules"},
{"id":4,"version":1,"titre":"Melle","nom":"BISTROU","prenom":"Brigitte"}]},"status":200,"config":
{"method":"GET","transformRequest":[null],"transformResponse":
[null],"timeout":1000,"url":"http://localhost:8080/getAllClients","headers":
{"Accept":"application/json, text/plain, */*","Authorization":"Basic
YWRtaW46YWRtaW4="}},"statusText":"OK"}

http://tahe.developpez.com

218/325

Donc la mthode a bien reu la liste des clients. Une fois le code vrifi, on en vient suspecter l'instruction suivante qu'on ne
matrise pas trs bien :
1. // on style la liste droulante
2. $('.selectpicker').selectpicker();

On met la ligne 2 en commentaires et on ressaie. On obtient alors la rponse suivante :


2

On a donc localis le problme. C'est l'application de la mthode [selectpicker] la liste droulante qui pose problme. Lorsqu'on
regarde le code source de la page errone, on a la chose suivante :

http://tahe.developpez.com

219/325

on dcouvre qu'en [1], la liste droulante est bien prsente avec ses lments mais qu'elle n'est pas affiche
[style='display:none'] ;
en [2], on voit le bouton [bootstrap select] affich. Les lments de la liste droulante devraient apparatre dans la liste <ul
role='menu'>. Ils n'y sont pas et on a donc une liste vide. Il semble que lorsque la mthode [selectpicker] a t applique
la liste droulante, son contenu tait vide ce moment l ;

En parcourant la toile la recherche d'une solution, on trouve celle-ci. On remplace le code :


1. // on style la liste droulante
2. $('.selectpicker').selectpicker();

par le suivant :
1.
2.
3.
4. });

// on style la liste droulante


$timeout(function(){
$('.selectpicker').selectpicker();

Le style [bootstrap-select] est appliqu au travers d'une fonction [$timeout]. Nous avons dj rencontr cette fonction qui permet
d'excuter une fonction pass un certain dlai. Ici, l'absence de dlai vaut un dlai nul. Les lignes prcdentes mettent un vnement
dans la liste d'attente des vnements du navigateur. Lorsque le traitement de l'vnement en cours (clic sur le bouton [Liste des
clients]) va tre termin, la vue V va tre affiche. Puis aussitt aprs, le navigateur va consulter sa liste d'vnements. A cause de
son dlai nul, l'vnement [$timeout] va tre en tte de liste et trait. Le style [bootstrap-select] est alors appliqu une liste
droulante remplie. Voyons le rsultat :

http://tahe.developpez.com

220/325

Si on regarde de nouveau le code source de la page affiche, on a la chose suivante :

Le bouton [bootstrap-select] qui prcdemment tait vide contient dsormais la liste des clients.

3.7.7.7

Utilisation d'une directive

Nous avons rencontr dans le contrleur C de la vue V, le code suivant :


// on style la liste droulante

http://tahe.developpez.com

221/325

$('.selectpicker').selectpicker();

On manipule un objet du DOM. Nombre de dveloppeurs Angular sont allergiques la manipulation du DOM dans le code d'un
contrleur. Pour eux, celle-ci doit tre faite dans une directive. Une directive Angular peut tre vue comme une extension du
langage HTML. Il est ainsi possible de crer de nouveaux lments ou attributs HTML. Voyons un premier exemple :
Nous crons le fichier JS [selectEnable] suivant :
1. angular.module("rdvmedecins").directive('selectEnable', ['$timeout', function ($timeout) {
2.
return {
3.
link: function (scope, element, attrs) {
4.
$timeout(function () {
5.
var selectpicker = $('.selectpicker');
6.
selectpicker.selectpicker();
7.
});
8.
}
9.
};
10. }]);

la directive suit la syntaxe du contrleur laquelle nous sommes dsormais habitus :

angular.module("rdvmedecins").directive('selectEnable', ['$timeout', function ($timeout)

La directive appartient au module [rvmedecins]. C'est une fonction qui accepte deux paramtres :

le premier est le nom de la directive [selectEnable] ;

le second est un tableau ['obj1','obj2',..., function(obj1, obj2,...)] o les [obj] sont les objets injecter dans la
fonction. Ici le seul objet inject est l'objet prdfini [$timeout] ;

la fonction [directive] retourne un objet qui peut avoir divers attributs. Ici le seul attribut est l'attribut [link] (ligne 3). Sa
valeur est ici une fonction admettant trois paramtres :
scope : le modle de la vue dans laquelle est utilise la directive ;
element : l'lment de la vue, objet de la directive ;
attrs : les attributs de cet lment ;

Prenons un exemple. La directive [selectEnable] pourrait tre utilise dans le contexte suivant :
<div select-enable="data"></div>

Ci-dessus, l'attribut [select-enable] applique la directive [selectEnable] l'lement HTML <div>. Une directive [doSomething] peut
tre applique n'importe quel lment HTML en lui ajoutant l'attribut [do-something]. On fera attention au changement d'criture
entre le nom de la directive et l'attribut qui lui est associ. On passe d'une criture [camelCase] une criture [camel-case].
La directive [selectEnable] pourrait tre galement utilise de la faon suivante :
<select-enable attr1='val1' attr2='val2' ...>...</select-enable>

Ici la directive [doSomething] est applique sous la forme d'une balise HTML <do-something>.
Revenons l'criture
<div select-enable="data"></div>

et aux trois paramtres de la fonction [link] de la directive, [scope, element, attrs] :

scope : est le modle de la vue dans laquelle se trouve la <div> ;


element : est la <div> elle-mme ;
attrs : est le tableau des attributs de la <div>. Ceux-ci peuvent tre utiliss pour transmettre de l'information la
directive. Ci-dessus, on crira attrs['selectEnable'] pour avoir l'information [data]. On notera bien le changement
d'criture [selectEnable] pour dsigner l'attribut [select-enable] ;

Revenons au code de la directive :


11. angular.module("rdvmedecins").directive('selectEnable', ['$timeout', function ($timeout) {
12. return {

http://tahe.developpez.com

222/325

13.
link: function (scope, element, attrs) {
14.
$timeout(function () {
15.
$('.selectpicker').selectpicker();
16.
});
17.
}
18. };
19. }]);

lignes 14-16 : on retrouve le code que nous avions plac auparavant dans le contrleur. Celui-ci est excut lors de la
rencontre de la directive [select-enable] (sous forme d'lment ou d'attribut) lors de l'affichage de la vue V.

Pour mettre en oeuvre cette directive, nous copions le fichier [app-17.html] dans [app-17B.html] et le modifions de la faon
suivante :
1.
<select data-style="btn-primary" class="selectpicker" select-enable="">
2.
<option ng-repeat="client in clients.data" value="{{client.id}}">
3.
{{client.titre}} {{client.prenom}} {{client.nom}}
4.
</option>
5. </select>

ligne 1 : on applique la directive [selectEnable] l'lment HTML [select]. Comme il n'y a pas d'informations passer la
directive, nous crivons simplement [select-enable=""] ;

Nous modifions galement le contrleur en dupliquant le fichier JS [rdvmedecins-05.js] dans [rdvmedecins-05B.js] et nous
rfrenons le nouveau fichier JS dans le fichier [app-17B.html] et le fichier [selectEnable.js] de directive. Il ne faut pas oublier ce
dernier point. Si le fichier de la directive est absent, l'attribut [select-enable=""] ne sera pas gr mais Angular ne signalera aucune
erreur.
1. <script type="text/javascript" src="rdvmedecins-05B.js"></script>
2. <script type="text/javascript" src="selectEnable.js"></script>

Dans le fichier JS [rdvmedecins-05B.js], nous supprimons du contrleur, les lignes suivantes :


1.
2.
3.
4. });

// on style la liste droulante


$timeout(function(){
$('.selectpicker').selectpicker();

cette opration tant dsormais faite par la directive.

3.7.7.8

Tests de l'application 3

Lorsqu'on teste la nouvelle application [app-17B.html], on obtient le rsultat suivant :

http://tahe.developpez.com

223/325

en [1], on obtient une liste vide.

Les logs console affichent la chose suivante :


1. [dao] init
2. directive selectEnable
3. [dao] getData[/getAllClients] success rponse : {"data":{"status":0,"data":
[{"id":1,"version":1,"titre":"Mr","nom":"MARTIN","prenom":"Jules"},
{"id":2,"version":1,"titre":"Mme","nom":"GERMAN","prenom":"Christine"},
{"id":3,"version":1,"titre":"Mr","nom":"JACQUARD","prenom":"Jules"},
{"id":4,"version":1,"titre":"Melle","nom":"BISTROU","prenom":"Brigitte"}]},"status":200,"config"
:{"method":"GET","transformRequest":[null],"transformResponse":
[null],"timeout":1000,"url":"http://localhost:8080/getAllClients","headers":
{"Accept":"application/json, text/plain, */*","Authorization":"Basic
YWRtaW46YWRtaW4="}},"statusText":"OK"}

ligne 1 : initialisation du service [dao] ;


ligne 2 : l'affichage initial de la vue V, la directive [selectEnable] est excute ;
ligne 3 : cette ligne apparat lorsque l'utilisateur clique sur le bouton [Liste des clients]. On constate alors que la directive
[selectEnable] n'est pas excute une seconde fois. Au final, elle a t excute lorsque la liste des clients tait vide et on a
donc une liste droulante vide ;

Dit autrement, l'opration :


1. $('.selectpicker').selectpicker();

ne s'est pas droule au bon moment. On peut essayer de rsoudre le problme de diverses faons. Au bout de nombreux tests
infructueux, on se rend compte que l'opration ci-dessus, ne doit se drouler qu'une fois et uniquement lorsque la liste
droulante a t remplie. Pour obtenir ce rsultat, on rcrit la balise <select> de la faon suivante :
1.
2.
3.
4.
5.

<select data-style="btn-primary" class="selectpicker" select-enable="" ngif="clients.data">


<option ng-repeat="client in clients.data" value="{{client.id}}">
{{client.titre}} {{client.prenom}} {{client.nom}}
</option>
</select>

Ligne 1, la balise <select> n'est gnre que si [clients.data] existe. Ce n'est pas le cas lors de l'affichage initial de la vue V. La balise
<select> ne sera donc pas gnre et la directive [selectEnable] pas value. Lorsque l'utilisateur va cliquer sur le bouton [Liste des
clients], [clients.data] aura une nouvelle valeur dans le modle M. Parce que le modle M a chang, la balise <select> va tre

http://tahe.developpez.com

224/325

rvalue et ici gnre. La directive [selectEnable] va donc tre value galement. Lorsqu'elle est value, les lignes 2-4 de la balise
<select> n'ont pas encore t values. On a donc une liste de clients vide. Si on crit la directive [selectEnable] de la faon
suivante :
1. angular.module("rdvmedecins").directive('selectEnable', ['$timeout', 'utils', function
($timeout, utils) {
2.
return {
3.
link: function (scope, element, attrs) {
4.
utils.debug("directive selectEnable");
5.
$('.selectpicker').selectpicker();
6.
}
7.
}

8. }]);

la ligne 5 va tre excute avec une liste vide et on aura alors une liste droulante vide l'affichage. Il faut alors crire :
1. angular.module("rdvmedecins").directive('selectEnable', ['$timeout', 'utils', function
($timeout, utils) {
2.
return {
3.
link: function (scope, element, attrs) {
4.
utils.debug("directive selectEnable");
5.
$timeout(function () {
6.
$('.selectpicker').selectpicker();
7.
})
8.
}
9.
}

10. }]);

pour avoir le rsultat attendu. A cause du [$timeout] de la ligne 5, la ligne 6 ne sera excute qu'aprs valuation complte de la vue
V, donc un moment ou la balise <select> aura tous ses lments.

3.7.8

Exemple 8 : l'agenda d'un mdecin

Nous prsentons maintenant une application qui affiche l'agenda d'un mdecin.

3.7.8.1

La vue V de l'application

Nous prsenterons le formulaire suivant :

2
3

en [1], on demande l'agenda de Mme PELISSIER [2], le 25 juin 2014 [3] ;

On obtient le rsultat [4] suivant :

http://tahe.developpez.com

225/325

Nous allons tudier les deux vues sparment.

3.7.8.2

Le formulaire

Nous dupliquons le fichier [app-17.html] dans [app-18.html] puis nous modifions le code de la faon suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.

<div class="container">
<h1>Rdvmedecins - v1</h1>
<!-- le message d'attente -->
<div class="alert alert-warning" ng-show="waiting.visible">
...
</div>
<!-- la demande -->
<div class="alert alert-info" ng-hide="waiting.visible">
<div class="row" style="margin-bottom: 20px">
<div class="col-md-3">
<h2 translate="{{medecins.title}}"></h2>
<select data-style="btn-primary" class="selectpicker">
<option ng-repeat="medecin in medecins.data" value="{{medecin.id}}">
{{medecin.titre}} {{medecin.prenom}} {{medecin.nom}}
</option>
</select>
</div>
<div class="col-md-3">
<h2 translate="{{calendar.title}}"></h2>
<div style="display:inline-block; min-height:290px;">
<datepicker ng-model="calendar.jour" min-date="calendar.minDate" show-weeks="true"
class="well well-sm"></datepicker>
</div>
</div>
</div>
<button class="btn btn-primary" ng-click="execute()">{{agenda.title|translate}}</button>
</div>
<!-- la liste d'erreurs -->
<div class="alert alert-danger" ng-show="errors.show">
...
</div>
<!-- l'agenda -->
<div id="agenda" ng-show="agenda.show">
...
</div>
</div>
...
<script type="text/javascript" src="rdvmedecins-06.js"></script>

http://tahe.developpez.com

226/325

3.7.8.3

lignes 5-7 : le message d'attente ne change pas ;


lignes 12-19 : la liste des mdecins de type [bootstrap select] ;
lignes 20-26 : le calendrier de [ui-bootstrap] que nous avons dj prsent. On notera que le jour slectionn est plac dans
le modle [calendar.jour] (attribut ng-model) ;
ligne 28 : le bouton qui demande l'agenda ;
lignes 32-34 : la liste des erreurs ne change pas ;
lignes 37-39 : l'agenda que nous prsenterons ultrieurement ;
ligne 42 : le code JS est transfr dans le fichier [rdvmedecins-06.js] par recopie du fichier [rdvmedecins-05.js] ;

Le contrleur C

Le code JS de l'application devient le suivant :

Seuls le service [utils] et le contrleur [rdvMedecinsCtrl] vont tre impacts par les modifications.
Le contrleur [rdvMedecinsCtrl] devient le suivant :
1. // contrleur
2. angular.module("rdvmedecins")
3.
.controller('rdvMedecinsCtrl', ['$scope', 'utils', 'config', 'dao', '$translate', '$timeout',
'$filter', '$locale',
4.
function ($scope, utils, config, dao, $translate, $timeout, $filter, $locale) {
5.
// ------------------- initialisation modle
6.
// modle
7.
$scope.waiting = {text: config.msgWaiting, visible: false, cancel: cancel, time: 3000};
8.
$scope.server = {url: 'http://localhost:8080', login: 'admin', password: 'admin'};
9.
$scope.errors = {show: false, model: {}};
10.
$scope.medecins = {
11.
data: [
12.
{id: 1, version: 1, titre: "Mme", nom: "PELISSIER", prenom: "Marie"},
13.
{id: 2, version: 1, titre: "Mr", nom: "BROMARD", prenom: "Jacques"},
14.
{id: 3, version: 1, titre: "Mr", nom: "JANDOT", prenom: "Philippe"},
15.
{id: 4, version: 1, titre: "Melle", nom: "JACQUEMOT", prenom: "Justine"}
16.
],
17.
title: config.listMedecins};
18.
$scope.agenda = {title: config.getAgendaTitle, data: undefined, show: false};
19.
$scope.calendar = {title: config.getCalendarTitle, minDate: new Date(), jour: new Date()};
20.
// on style la liste droulante
21.
$timeout(function () {
22.
$('.selectpicker').selectpicker();
23.
});
24.
// locale franaise pour le calendrier
25.
angular.copy(config.locales['fr'], $locale);
26. ...

http://tahe.developpez.com

227/325

27.
}
28. ])
29. ;

ligne 7 : on fixe un temps d'attente de 3 secondes avant de faire l'appel HTTP ;


ligne 8 : on fixe en dur les lments ncessaires la connexion HTTP ;
lignes 10-17 : la liste des mdecins est dfinie en dur ;
ligne 18 : le modle [agenda] configure l'affichage de l'agenda dans la vue ;
ligne 19 : le modle [calendar] configure l'affichage du calendrier dans la vue. On fixe une date minimale [minDate]
aujourd'hui et la date du jour galement aujourd'hui ;
lignes 21-23 : la liste droulante est style avec la mthode vue prcdemment ;
ligne 25 : on met la locale de l'application 'fr'. Par dfaut, elle est 'en' ;

La mthode excute lors de la demande de l'agenda est la suivante :


1. // excution action
2.
$scope.execute = function () {
3.
// les infos du formulaire
4.
var idMedecin = $('.selectpicker').selectpicker('val');
5.
6.
// vrification
7.
utils.debug("[homeCtrl] idMedecin", idMedecin);
8.
utils.debug("[homeCtrl] jour", $scope.calendar.jour);
9.
10.
// on met le jour au format yyyy-MM-dd
11.
var formattedJour = $filter('date')($scope.calendar.jour, 'yyyy-MM-dd');
12.
// mise jour de la vue
13.
$scope.waiting.visible = true;
14.
$scope.errors.show = false;
15.
$scope.agenda.show = false;
16. ...
17.
};

ligne 4 : on rcupre l'attribut [value] du mdecin slectionn. On utilise ici de nouveau la mthode [selectpicker] qui
provient du fichier [bootstrap-select.min.js]. Il faut se souvenir de la forme des options de la liste droulante :
<option ng-repeat="medecin in medecins.data" value="{{medecin.id}}">
{{medecin.titre}} {{medecin.prenom}} {{medecin.nom}}

La valeur (attribut value) de l'option est donc l'identifiant [id] du mdecin.

ligne 11 : on met le jour choisi par l'utilisateur au format [aaaa-mm-jj] qui est le format de date attendu par le serveur web ;
lignes 13-15 : lorsque la mthode [execute] sera termine, le bandeau d'attente sera affich et tout le reste cach ;

Le code se poursuit de la faon suivante :


1. // attente simule
2.
var task = utils.waitForSomeTime($scope.waiting.time);
3.
// on demande l'agenda du mdecin
4.
var promise = task.promise.then(function () {
5.
// le chemin de l'URL de service
6.
var path = config.urlSvrAgenda + "/" + idMedecin + "/" + formattedJour;
7.
// on demande l'agenda
8.
task = dao.getData($scope.server.url, $scope.server.login, $scope.server.password,
path);
9.
// on retourne la promesse d'achvement de la tche
10.
return task.promise;
11.
});
12.
// on analyse le rsultat de l'appel au service [dao]
13.
promise.then(function (result) {
14.
// fin d'attente
15.
$scope.waiting.visible = false;
16.
// erreur ?
17.
if (result.err == 0) {
18.
// on prpare le modle de l'agenda

http://tahe.developpez.com

228/325

19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36. }

$scope.agenda.data = result.data;
$scope.agenda.show = true;
// mise en forme de l'affichage des horaires
angular.forEach($scope.agenda.data.creneauxMedecin, function (creneauMedecin) {
creneauMedecin.creneau.text = utils.getTextForCreneau(creneauMedecin.creneau);
});
// on cre un evt pour styler la table aprs l'affichage de la vue
$timeout(function () {
$("#creneaux").footable();
});
} else {
// il y a eu des erreurs pour obtenir l'agenda
$scope.errors = {
title: config.getAgendaErrors,
messages: utils.getErrors(result),
show: true
};

ligne 2 : la tche asynchrone d'attente de 3 secondes ;


lignes 5-10 : le code qui sera excut lorsque cette attente sera termine ;
ligne 6 : on construit l'URL interroge [/getAgendaMedecinJour/1/2014-06-25] ;
ligne 8 : l'URL est interroge. Une tche asynchrone dmarre ;
ligne 10 : on rend la promesse de cette tche asynchrone ;
lignes 14-38 : le code qui sera excut lorsque l'appel HTTP aura renvoy sa rponse ;
ligne 13 : [result] est la rponse envoye par la mthode [dao.getData]. Il faut ici se souvenir de la forme de la rponse du
serveur web :

Le paramtre [result.data] de la ligne 19 est l'attribut [data] [1] ci-dessus. Cet attribut contient son tour l'attribut [creneauxMedecin]
[2] ci-dessus. Celui-ci est un tableau de crneaux avec pour chacun d'eux les deux informations :

[rv] : la forme JSON d'un rendez-vous ou [null] s'il n'y a pas de rendez-vous pris sur ce crneau ;

[hDeb, mDeb, hFin, mFin] : les informations horaires du crneau ;


Revenons au code du contrleur :

ligne 15 : l'attente est termine ;

http://tahe.developpez.com

229/325

3.7.8.4

ligne 19 : on renseigne le modle [$scope.agenda] qui contrle l'affichage de l'agenda ;


ligne 20 : l'agenda est rendu visible ;
lignes 22-24 : on passe en revue chacun des lments C du tableau [creneauxMedecin] dont nous venons de parler ;
ligne 23 : chaque lment C a un attribut [creneau] qui est le crneau horaire. Celui-ci est enrichi d'un attribut [text] qui
sera la reprsentation texte du crneau horaire sous la forme [10h20:10h40] ;
lignes 26-28 : on rend 'responsive' la table HTML utilise pour afficher les crneaux de l'agenda. Nous avons vu cette
notion au paragraphe 3.6.7, page 158 ;

ligne 27 : pour rendre la table 'responsive', il faut lui appliquer la mthode [footable]. On retrouve ici la mme difficult
que celle rencontre pour le composant [bootstrap-select]. Si on crit simplement la ligne 17, on constate que la table n'est
pas 'responsive'. On rsoud ce problme de la mme faon avec la fonction [$timeout] (ligne 26) ;
lignes 31-34 : le cas o l'appel HTTP a chou. On affiche alors les messages d'erreur ;

Affichage de l'agenda

Nous revenons maintenant au code de l'agenda dans le fichier [app-18.html]. C'est le suivant :
1. <!-- l'agenda -->
2.
<div id="agenda" ng-show="agenda.show">
3.
<!-- cas du mdecin sans crneaux de consultation -->
4.
<h4 class="alert alert-danger" ng-if="agenda.data.creneauxMedecin.length==0"
5.
translate="agenda_medecinsanscreneaux"></h4>
6.
<!-- agenda du mdecin -->
7.
<div class="row tab-content alert alert-warning" ng-if="agenda.data.creneauxMedecin.length!=0">
8.
<div class="tab-pane active col-md-6">
9.
<table creneaux-table id="creneaux" class="table">
10.
<thead>
11.
<tr>
12.
<th data-toggle="true">
13.
<span translate="agenda_creneauhoraire"></span>
14.
</th>
15.
<th>
16.
<span translate="agenda_client">Client</span>
17.
</th>
18.
<th data-hide="phone">
19.
<span translate="agenda_action">Action</span>
20.
</th>
21.
</tr>
22.
</thead>
23.
<tbody>
24.
<tr ng-repeat="creneauMedecin in agenda.data.creneauxMedecin">
25.
<td>
26.
<span
27.
ng-class="! creneauMedecin.rv ? 'status-metro status-active' : 'status-metro statussuspended'">
28.
{{creneauMedecin.creneau.text}}
29.
</span>
30.
</td>
31.
<td>
32.
<span>{{creneauMedecin.rv.client.titre}} {{creneauMedecin.rv.client.prenom}}
{{creneauMedecin.rv.client.nom}}</span>
33.
</td>
34.
<td>

http://tahe.developpez.com

230/325

35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.

<a href="" ng-if="!creneauMedecin.rv" translate="agenda_reserver" class="status-metro


status-active">
</a>
<a href="" ng-if="creneauMedecin.rv" translate="agenda_supprimer" class="status-metro
status-suspended">
</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

lignes 4-5 : on se rappelle que [agenda.data] est l'agenda, que [agenda.data.creneauxMedecin] est un tableau d'objets de
type [creneauMedecin]. Chaque lment de ce dernier type a un attribut [creneauMedecin.creneau] qui est un crneau
horaire. Chaque crneau horaire a deux lments qui nous intressent :
[creneauMedecin.creneau.rv] qui est l'ventuel RV (rv!=null) pris sur le crneau ;
[creneauMedecin.creneau.text] qui est le texte [dbut:fin] du crneau horaire ;
ligne 4 : affiche un message spcial si le mdecin n'a pas de crneaux horaires. C'est improbable mais il se trouve que notre
base de donnes est incomplte et ce cas existe. La gnration HTML ou non du message est contrle par la directive
[ng-if] ;

La directive [ng-if] est diffrente des directives [ng-show, ng-hide]. Ces dernires se contentent de cacher une zone
prsente dans le document. Si [ng-if='false'], alors la zone est enleve du document. On l'a utilise ici pour illustration ;

ligne 9 : l'attribut [id='creneaux'] est important. C'est lui qui est utilis dans l'instruction :
$("#creneaux").footable();

lignes 10-22 : affichent les enttes du tableau [1] ;


lignes 23-45 : affichent le contenu du tableau [2] ;

http://tahe.developpez.com

231/325

1
4
3
2
ligne 24 : on parcourt le tableau [agenda.data.creneauxMedecin] ;
lignes 26-29 : on crit le texte [3]. On utilise la directive [ng-class] qui va gnrer l'attribut [class] de l'lment. Ici, si on a
[creneauMedecin.rv==null], cela veut dire que le crneau est libre et on met un fond vert au texte. Sinon, on met un fond
rouge ;
ligne 32 : on crit le nom du client pour qui a t pris le RV [4]. Si [rv==null], ces informations n'existent pas mais Angular
gre correctement ce cas et ne dclare pas d'erreur ;
lignes 34-39 : affichent l'un des deux boutons [Rserver] ou [Supprimer]. C'est l'existence ou non d'un rendez-vous qui fait
que l'on choisit l'un ou l'autre des boutons ;

3.7.8.5

Modification du serveur web

Comme pour les exemples prcdents, le serveur web doit tre modifi pour que l'URL [/getAgendaMedecinJour] envoie les enttes
CORS :

Dans la classe [RdvMedecinsCorsController] on ajoute ue nouvelle mthode :


1.
2.

// agenda du mdecin
@RequestMapping(value = "/getAgendaMedecinJour/{idMedecin}/{jour}", method =
RequestMethod.OPTIONS)
3.
public void getAgendaMedecinJour(HttpServletResponse response) {
4.
sendOptions(response);
5. }

Cette mthode va envoyer les enttes CORS pour la requte HTTP [OPTIONS]. On doit faire la mme chose pour la requte
HTTP [GET] dans la classe [RdvMedecinsController] :
1. @RequestMapping(value = "/getAgendaMedecinJour/{idMedecin}/{jour}", method = RequestMethod.GET)
2.
public Reponse getAgendaMedecinJour(@PathVariable("idMedecin") long idMedecin,
@PathVariable("jour") String jour, HttpServletResponse response) {
3.
// enttes CORS
4.
rdvMedecinsCorsController.getAgendaMedecinJour(response);
5. ...
6. }

3.7.8.6

Utilisation de directives

Comme il a t fait prcdemment, nous allons dporter la manipulation du DOM dans des directives. Nous avons deux
manipulations de DOM :

lors de l'affichage initial de la vue :

http://tahe.developpez.com

232/325

1.
2.
3.
4. });

// on style la liste droulante


$timeout(function () {
$('.selectpicker').selectpicker();

lors de l'affichage de l'agenda :

1.
2.
3.
4. });

// on cre un evt pour styler la table aprs l'affichage de la vue


$timeout(function () {
$("#creneaux").footable();

Pour le 1er cas, nous allons utiliser la directive [selectEnable] dj prsente. Pour le second cas, nous crons la directive [footable]
dans le fichier JS [footable.js] suivant :
1. angular.module("rdvmedecins").directive('footable', ['$timeout', 'utils', function ($timeout,
utils) {
2.
return {
3.
link: function (scope, element, attrs) {
4.
utils.debug("directive footable");
5.
$timeout(function () {
6.
$("#creneaux").footable();
7.
})
8.
}
9.
}

10. }]);

On utilise donc la mme technique que pour la directive [selectEnable].


Le code HTML [app-18.html] est dupliqu dans [app-18B.html]. Puis on le fait voluer de la faon suivante :
1.
<select data-style="btn-primary" class="selectpicker" select-enable="">
2.
<option ng-repeat="medecin in medecins.data" value="{{medecin.id}}">
3.
{{medecin.titre}} {{medecin.prenom}} {{medecin.nom}}
4.
</option>
5. </select>

1.

ligne 1 : on applique la directive [selectEnable] (via l'attribut [select-enable]) la balise <select> des mdecins ;
=0">

2.
3.
4.
5. <tr>

<div class="row tab-content alert alert-warning" ng-if="agenda.data.creneauxMedecin.length!


<div class="tab-pane active col-md-6">
<table id="creneaux" class="table" footable="">
<thead>

ligne 3 : on applique la directive [footable] (via l'attribut [footable]) la table HTML de l'agenda ;

1.
2.
3.
4.

<script type="text/javascript" src="rdvmedecins-06B.js"></script>


<!-- directives -->
<script type="text/javascript" src="selectEnable.js"></script>
<script type="text/javascript" src="footable.js"></script>

lignes 3-4 : on rfrence les fichiers JS des deux directives ;


ligne 1 : le code JS de [app-18B.html] est le code JS de [app-18.html] dupliqu dans le fichier [rdvmedecins-06B.js] ;

Le fichier [rdvmedecins-06B.js] est identique au fichier [rdvmedecins-06.js] deux dtails prs. Les lignes manipulant le DOM
disparaissent :
1.
2.
3.
4. });

// on style la liste droulante


$timeout(function () {
$('.selectpicker').selectpicker();

http://tahe.developpez.com

233/325

1.
2.
3.
4. });

// on cre un evt pour styler la table aprs l'affichage de la vue


$timeout(function () {
$("#creneaux").footable();

Cei fait, l'excution de l'application [app-18B.html] donne les mmes rsultats que celle de [app-18.html].

3.7.9

Exemple 9 : crer et annuler des rservations

Nous prsentons maintenant une application qui permet de crer et d'annuler des rservations.

3.7.9.1

La vue V de l'application

Nous prsenterons le formulaire suivant :

en [1], on pourra rserver. La rservation qui sera faite le sera pour un client alatoire ;
en [2], on pourra supprimer les rservations que nous aurons faites ;

Nous dupliquons le fichier [app-18.html] dans [app-19.html] puis nous modifions le code de la faon suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.

<div class="container">
<h1>Rdvmedecins - v1</h1>
<!-- le message d'attente -->
<div class="alert alert-warning" ng-show="waiting.visible">
...
</div>
<!-- la liste d'erreurs -->
<div class="alert alert-danger" ng-show="errors.show">
...
</div>
<!-- l'agenda -->
<div id="agenda" ng-show="agenda.show">
..
<!-- agenda du mdecin -->
<div class="row tab-content alert alert-warning" ng-if="agenda.data.creneauxMedecin.length!=0">
<div class="tab-pane active col-md-6">
<table id="creneaux" class="table" footable="">
...

http://tahe.developpez.com

234/325

22.
<tbody>
23.
<tr ng-repeat="creneauMedecin in agenda.data.creneauxMedecin">
24. ...
25.
<td>
26.
<a href="" ng-if="!creneauMedecin.rv" translate="agenda_reserver" class="status-metro
status-active" ng-click="reserver(creneauMedecin.creneau.id)">
27.
</a>
28.
<a href="" ng-if="creneauMedecin.rv" translate="agenda_supprimer" class="status-metro
status-suspended" ng-click="supprimer(creneauMedecin.rv.id)">
29.
</a>
30.
</td>
31.
</tr>
32.
</tbody>
33.
</table>
34.
</div>
35.
</div>
36.
</div>
37. </div>
38. ....
39. <script type="text/javascript" src="rdvmedecins-07.js"></script>
40. <script type="text/javascript" src="footable.js"></script>

3.7.9.2

lignes 5-7 : le message d'attente est celui de la version prcdente ;


lignes 10-12 : le message d'erreurs est celui de la version prcdente ;
lignes 15-36 : l'agenda est celui de la version prcdente deux dtails prs :
ligne 26 : le clic sur le bouton [rserver] (attribut ng-click) est gr par la mthode [reserver] du modle M de la vue V.
On lui passe le n du crneau horaire de rservation ;
ligne 26 : le clic sur le bouton [supprimer] est gr par la mthode [reserver] du modle M de la vue V. On lui passe le
n du rendez-vous supprimer ;
ligne 39 : le code JS qui gre l'application est dans le fichier [rdvmedecins-07.js] ;
ligne 40 : le code JS de la directive [footable] applique ligne 20 ;

Le contrleur C

Le code JS de [rdvmedecins-07.js] est d'abord obtenu par recopie du fichier [rdvmedecins-06.js]. Puis il est modifi. On a toujours les
grands blocs de code habituels. Les modifications se font essentiellement dans le contrleur :

Nous allons dcrire le contrleur C de la vue V en plusieurs tapes.

3.7.9.3

Initialisation du contrleur C

Le code d'initialisation du contrleur est le suivant :


1. angular.module("rdvmedecins")
2.
.controller('rdvMedecinsCtrl', ['$scope', 'utils', 'config', 'dao', '$translate', '$timeout',
'$filter', '$locale',
3.
function ($scope, utils, config, dao, $translate, $timeout, $filter, $locale) {
4.
// ------------------- initialisation modle

http://tahe.developpez.com

235/325

5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.

// modle
$scope.waiting = {text: config.msgWaiting, visible: false, cancel: cancel, time: 3000};
$scope.server = {url: 'http://localhost:8080', login: 'admin', password: 'admin'};
$scope.errors = {show: false, model: {}};
$scope.medecins = {
data: [
{id: 1, version: 1, titre: "Mme", nom: "PELISSIER", prenom: "Marie"},
{id: 2, version: 1, titre: "Mr", nom: "BROMARD", prenom: "Jacques"},
{id: 3, version: 1, titre: "Mr", nom: "JANDOT", prenom: "Philippe"},
{id: 4, version: 1, titre: "Melle", nom: "JACQUEMOT", prenom: "Justine"}
],
title: config.listMedecins
};
var mdecin = $scope.medecins.data[0];
var clients = [
{id: 1, version: 1, titre: "Mr", nom: "MARTIN", prenom: "Jules"},
{id: 2, version: 1, titre: "Mme", nom: "GERMAN", prenom: "Christine"},
{id: 3, version: 1, titre: "Mr", nom: "JACQUARD", prenom: "Maurice"},
{id: 4, version: 1, titre: "Melle", nom: "BISTROU", prenom: "Brigitte"}
];
// locale franaise pour la date
angular.copy(config.locales['fr'], $locale);
var today = new Date();
var formattedDay = $filter('date')(today, 'yyyy-MM-dd');
var fullDay = $filter('date')(today, 'fullDate');
$scope.agenda = {title: config.agendaTitle, data: undefined, show: false, model: {titre:
mdecin.titre, prenom: mdecin.prenom, nom: mdecin.nom, jour: fullDay}};
// ---------------------------------------------------------------- agenda initial
// la tche asynchrone globale
var task;
// on demande l'agenda
getAgenda();

....

// ------------------------------------------------------------------ rservation
$scope.reserver = function (creneauId) {
};
// ------------------------------------------------------------ suppression RV
$scope.supprimer = function (idRv) {

...

...

};
// obtention de l'agenda
function getAgenda() {
}
// annulation attente
function cancel() {

...

} ]);

ligne 6 : configuration du message d'attente. Par dfaut, on attendra 3 secondes avant de faire un appel HTTP ;
ligne 7 : les informations ncessaires aux appels HTTP ;
ligne 8 : configuration du message d'erreurs ;
lignes 9-17 : les mdecins en dur ;
ligne 18 : un mdecin particulier. C'est pour ses crneaux qu'on fera des rservations ;
lignes 19-24 : les clients en dur ;
ligne 26 : on veut manipuler des dates franaises ;
ligne 27 : les rendez-vous seront pris la date d'aujourd'hui ;
ligne 28 : le service web de rservation attend des dates au format 'aaaa-mm-jj' ;
ligne 29 : la date d'aujourd'hui sous la forme [jeudi 26 juin 2014] ;

http://tahe.developpez.com

236/325

ligne 30 : configuration de l'agenda. L'attribut [model] transporte les paramtres du message internationalis qui va tre
affich :
agenda_title: "Agenda de {{titre}} {{prenom}} {{nom}} le {{jour}}"

ligne 35 : la variable globale [task] reprsente un moment donn la tche asynchrone en cours d'excution ;
ligne 37 : on demande l'agenda initial ;

C'est tout ce qui est fait lors du chargement initial de la page. Si tout se passe bien, la vue affiche l'agenda du jour de Mme
PELISSIER.

3.7.9.4

Obtention de l'agenda

L'agenda est obtenu avec la mthode [getAgenda] suivante :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.

// obtention de l'agenda
function getAgenda() {
// le chemin de l'URL de service
var path = config.urlSvrAgenda + "/" + mdecin.id + "/" + formattedDay;
// on demande l'agenda
task = dao.getData($scope.server.url, $scope.server.login, $scope.server.password,
path);

// msg d'attente
$scope.waiting.visible = true;
// on analyse le rsultat de l'appel au service [dao]
task.promise.then(function (result) {
// fin d'attente
$scope.waiting.visible = false;
// erreur ?
if (result.err == 0) {
// on prpare le modle de l'agenda
$scope.agenda.data = result.data;
$scope.agenda.show = true;
// mise en forme de l'affichage des horaires
angular.forEach($scope.agenda.data.creneauxMedecin, function (creneauMedecin) {
creneauMedecin.creneau.text = utils.getTextForCreneau(creneauMedecin.creneau);
});
} else {
// il y a eu des erreurs pour obtenir l'agenda
$scope.errors = {title: config.getAgendaErrors, messages: utils.getErrors(result),
show: true};
25.
}
26.
});
27. }

http://tahe.developpez.com

237/325

Ce code est celui tudi dans l'application prcdente. Il y a deux changements :

il n'y a pas d'attente simule avant l'appel HTTP ;

ligne 4 : on utilise le mdecin cr lors de l'initialisation du contrleur ainsi que le jour format qui a t construit ;
Ce code a t isol dans une fonction car il est galement utilis par les fonctions [reserver] et [supprimer].

3.7.9.5

Rservation d'un crneau horaire

On rappelle que les clients sont choisis de faon alatoire.


Le code rservation est le suivant :
1. $scope.reserver = function (creneauId) {
2.
utils.debug("rservation du crneau", creneauId);
3.
// on cre un RV avec un client alatoire dans le crneau identifi par [id]
4.
var idClient = clients[Math.floor(Math.random() * clients.length)].id;
5.
utils.debug("rservation du crneau pour le client", idClient);
6.
// attente simule
7.
$scope.waiting.visible = true;
8.
var task = utils.waitForSomeTime($scope.waiting.time);
9.
// on ajoute le crneau
10.
var promise = task.promise.then(function () {
11.
// le chemin de l'URL de service
12.
var path = config.urlSvrResaAdd;
13.
// les donnes transmettre au service
14.
var post = {jour: formattedDay, idCreneau: creneauId, idClient: idClient};
15.
// on lance la tche asynchrone
16.
task = dao.getData($scope.server.url, $scope.server.login, $scope.server.password,
path, post);
17.
// on retourne la promesse d'achvement de la tche
18.
return task.promise;
19.
});
20.
21.
// analyse du rsultat de la tche
22.
promise = promise.then(function (result) {
23.
if (result.err != 0) {
24.
// il y a eu des erreurs pour valider le rv
25.
$scope.errors = {title: config.postResaErrors, messages: utils.getErrors(result,
$filter), show: true};
26.
} else {
27.
// on demande le nouvel agenda
28.
getAgenda();
29.
}
30.
});
31.
32.
};

ligne 1 : on rappelle que le paramtre de la fonction [reserver] est le n du crneau (attribut id) ;
ligne 4 : un client est choisi de faon alatoire dans la liste des clients dfinie en dur dans le code d'initialisation. On retient
de lui son identifiant [id] ;
lignes 7-8 : l'attente de 3 secondes ;
lignes 11-18 : ces lignes ne sont excutes qu' la fin des 3 secondes ;

http://tahe.developpez.com

238/325

ligne 12 : l'URL du service de rservation [/ajouterRv]. Cette URL est particulire vis vis de celles qu'on a rencontres
jusqu' maintenant. Elle est dfinie comme suit dans le service web :

1.

@RequestMapping(value = "/ajouterRv", method = RequestMethod.POST, consumes =


"application/json; charset=UTF-8")
2. public Reponse ajouterRv(@RequestBody PostAjouterRv post, HttpServletResponse response) {

ligne 1 : l'URL n'a pas de paramtres et elle est demande avec un POST ;
ligne 2 : les paramtres posts le sont sous la forme d'un objet JSON. Celui-ci sera dsrialis dans le paramtre
[post] (@RequestBody) ;

Nous avons vu un exemple de ce POST(page 78) :


0
1

en [0], l'URL du service web ;


en [1], la mthode POST est utilise ;
en [2], le texte JSON des informations transmises au service web sous la forme {jour, idClient, idCreneau} ;
en [3], le client prcise au service web qu'il lui envoie des informations JSON ;

Revenons au code JS de la fonction [reserver] :

3.7.9.6

ligne 14 : on cre la valeur poster sous la forme d'un objet JS. Angular le srialisera en JSON lorsqu'il sera post ;
ligne 16 : l'appel HTTP est fait. La valeur poster est le dernier paramtre de la fonction [dao.getData]. Lorsque ce
paramtre est prsent, fonction [dao.getData] fait un POST au lieu d'un GET (revoir le code page 198, paragraphe
3.7.6.4) ;
ligne 18 : on retourne la promesse de l'appel HTTP ;
lignes 23-29 : ne sont excutes que lorsque l'appel HTTP a rendu sa rponse ;
ligne 23 : le paramtre [result] est de la forme [err,data] ou [err,messages] o [err] est un code d'erreur ;
lignes 23-26 : s'il y a eu des erreurs, on rend visible le message d'erreur ;
ligne 28 : si la rservation s'est bien passe, on raffiche le nouvel agenda ;

Modification serveur

http://tahe.developpez.com

239/325

Dans la classe [RdvMedecinsCorsController], nous ajoutons la mthode suivante :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13. }

// envoi des options au client


private void sendOptions(HttpServletResponse response) {
if (application.isCORSneeded()) {
// on fixe le header CORS
response.addHeader("Access-Control-Allow-Origin", "*");
// on autorise le header [authorization]
response.addHeader("Access-Control-Allow-Headers", "authorization");
}
@RequestMapping(value = "/ajouterRv", method = RequestMethod.OPTIONS)
public void ajouterRv(HttpServletResponse response) {
sendOptions(response);

L'ajout est fait lignes 10-13. Les enttes des lignes 2-8 seront envoys pour l'URL [/ajouterRv] (ligne 10) et la mthode HTTP
[OPTIONS] (ligne 10).
La classe [RdvMedecinsController] est elle modifie de la faon suivante :
1.
2.
3.
4.
5.

@RequestMapping(value = "/ajouterRv", method = RequestMethod.POST, consumes =


"application/json; charset=UTF-8")
public Reponse ajouterRv(@RequestBody PostAjouterRv post, HttpServletResponse response) {
// enttes CORS
rdvMedecinsCorsController.ajouterRv(response);
...

Pour la mthode [POST] (ligne 1) et l'URL [/ajouterRv] (ligne 1), la mthode que nous venons d'ajouter dans
[RdvMedecinsCorsController] est appele (ligne 4), renvoyant donc les mmes enttes HTTP que pour la mthode HTTP
[OPTIONS].

3.7.9.7

Tests

Faisons un premier test o nous rservons un crneau quelconque :

http://tahe.developpez.com

240/325

Comme toujours dans ces cas l, il faut regarder les logs de la console :
1. [dao] getData[/ajouterRv] error rponse : {"data":"","status":0,"config":
{"method":"POST","transformRequest":[null],"transformResponse":
[null],"timeout":1000,"url":"http://localhost:8080/ajouterRv","data":{"jour":"2014-0630","idCreneau":1,"idClient":4},"headers":{"Accept":"application/json, text/plain,
*/*","Authorization":"Basic YWRtaW46YWRtaW4=","Content-Type":"application/json;charset=utf8"}},"statusText":""}

La mthode [dao.getData] a chou avec [status=0], ce qui signifie que c'est Angular qui a annul la requte. On a la cause de
l'erreur dans les logs :
XMLHttpRequest cannot load http://localhost:8080/ajouterRv. Request header field Content-Type is not
allowed by Access-Control-Allow-Headers.

Si on regarde les changes rseau, on a la chose suivante :

1
2

en [1] et [2] : il n'y a eu qu'une requte HTTP, la requte [OPTIONS] ;


en [3], le client Angular demande deux autorisations :
celle d'envoyer les enttes HTTP [accept, authorization, content-type] ;
celle d'envoyer une commande POST ;
en [4] : le serveur autorise l'entte [authorization]. Rappelons que ct serveur, c'est nous-mmes qui envoyons cette
autorisation ;

La nouveaut est donc que sur une opration POST, le client Angular demande davantage d'autorisations au serveur. Il faut donc
modifier celui-ci pour qu'il les lui donne :

http://tahe.developpez.com

241/325

Dans la classe [RdvMedecinsCorsController], nous modifions la mthode prive qui gnre les enttes HTTP envoys pour les
commandes OPTIONS, GET et POST :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.

// envoi des options au client


private void sendOptions(HttpServletResponse response) {
if (application.isCORSneeded()) {
// on fixe le header CORS
response.addHeader("Access-Control-Allow-Origin", "*");
// on autorise certains headers
response.addHeader("Access-Control-Allow-Headers", "accept, authorization, contenttype");
// on autorise le POST
response.addHeader("Access-Control-Allow-Methods", "POST");
}
}

ligne 7 : on a ajout une autorisation pour les enttes HTTP [accept, content-type] ;
ligne 9 : on a ajout une autorisation pour la mthode POST ;

On refait le test aprs avoir relanc le serveur :

Cette fois-ci, on a russi rserver.

3.7.9.8

Suppression d'un rendez-vous

Le code de la fonction [supprimer] est le suivant :


1. $scope.supprimer = function (idRv) {
2.
utils.debug("suppression rv n", idRv);
3.
// attente simule
4.
$scope.waiting.visible = true;
5.
task = utils.waitForSomeTime($scope.waiting.time);
6.
// on ajoute le crneau
7.
var promise = task.promise.then(function () {
8.
// le chemin de l'URL de service
9.
var path = config.urlSvrResaRemove;
10.
// les donnes transmettre au service
11.
var post = {idRv: idRv};

http://tahe.developpez.com

242/325

12.
13.

// on lance la tche asynchrone


task = dao.getData($scope.server.url, $scope.server.login, $scope.server.password,
path, post);
14.
// on retourne la promesse d'achvement de la tche
15.
return task.promise;
16.
});
17.
18.
// analyse du rsultat de la tche
19.
promise = promise.then(function (result) {
20.
if (result.err != 0) {
21.
// il y a eu des erreurs pour supprimer le rv
22.
$scope.errors = {title: config.postRemoveErrors, messages: utils.getErrors(result,
$filter), show: true};
23.
// on met jour l'UI
24.
$scope.waiting.visible = false;
25.
} else {
26.
// on demande le nouvel agenda
27.
getAgenda();
28.
}
29.
});
30.
};

ligne 1 : il faut se rappeler que le paramtre de la fonction est le n du rendez-vous supprimer. On a l un code trs
similaire celui de la rservation. Nous ne commentons que les diffrences ;
ligne 9 : l'URL du service est ici [/supprimerRV] et l galement elle est accde via un POST :

1.

@RequestMapping(value = "/supprimerRv", method = RequestMethod.POST, consumes =


"application/json; charset=UTF-8")
2. public Reponse supprimerRv(@RequestBody PostSupprimerRv post, HttpServletResponse response) {

Le paramtre post est l encore transmis sous une forme JSON. A la page 103, nous avons montr la nature du POST
ralis la main :

1
2

en [1], l'URL du service web ;


en [2], la mthode POST est utilise;
en [3], le texte JSON des informations transmises au service web sous la forme {idRv} ;
en [4], le client prcise au service web qu'il lui envoie des informations JSON ;

Revenons au code JS de la fonction [supprimer] :

http://tahe.developpez.com

243/325

ligne 11 : on cre l'objet post. Angular le srialisera automatiquement en JSON ;

Le reste du code est analogue celui de la rservation.

3.7.9.9

Modification serveur

Ct serveur, nous faisons les modifications suivantes :

Dans la classe [RdvMedecinsCorsController], nous ajoutons la mthode suivante :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.

// envoi des options au client


private void sendOptions(HttpServletResponse response) {
if (application.isCORSneeded()) {
// on fixe le header CORS
response.addHeader("Access-Control-Allow-Origin", "*");
// on autorise certains headers
response.addHeader("Access-Control-Allow-Headers", "accept, authorization, contenttype");
// on autorise le POST
response.addHeader("Access-Control-Allow-Methods", "POST");
}
}
...
@RequestMapping(value = "/supprimerRv", method = RequestMethod.OPTIONS)
public void supprimerRv(HttpServletResponse response) {
sendOptions(response);
}

L'ajout est fait lignes 13-16. Les enttes des lignes 2-10 seront envoys pour l'URL [/supprimerRv] (ligne 13) et la mthode HTTP
[OPTIONS] (ligne 13).
La classe [RdvMedecinsController] est elle, modifie de la faon suivante :
1.
2.
3.
4.
5.

@RequestMapping(value = "/supprimerRv", method = RequestMethod.POST, consumes =


"application/json; charset=UTF-8")
public Reponse supprimerRv(@RequestBody PostSupprimerRv post, HttpServletResponse response) {
// enttes CORS
rdvMedecinsCorsController.supprimerRv(response);
...

Pour la mthode [POST] (ligne 1) et l'URL [/supprimerRv] (ligne 1), la mthode que nous venons d'ajouter dans
[RdvMedecinsCorsController] est appele (ligne 4), renvoyant donc les mmes enttes HTTP que pour la mthode HTTP
[OPTIONS].

3.7.10

Exemple 10 : crer et annuler des rservations - 2

Nous prsentons maintenant la mme application que prcdemment mais au lieu de rserver pour un client alatoire, celui-ci sera
slectionn dans une liste droulante.

3.7.10.1

La vue V de l'application

Nous prsenterons le formulaire suivant :

http://tahe.developpez.com

244/325

Les clients seront slectionns en [1].


Le code est similaire celui de l'application prcdente, aussi ne prsentons-nous que les principales diffrences.
Nous dupliquons le fichier [app-19.html] dans [app-20.html] puis nous crons le code de la liste droulante des clients [1] :
1. <!-- la liste des clients -->
2.
<div class="alert alert-info">
3.
<h3>{{agenda.title|translate:agenda.model}}</h3>
4.
5.
<div class="row" ng-show="clients.show">
6.
<div class="col-md-3">
7.
<h2 translate="{{clients.title}}"></h2>
8.
<select data-style="btn-primary" class="selectpicker" select-enable="" ngif="clients.data">
9.
<option ng-repeat="client in clients.data" value="{{client.id}}">
10.
{{client.titre}} {{client.prenom}} {{client.nom}}
11.
</option>
12.
</select>
13.
</div>
14.
</div>
15. </div>

lignes 8-12 : la liste droulante va tre implmente avec le composant [bootstrap-select] ;


ligne 1 : la directive [selectEnable] est applique via l'attribut [select-enable] ;
ligne 1 : la balise <select> n'est gnre que si [clients.data] existe (# null, undefined). Ce point est important et a t
expliqu page 224;

Par ailleurs, nous importons de nouveaux fichiers JS :


1.
2.
3.
4.

<script type="text/javascript" src="rdvmedecins-08.js"></script>


<!-- directives -->
<script type="text/javascript" src="selectEnable.js"></script>
<script type="text/javascript" src="footable.js"></script>

ligne 1 : le fichier [rdvmedecins-08.js] est obtenu par recopie du fichier [rdvmedecins-0.js] ;


lignes 3-4 : on importe les fichiers des deux directives ;

http://tahe.developpez.com

245/325

3.7.10.2

Le constructeur C

Le code du contructeur C volue de la faon suivante :


1. // contrleur
2. angular.module("rdvmedecins")
3.
.controller('rdvMedecinsCtrl', ['$scope', 'utils', 'config', 'dao', '$translate', '$timeout',
'$filter', '$locale',
4.
function ($scope, utils, config, dao, $translate, $timeout, $filter, $locale) {
5.
// ------------------- initialisation modle
6. ...
7.
// les clients
8.
$scope.clients = {title: config.listClients, show: false, model: {}};
9.
10.
//------------------------------------------- initilisation vue
11.
// la tche asynchrone globale
12.
var task;
13.
// on demande les clients puis l'agenda
14.
getClients().then(function () {
15.
getAgenda();
16.
});
17. ...
18.
19.
// excution action
20.
function getClients() {
21. ....
22.
};
23. } ]);

ligne 8 : l'objet [$scope.clients] configure la liste droulante des clients dans la vue V ;
lignes 14-16 : de faon asynchrone, on demande d'abord la liste des clients, puis une fois celle-ci obtenue, on demande
l'agenda de Mme PELISSIER pour le jour d'aujourd'hui. La syntaxe utilise ici ne fonctionne que parce que la fonction
[getClients] rend une promesse (promise) ;

La mthode [getClients] demande la liste des clients :


1. function getClients() {
2.
// on met jour l'UI
3.
$scope.waiting.visible = true;
4.
$scope.clients.show = false;
5.
$scope.errors.show = false;
6.
// on demande la liste des clients;
7.
task = dao.getData($scope.server.url, $scope.server.login, $scope.server.password,
config.urlSvrClients);
8.
var promise = task.promise;
9.
// on analyse le rsultat de l'appel prcdent
10.
promise = promise.then(function (result) {
11.
// result={err: 0, data: [client1, client2, ...]}
12.
// result={err: n, messages: [msg1, msg2, ...]}
13.
if (result.err == 0) {
14.
// on met les donnes acquises dans le modle
15.
$scope.clients.data = result.data;
16.
// on met jour l'UI
17.
$scope.clients.show = true;
18.
$scope.waiting.visible = false;
19.
} else {
20.
// il y a eu des erreurs pour obtenir la liste des clients
21.
$scope.errors = { title: config.getClientsErrors, messages: utils.getErrors(result),
show: true, model: {}};
22.
// on met jour l'UI
23.
$scope.waiting.visible = false;
24.
}
25.
});
26.
// on rend la promesse
27.
return promise;
28.
};

http://tahe.developpez.com

246/325

C'est un code que nous avons dj rencontr et comment. L'lment important noter est ligne 31 :

ligne 27 : on rend la promesse de la ligne 10, --d la dernire promesse obtenue dans le code. Cette promesse ne sera
obtenue que lorsque l'appel HTTP aura rendu sa rponse ;
La mthode [reserver] volue lgrement :
1.
2.
3.
4.
5.
6.

$scope.reserver = function (creneauId) {


utils.debug("rservation du crneau", creneauId);
// on cre un RV pour le client slectionn
var idClient = $(".selectpicker").selectpicker('val');
...
});

ligne 4 : on ne rserve plus pour un client alatoire mais pour le client slectionn dans la liste des clients.

3.7.11

Exemple 11 : une directive [selectEnable2]

Cet exemple revient sur les directives.

3.7.11.1

La vue V

L'application affiche la vue suivante :

3.7.11.2

Le code HTML de la vue

Le code HTML de la vue [app-21.html] est le suivant :


1. <div class="container">
2.
<h1>Rdvmedecins - v1</h1>
3.
4.
<!-- le message d'attente -->
5.
<div class="alert alert-warning" ng-show="waiting.visible">
6.
...
7.
</div>
8.
9.
<!-- la liste d'erreurs -->
10. <div class="alert alert-danger" ng-show="errors.show">
11.
...
12. </div>
13.
14. <!-- la liste des clients -->

http://tahe.developpez.com

247/325

15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.

<div class="alert alert-info">


<div class="row" ng-show="clients.show">
<div class="col-md-4">
<h2 translate="{{clients.title}}"></h2>
<select data-style="btn-primary" id="selectpickerClients" select-enable2="" ngif="clients.data">
<option ng-repeat="client in clients.data" value="{{client.id}}">
{{client.titre}} {{client.prenom}} {{client.nom}}
</option>
</select>
</div>
</div>
</div>
<!-- la liste des mdecins -->
<div class="alert alert-info">
<div class="row" ng-show="medecins.show">
<div class="col-md-4">
<h2 translate="{{medecins.title}}"></h2>
<select data-style="btn-primary" id="selectpickerMedecins" select-enable2="" ngif="medecins.data">
<option ng-repeat="medecin in medecins.data" value="{{medecin.id}}">
{{medecin.titre}} {{medecin.prenom}} {{medecin.nom}}
</option>
</select>
</div>
</div>
</div>
</div>
...
<script type="text/javascript" src="rdvmedecins-09.js"></script>
<!-- directives -->

34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45. <script type="text/javascript" src="selectEnable2.js"></script>

lignes 19-23 : la liste droulante des clients ;


ligne 19 : on applique la directive [selectEnable2] (attribut [select-enable2]) ;
ligne 19 : que si [clients.data] est non vide ;
ligne 19 : la liste droulante est identifie par l'attribut [id="selectpickerClients"] ;
lignes 33-37 : la liste droulante des mdecins ;
ligne 33 : on applique la directive [selectEnable2] (attribut [select-enable2]) ;
ligne 33 : que si [medecins.data] est non vide ;
ligne 33 : la liste droulante est identifie par l'attribut [id="selectpickerMedecins"] ;
ligne 43 : on importe un nouvau fichier JS [rdvmedecins-09.js] ;
ligne 45 : on importe le fichier JS de la nouvelle directive ;

3.7.11.3

La directive [selectEnable2]

Le code de la directive [selectEnable2] est le suivant :


1. angular.module("rdvmedecins").directive('selectEnable2', ['$timeout', 'utils', function
($timeout, utils) {
2.
return {
3.
link: function (scope, element, attrs) {
4.
utils.debug("directive selectEnable2 attrs", attrs);
5.
$timeout(function () {
6.
$('#' + attrs['id']).selectpicker();
7.
})
8.
}
9.
}

10. }]);

ligne 4 : on fait afficher la valeur du paramtres [attrs] afin de faire comprendre le fonctionnement du code. On va
dcouvrir que attrs['id']='selectpickerClients' pour la liste des clients ;
ligne 6 : pour localiser dans le DOM un lment d'[id='x'], on crit [$('#x')]. Donc on doit crire [$('#selectpickerClients')]
pour localiser la liste des clients. Ceci est obtenu avec la syntaxe [$('#' + attrs['id'])] ;

http://tahe.developpez.com

248/325

La directive [selectEnable2] utilise donc l'information transporte par l'un des attributs de l'lement HTML auquel elle est
applique.

3.7.11.4

Le contrleur C

Le contrleur C se trouve dans le fichier JS [rdvmedecins-09.js] et a la structure suivante :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.

// contrleur
angular.module("rdvmedecins")
.controller('rdvMedecinsCtrl', ['$scope', 'utils', 'config', 'dao',
function ($scope, utils, config, dao) {
// ------------------- initialisation modle
// le msg d'attente
$scope.waiting = {text: config.msgWaiting, visible: false, cancel: cancel, time: 3000};
// les informations de connexion
$scope.server = {url: 'http://localhost:8080', login: 'admin', password: 'admin'};
// les erreurs
$scope.errors = {show: false, model: {}};
// les mdecins
$scope.medecins = {title: config.listMedecins, show: false, model: {}};
// les clients
$scope.clients = {title: config.listClients, show: false, model: {}};

lignes 26-28 : on demande d'abord les clients puis les mdecins ;

// la tche asynchrone globale


var task;
// ---------------------------------------------------- initialisation vue
// on met jour l'UI
$scope.waiting.visible = true;
$scope.clients.show = false;
$scope.medecins.show = false;
$scope.errors.show = false;
// on demande les clients puis les mdecins
getClients().then(function () {
getMedecins();
});
// liste des clients
function getClients() {
...
}

...

// liste des mdecins


function getMedecins() {
}
// annulation attente
function cancel() {

...

3.7.11.5

}
} ]);

Les tests

Testez cette nouvelle version.

3.7.12

Exemple 12 : une directive [list]

Nous reprenons le mme exemple que prcdemment mais nous voulons allger le code HTML en utilisant une directive. En effet,
nous avons actuellement le code HTML suivant :
1. <!-- la liste des clients -->

http://tahe.developpez.com

249/325

2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.

<div class="alert alert-info">


<div class="row" ng-show="clients.show">
<div class="col-md-4">
<h2 translate="{{clients.title}}"></h2>
<select data-style="btn-primary" id="selectpickerClients" select-enable2="" ngif="clients.data">
<option ng-repeat="client in clients.data" value="{{client.id}}">
{{client.titre}} {{client.prenom}} {{client.nom}}
</option>
</select>
</div>
</div>
</div>
<!-- la liste des mdecins -->
<div class="alert alert-info">
<div class="row" ng-show="medecins.show">
<div class="col-md-4">
<h2 translate="{{medecins.title}}"></h2>
<select data-style="btn-primary" id="selectpickerMedecins" select-enable2="" ngif="medecins.data">
<option ng-repeat="medecin in medecins.data" value="{{medecin.id}}">
{{medecin.titre}} {{medecin.prenom}} {{medecin.nom}}
</option>
</select>
</div>
</div>
</div>

Les lignes 14-26 sont identiques aux lignes 1-13. Elles s'appliquent des mdecins au lieu des clients. Nous voudrions pouvoir crire
la chose suivante :
1.
<!-- la liste des clients -->
2.
<list model="clients" ng-if="clients.show"></list>
3.
<!-- la liste des mdecins -->
4. <list model="medecins" ng-if="medecins.show"></list>

Ce code fait intervenir une nouvelle directive [list] que nous allons crer maintenant.

3.7.12.1

La directive [list]

La directive [list] est place dans le fichier JS [list.js]. Son code est le suivant :
1. angular.module("rdvmedecins")
2.
.directive("list", ['utils', '$timeout', function (utils, $timeout) {
3.
// instance de la directive retourne
4.
return {
5.
// lment HTML
6.
restrict: "E",
7.
// url du fragment
8.
templateUrl: "list.html",
9.
// scope unique chaque instance de la directive
10.
scope: true,
11.
// fonction lien avec le document
12.
link: function (scope, element, attrs) {
13.
utils.debug("directive list attrs", attrs);
14.
scope.model = scope[attrs['model']];
15.
utils.debug("directive list model", scope.model);
16.
$timeout(function () {
17.
$('#' + scope.model.id).selectpicker();
18.
})
19.
}
20.
}

21. }]);

ligne 2 : dfinit une directive nomme 'list' ;

http://tahe.developpez.com

250/325

ligne 6 : l'attribut [restrict] fixe les faons d'utiliser la directive. [restrict: "E"] signifie que la directive [list] est utilisable
comme lment HTML <list ...>...</list>. [restrict: "A"] signifie que la directive [list] est utilisable comme attribut, par
exemple <div ... list='...'>. [restrict: "AE"] signifie que la directive [list] est utilisable comme attribut et comme lment ;
ligne 8 : l'attribut [templateUrl] indique le nom du fragment HTML utiliser la rencontre de la balise. Ce fragment sera le
corps de la balise ;
ligne 10 : l'attribut [scope] fixe la porte du modle de la directive. [scope: true] signifie que deux lments de type <list>
auront chacun leur modle. Par dfaut, (scope non initialis), ils partagent leurs modles ;
ligne 12 : la fonction [link] que nous avons utilise dj plusieurs fois ;

Pour comprendre le code ci-dessus, il faut se rappeler l'utilisation qui va tre faite de la directive :
1.
<!-- la liste des clients -->
2.
<list model="clients" ng-if="clients.show"></list>
3.
<!-- la liste des mdecins -->
4. <list model="medecins" ng-if="medecins.show"></list>

La directive [list] est utilise comme lment HTML <list>. Cet lment a deux attributs :

[model] : qui va avoir pour valeur, l'lment du modle M de la vue V dans laquelle se trouve la directive [list]. Cet lment
va alimenter le modle de la directive ;

[ng-if] : qui va faire en sorte que le code HTML de la directive ne soit pas gnr s'il n'y a rien visualiser ;
Revenons au code de la fonction [link] de la directive :
1. link: function (scope, element, attrs) {
2.
utils.debug("directive list attrs", attrs);
3.
scope.model = scope[attrs['model']];
4.
utils.debug("directive list model", scope.model);
5.
$timeout(function () {
6.
$('#' + scope.model.id).selectpicker();
7.
})
8.
}

Associons ce code JS avec le code HTML qui utilise la directive :


<list model="clients" ng-if="clients.show"></list>

ligne 3 : attrs['model'] a ici pour valeur 'clients' ;


ligne 3 : scope[attrs['model']] a pour valeur scope['clients'] et reprsente alors [$scope.clients], --d le champ [clients]
du modle de la vue. Ce champ aura pour valeur {id :'...', data:[client1, client2, ...], show : ..., title :'...'} ;
ligne 3 : on ajoute un champ [model] au modle de la directive. Celle-ci a hrit du modle de la vue dans laquelle elle se
trouve. Il faut donc viter les collisions avec un ventuel champ [model] que pourrait avoir galement la vue. Ici, il n'y aura
pas de collision ;
ligne 4 : on affiche [scope.model] afin de mieux comprendre le code ;
lignes 5-7 : on retrouve un code dj rencontr. La diffrence est que l'id du composant tait pris auparavant dans un
attribut attrs['id']. L il sera pris dans [scope.model.id] ;

Maintenant, regardons le code HTML gnr par la directive. A cause de l'attribut [templateUrl: "list.html"] de la directive, il faut le
chercher dans le fichier [list.html] :
1. <!-- une liste de clients ou de mdecins -->
2. <div class="alert alert-info" ng-show="model.show">
3.
<div class="row">
4.
<div class="col-md-4">
5.
<h2 translate="{{model.title}}"></h2>
6.
<select data-style="btn-primary" id="{{model.id}}" ng-if="model.data">
7.
<option ng-repeat="element in model.data" value="{{element.id}}">
8.
{{element.titre}} {{element.prenom}} {{element.nom}}
9.
</option>
10.
</select>
11.
</div>
12. </div>
13. </div>

http://tahe.developpez.com

251/325

la premire chose qu'il faut se rappeler pour lire ce code est que la directive a cr un objet [scope.model] de la forme
[{id :'...', data:[client1, client2, ...], show : ..., title :'...'}]. Cet objet [model] (scope est implicite dans le code HTML) est
utilis par le code HTML de la directive ;
ligne 2 : utilisation de [model.show] pour montrer / cacher la vue gnre par la directive ;
ligne 5 : utilisation de [model.title] pour mettre un titre ;
ligne 6 : utilisation de [model.id] pour mettre un id la balise <select>. Cet id est utilis par le code JS de la directive ;
ligne 6 : utilisation de [model.data] pour gnrer le <select> uniquement s'il y a des donnes afficher ;
lignes 7-9 : utilisation de [model.data] pour gnrer les lments de la liste droulante ;

3.7.12.2

Le code HTML

Le code HTML de l'application [app-22.html] est le suivant :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.

<div class="container">
<h1>Rdvmedecins - v1</h1>

ligne 22 : il ne faut pas oublier d'inclure le code JS de la directive ;

<!-- le message d'attente -->


<div class="alert alert-warning" ng-show="waiting.visible">
...
</div>
<!-- la liste d'erreurs -->
<div class="alert alert-danger" ng-show="errors.show">
...
</div>
<!-- la liste des clients -->
<list model="clients" ng-if="clients.show"></list>
<!-- la liste des mdecins -->
<list model="medecins" ng-if="medecins.show"></list>
</div>
...
<script type="text/javascript" src="rdvmedecins-10.js"></script>
<!-- directives -->

<script type="text/javascript" src="list.js"></script>

3.7.12.3

Le contrleur C

Le contrleur C volue trs peu :


1. angular.module("rdvmedecins")
2.
.controller('rdvMedecinsCtrl', ['$scope', 'utils', 'config', 'dao',
3.
function ($scope, utils, config, dao) {
4.
// ------------------- initialisation modle
5. ...
6.
// les mdecins
7.
$scope.medecins = {title: config.listMedecins, show: false, id: 'medecins'};
8.
// les clients
9.
$scope.clients = {title: config.listClients, show: false, id: 'clients'};
10. ...

lignes 7 et 9, nous ajoutons l'attribut [id] aux modles des mdecins et des clients ;

3.7.12.4

Les tests

Les tests donnent les mmes rsultats que dans l'exemple prcdent.

3.7.13

Exemple 13 : mise jour du modle d'une directive

Nous restons dans l'tude des directives et nous gardons l'exemple de la liste droulante. On veut tudier ici le comportement de la
directive [list] lorsque le contenu de la liste droulante change.

http://tahe.developpez.com

252/325

3.7.13.1

Les vues V

Les diffrentes vues sont les suivantes :

en [1], on demande une premire fois la liste des clients ;

en [2], on demande une seconde fois la liste des clients. Cette seconde liste est alors cumule la premire [3]. C'est la mise
jour du composant [Bootstrap select] qu'on veut tudier dans cet exemple.

3.7.13.2

La page HTML

La page HTML [app-23.html] est obtenue par recopie de [app-22.html] puis modifie de la faon suivante :

http://tahe.developpez.com

253/325

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.

<div class="container">
<h1>Rdvmedecins - v1</h1>
<!-- le message d'attente -->
<div class="alert alert-warning" ng-show="waiting.visible">
...
</div>
<!-- la liste d'erreurs -->
<div class="alert alert-danger" ng-show="errors.show">
...
</div>
<!-- le bouton -->
<div class="alert alert-warning">
<button class="btn btn-primary" ng-click="getClients()">{{clients.title|translate}}</button>
</div>
<!-- la liste des clients -->
<list2 model="clients" ng-if="clients.show"></list2>
</div>
...
<script type="text/javascript" src="rdvmedecins-11.js"></script>
<!-- directives -->
<script type="text/javascript" src="list2.js"></script>

Les modifications par rapport l'application prcdente sont les suivantes :

lignes 15-17 : ajout d'un bouton ;


ligne 20 : utilisation d'une nouvelle directive [list2] ;
ligne 23 : utilisation d'un nouveau fichier JS ;
ligne 25 : importation du fichier JS de la directive [list2] ;

3.7.13.3

La directive [list2]

La directive [list2] dans [list2.js] est la suivante :


1. angular.module("rdvmedecins")
2.
.directive("list2", ['utils', '$timeout', function (utils, $timeout) {
3.
// instance de la directive retourne
4.
return {
5.
// lment HTML
6.
restrict: "E",
7.
// url du fragment
8.
templateUrl: "list.html",
9.
// scope unique chaque instance de la directive
10.
scope: true,
11.
// fonction lien avec le document
12.
link: function (scope, element, attrs) {
13.
utils.debug('directive list2');
14.
scope.model = scope[attrs['model']];
15.
$timeout(function () {
16.
$('#' + scope.model.id).selectpicker('refresh');
17.
})
18.
}
19.
}
20. }]);

La seule diffrence avec la directive [list] est ligne 16 : avec la mthode [selectpicker('refresh')], on demande au composant [Bootstrapselect] de se rafrachir. L'ide derrire cela est qu' chaque fois que l'utilisateur va demander une nouvelle liste de clients, on va
rafrachir la liste droulante. Ca ne va pas marcher mais c'est l'ide de base.

http://tahe.developpez.com

254/325

3.7.13.4

Le contrleur C

Le contrleur est dans le fichier [rdvmedecins-11.js] obtenu par recopie du fichier [rdvmedecins-10.js] :
1.
// les clients
2.
$scope.clients = {title: config.listClients, show: false, id: 'clients', data: []};
3. ...
4.
// liste des clients
5.
$scope.getClients = function getClients() {
6.
// on met jour l'UI
7.
$scope.waiting.visible = true;
8.
$scope.errors.show = false;
9.
// on demande la liste des clients;
10.
task = dao.getData($scope.server.url, $scope.server.login, $scope.server.password,
config.urlSvrClients);
11.
var promise = task.promise;
12.
// on analyse le rsultat de l'appel prcdent
13.
promise = promise.then(function (result) {
14.
// result={err: 0, data: [client1, client2, ...]}
15.
// result={err: n, messages: [msg1, msg2, ...]}
16.
if (result.err == 0) {
17.
// on met les donnes acquises dans un nouveau modle pour forcer la vue se
rafrachir
18.
$scope.clients = {title: $scope.clients.title, data:
$scope.clients.data.concat(result.data), show: $scope.clients.show, id: $scope.clients.id};
19.
// on met jour l'UI
20.
$scope.clients.show = true;
21.
$scope.waiting.visible = false;
22.
} else {
23.
// il y a eu des erreurs pour obtenir la liste des clients
24.
$scope.errors = { title: config.getClientsErrors, messages: utils.getErrors(result),
show: true, model: {}};
25.
// on met jour l'UI
26.
$scope.waiting.visible = false;
27.
}
28.
});
29. }

ligne 1 : afin de permettre la concatnation de tableaux dans [clients.data], cet objet est initialis avec un tableau vide ;
ligne 18 : on concatne la nouvelle liste de clients avec celles dj prsentes dans le tableau [clients.data] ;

Avant on avait crit :


1. // on met les donnes acquises dans le modle
2. $scope.clients.data = result.data;

Maintenant on crit :
1. // on met les donnes acquises dans un nouveau modle pour forcer la vue se rafrachir
2. $scope.clients = {title: $scope.clients.title, data: $scope.clients.data.concat(result.data),
show: $scope.clients.show, id: $scope.clients.id};

Pour comprendre ce code, il faut se rappeler comment le modle M est utilis dans la vue V dans le cas de la directive [list2] :
1.
<!-- la liste des clients -->
2. <list2 model="clients" ng-if="clients.show"></list2>

Le modle utilis par la directive [list2] est [clients]. Elle ne sera rvalue dans la vue V, que si [clients] change dans le modle M de
la vue. La premire ide qui vient pour la modification est d'crire :
$scope.clients.data=$scope.clients.data.concat(result.data) ;

pour tenir compte du fait que la nouvelle liste de clients doit tre ajoute aux prcdentes. Ce faisant, on modifie [clients.data] mais
pas [clients]. Je ne connais pas les arcanes de Javascript mais il ne serait pas tonnant que [clients] soit un pointeur, ainsi que

http://tahe.developpez.com

255/325

[clients.data]. Le pointeur [clients] ne change pas lorsqu'on change le pointeur [clients.data]. La directive [list2] n'est alors pas
rvalue. C'est effectivement ce qu'on constate lorsqu'on dbogue l'application (F12 dans Chrome).
En crivant :
$scope.clients = {title: $scope.clients.title, data: $scope.clients.data.concat(result.data), show:
$scope.clients.show, id: $scope.clients.id};

On s'assure que [$scope.clients] reoit bien une nouvelle valeur. Le pointeur [$scope.clients] pointe sur un nouvel objet. La directive
[list2] devrait alors tre rvalue. Mais pourtant, on n'a pas le rsultat cherch. Examinons les copies d'cran lorsqu'on demande
deux fois la liste des clients :

en [1], on n'a que quatre lements au lieu de huit ;


en [2], ces quatre lments sont dans un [select] mais celui-ci est cach (style='display : none');

http://tahe.developpez.com

256/325

en [3], on retrouve les quatre clients dans une autre architecture HTML et c'est celle-ci que l'utilisateur voit lorsqu'il clique
sur la liste droulante ;

Enfin, les logs console disent la chose suivante :


1. [dao] init
2. [dao] getData[/getAllClients] success rponse : {"data":{"status":0,"data":
[{"id":1,"version":1,"titre":"Mr","nom":"MARTIN","prenom":"Jules"},
{"id":2,"version":1,"titre":"Mme","nom":"GERMAN","prenom":"Christine"},
{"id":3,"version":1,"titre":"Mr","nom":"JACQUARD","prenom":"Jules"},
{"id":4,"version":1,"titre":"Melle","nom":"BISTROU","prenom":"Brigitte"}]},"status":200,"config"
:{"method":"GET","transformRequest":[null],"transformResponse":
[null],"timeout":1000,"url":"http://localhost:8080/getAllClients","headers":
{"Accept":"application/json, text/plain, */*","Authorization":"Basic
YWRtaW46YWRtaW4="}},"statusText":"OK"}
3. directive list2
4. [dao] getData[/getAllClients] success rponse : {"data":{"status":0,"data":
[{"id":1,"version":1,"titre":"Mr","nom":"MARTIN","prenom":"Jules"},
{"id":2,"version":1,"titre":"Mme","nom":"GERMAN","prenom":"Christine"},
{"id":3,"version":1,"titre":"Mr","nom":"JACQUARD","prenom":"Jules"},
{"id":4,"version":1,"titre":"Melle","nom":"BISTROU","prenom":"Brigitte"}]},"status":200,"config"
:{"method":"GET","transformRequest":[null],"transformResponse":
[null],"timeout":1000,"url":"http://localhost:8080/getAllClients","headers":
{"Accept":"application/json, text/plain, */*","Authorization":"Basic
YWRtaW46YWRtaW4="}},"statusText":"OK"}

ligne 1 : le service [dao] est instanci ;


ligne 2 : le service [dao] obtient une premire liste de clients ;
ligne 3 : la directive [list2] est excute ;
ligne 4 : le service [dao] obtient une seconde liste de clients ;

L'affichage de la ligne 2 vient du code suivant dans la directive :


1.
2.
3.
4. }

link: function (scope, element, attrs) {


utils.debug('directive list2');
...

Examinons le cycle de vie de la directive [list2] :

entre les lignes 1 et 2, elle n'est pas active alors que la vue a t affiche une premire fois. C'est d son attribut [ ngif="clients.show"] dans la vue V :
<list2 model="clients" ng-if="clients.show"></list2>

ligne 3 : aprs l'obtention de la premire liste de mdecins, [clients.show] passe true et la directive est active ;
aprs l'obtention de la seconde liste de clients, on voit que le code de la directive [list2] n'est pas appel. C'est pourquoi, on
ne voit pas la seconde liste ;

Pour rsoudre ce problme, nous modifions la directive [list2] de la faon suivante :


1. angular.module("rdvmedecins")
2.
.directive("list2", ['utils', '$timeout', function (utils, $timeout) {
3.
// instance de la directive retourne
4.
return {
5.
// lment HTML
6.
restrict: "E",
7.
// url du fragment
8.
templateUrl: "list.html",
9.
// scope unique chaque instance de la directive
10.
scope: true,
11.
// fonction lien avec le document
12.
link: function (scope, element, attrs) {
13.
// chaque fois que attrs["model"] change, le modle de la directive doit changer
galement

http://tahe.developpez.com

257/325

14.
15.
16.
17.
18.
19.
20.
21.
22.
23.

scope.$watch(attrs["model"], function (newValue) {


utils.debug("directive list2 newValue", newValue);
// on met jour le modle de la directive
scope.model = newValue;
$timeout(function () {
$('#' + scope.model.id).selectpicker('refresh');
})
});
}

24. }]);

ligne 14 : la fonction [scope.$watch] permet d'observer une valeur du modle. Sa syntaxe est [scope.$watch('var'), f] o
[var] est l'identifiant d'une variable du modle et f la fonction excuter lorsque cette variable change de valeur. Ici, nous
voulons observer la variable [clients]. Donc on doit crire [scope.$watch('clients')]. Comme on a attrs['model']='clients', on
crit [scope.$watch(attrs["model"], function (newValue)] ;
ligne 14 : le second paramtre de la fonction [scope.$watch] est la fonction excuter lorsque la variable observe change
de valeur. Le paramtre [newValue] est la nouvelle valeur de la variable, donc pour nous la nouvelle valeur de la variable
[clients] du modle ;
ligne 17 : cette nouvelle valeur est affecte au champ [model] du modle de la directive ;

Cette modification faite, les logs changent :

Ci-dessus, on voit qu'aprs avoir obtenu la seconde liste de clients, la directive [list2] est bien excute de nouveau, ce que confirme
le rsultat [2].

3.7.14

Exemple 14 : les directives [waiting] et [errors]

Revenons au code HTML de l'application prcdente :


1. <div class="container">
2.
<h1>Rdvmedecins - v1</h1>
3.
4.
<!-- le message d'attente -->
5.
<div class="alert alert-warning" ng-show="waiting.visible">
6.
...
7.
</div>
8.
9.
<!-- la liste d'erreurs -->
10. <div class="alert alert-danger" ng-show="errors.show">
11. ...
12. </div>
13.
14. <!-- le bouton -->

http://tahe.developpez.com

258/325

15. <div class="alert alert-warning">


16.
<button class="btn btn-primary" ng-click="getClients()">{{clients.title|translate}}</button>
17. </div>
18.
19. <!-- la liste des clients -->
20. <list2 model="clients" ng-if="clients.show"></list2>
21. </div>

lignes 5-7 : le message d'attente ;


lignes 10-12 : le message d'erreurs ;

Nous dcidons de mettre les codes HTML de ces deux messages dans des directives.

3.7.14.1

Le nouveau code HTML

Le nouveau code HTML [app-24.html] est le suivant :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.

<div class="container">
<h1>Rdvmedecins - v1</h1>

ligne 5 : la directive pour le message d'attente ;


ligne 8 : la directive pour le message d'erreurs ;
ligne 19 : le nouveau fichier JS associ l'application ;
lignes 21-23 : les fichiers JS des trois directives ;

<!-- le message d'attente -->


<waiting model="waiting"></waiting>
<!-- la liste d'erreurs -->
<errors model="errors"></errors>
<!-- le bouton -->
<div class="alert alert-warning">
<button class="btn btn-primary" ng-click="getClients()">{{clients.title|translate}}</button>
</div>
<!-- la liste des clients -->
<list2 model="clients" ng-if="clients.show"></list2>
</div>
...
<script type="text/javascript" src="rdvmedecins-12.js"></script>
<!-- directives -->
<script type="text/javascript" src="list2.js"></script>
<script type="text/javascript" src="errors.js"></script>
<script type="text/javascript" src="waiting.js"></script>

3.7.14.2

La directive [waiting]

Le code JS de la directive [waiting] est dans le fichier [waiting.js] suivant :


1. angular.module("rdvmedecins")
2.
.directive("waiting", ['utils', function (utils) {
3.
// instance de la directive retourne
4.
return {
5.
// lment HTML
6.
restrict: "E",
7.
// url du fragment
8.
templateUrl: "waiting.html",
9.
// scope unique chaque instance de la directive
10.
scope: true,
11.
// fonction lien avec le document
12.
link: function (scope, element, attrs) {
13.
// chaque fois que attr["model"] change, le modle de la page doit changer galement
14.
scope.$watch(attrs["model"], function (newValue) {
15.
utils.debug("[waiting] watch newValue", newValue);

http://tahe.developpez.com

259/325

16.
17.
18.
19.

20.

scope.model = newValue;
});
}

}]);

Ce code suit la mme logique que celui de la directive [list2] dj tudie.


Ligne 8, on rfrence le fichier [waiting.html] suivant :
1. <div class="alert alert-warning" ng-show="model.show">
2.
<h1>{{ model.title.text | translate:model.title.values}}
3.
<button class="btn btn-primary pull-right" ng-click="model.cancel()">{{'cancel'|
translate}}</button>
4.
<img src="assets/images/waiting.gif" alt=""/>
5.
</h1>
6. </div>

Dans le code JS de l'application, le modle [$scope.waiting] de ce code HTML sera dfini de la faon suivante :
// le msg d'attente
$scope.waiting = {title: {text: config.msgWaiting, values: {}}, show: false, cancel: cancel, time:
3000};

3.7.14.3

La directive [errors]

Le code JS de la directive [errors] est dans le fichier [errors.js] suivant :


1. angular.module("rdvmedecins")
2.
.directive("errors", ['utils', function (utils) {
3.
// instance de la directive retourne
4.
return {
5.
// lment HTML
6.
restrict: "E",
7.
// url du fragment
8.
templateUrl: "errors.html",
9.
// scope unique chaque instance de la directive
10.
scope: true,
11.
// fonction lien avec le document
12.
link: function (scope, element, attrs) {
13.
// chaque fois que attr["model"] change, le modle de la page doit changer galement
14.
scope.$watch(attrs["model"], function (newValue) {
15.
utils.debug("[errors] watch newValue", newValue);
16.
scope.model = newValue;
17.
});
18.
}
19.
}

20. }]);

Ce code suit la mme logique que celui de la directive [list2] dj tudi.


Ligne 8, on rfrence le fichier [errors.html] suivant :
1. <div class="alert alert-danger" ng-show="model.show">
2.
{{model.title.text|translate:model.title.values}}
3.
<ul>
4.
<li ng-repeat="message in model.messages">{{message|translate}}</li>
5.
</ul>
6. </div>

Dans le code JS de l'application, le modle [$scope.errors] de ce code HTML sera dfini de la faon suivante :
// il y a eu des erreurs pour obtenir la liste des clients
$scope.errors = { title: { text: config.getClientsErrors, values: {}}, messages:
utils.getErrors(result), show: true, model: {}};

http://tahe.developpez.com

260/325

3.7.15

Exemple 15 : navigation

Jusqu' maintenant, nous avons utilis des applications page unique. Nous abordons dans cet exemple, les applications plusieurs
pages et la navigation entre celles-ci.

3.7.15.1

Les vues V de l'application


1
5
4
3
2

en [1], l'URL de la vue n 1 ;


en [2], son contenu ;
en [3], on passe la page 2 ;
en [4], la vue n 2 ;
en [5], on passe la page 3 ;

8
6

en [6], la vue n 3 ;
en [7], on passe la page 1 ;
en [8], on est revenu la vue n 1 ;

3.7.15.2

Organisation du code

Nous commenons une nouvelle organisation du code :

http://tahe.developpez.com

261/325

les vues de l'application seront places dans le dossier [views] ;


le module de l'application sera plac dans le dossier [modules] ;
les contrleurs de l'application seront placs dans le dossier [controllers] ;

De mme, dans la version finale :

les services seront placs dans le dossier [services] ;

les directives seront places dans le dossier [directives] ;

3.7.15.3

Le conteneur des vues

Les vues du dossier [views] seront affiches dans le conteneur suivant [app-25.html] :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.

<!DOCTYPE html>
<html ng-app="rdvmedecins">
<head>
...
</head>
<body>
<div class="container" ng-controller="mainCtrl">
<!-- la barre de navigation -->
<ng-include src="'views/navbar.html'"></ng-include>

ligne 7 : le corps du conteneur est contrl par [mainCtrl] ;


ligne 9 : la directive [ng-include] permet d'inclure un fichier HTML externe, ici une barre de navigation ;
ligne 12 : les diffrentes vues affiches par le conteneur le sont l'intrieur de la directive [ng-view]. Au final, on a un
conteneur qui affiche :
toujours la mme barre de navigation (ligne 9) ;
des vues diffrentes en ligne 12 ;
lignes 16-22 : on importe les fichiers JS du module de l'application [rdvmedecins-13.js] et de ses contrleurs ;

<!-- la vue courante -->


<ng-view></ng-view>
</div>
...
<!-- le module -->
<script type="text/javascript"
<!-- les contrleurs -->
<script type="text/javascript"
<script type="text/javascript"
<script type="text/javascript"
<script type="text/javascript"
</body>
</html>

http://tahe.developpez.com

src="modules/rdvmedecins-13.js"></script>
src="controllers/mainController.js"></script>
src="controllers/page1Controller.js"></script>
src="controllers/page2Controller.js"></script>
src="controllers/page3Controller.js"></script>

262/325

3.7.15.4

Le module de l'application

Le fichier [rdvmedecins-13.js] dfinit le module de l'application et le routage entre vues :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.

// --------------------- module Angular


angular.module("rdvmedecins", [ 'ngRoute' ]);

ligne 1 : on dfinit le module [rdvmedecins]. Il a une dpendance sur le module [ngRoute] fourni par la bibliothque
[angular-route.min.js]. C'est ce module qui permet le routage dfini aux lignes 6-24 ;
ligne 4 : dfinit la fonction [config] du module [rdvmedecins]. On rappelle que cette fonction est excute avant toute
instanciation de service. C'est une fonction de configuration du module. Ici, c'est son routage qui est configur. Ceci est
fait au moyen de l'objet [$routeProvider] fourni par le module [ngRoute] ;
lignes 6-10 : dfinissent la vue afficher lorsque l'utilisateur demande l'URL [/page1]. C'est un routage interne
l'application. L'URL est en fait [/rdvmedecins-angular-v1/app-21.html#/page1]. On voit que c'est toujours l'URL du
conteneur [/rdvmedecins-angular-v1/app-21.html] qui est utilise mais avec une information supplmentaire derrire un
caractre #. C'est cette information supplmentaire que le routage Angular gre ;
ligne 8 : indique le fragment HTML insrer dans la directive [ng-view] du conteneur :
ligne 9 : indique le nom du contrleur de ce fragment ;
lignes 11-15 : dfinissent la vue afficher lorsque l'utilisateur demande l'URL [/page2] ;
lignes 16-20 : dfinissent la vue afficher lorsque l'utilisateur demande l'URL [/page3] ;
lignes 21-24 : dfinissent le routage exercer lorsque l'URL demande n'est pas l'une des trois prcdentes (otherwise,
ligne 21) ;
ligne 23 : redirection vers l'URL [/page1], donc vers la vue dfinie aux lignes 6-10 ;

angular.module("rdvmedecins").config(["$routeProvider", function ($routeProvider) {


// ------------------------ routage
$routeProvider.when("/page1",
{
templateUrl: "views/page1.html",
controller: 'page1Ctrl'
});
$routeProvider.when("/page2",
{
templateUrl: "views/page2.html",
controller: 'page2Ctrl'
});
$routeProvider.when("/page3",
{
templateUrl: "views/page3.html",
controller: 'page3Ctrl'
});
$routeProvider.otherwise(
{
redirectTo: "/page1"
});
}]);

3.7.15.5

Le contrleur du conteneur de vues

Nous avons vu que le conteneur de vues dclarait un contrleur :


<div class="container" ng-controller="mainCtrl">

Le contrleur [mainCtrl] est dfini dans le fichier [mainController.js] :


1. // contrleur
2. angular.module("rdvmedecins")
3.
.controller('mainCtrl', ['$scope', '$location',
4.
function ($scope, $location) {
5.
6.
// modles des pages
7.
$scope.page1 = {};
8.
$scope.page2 = {};
9.
$scope.page3 = {};
10.
// modle global

http://tahe.developpez.com

263/325

11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24. }]);

var main = $scope.main = {};


main.text = "[Modle global]";
// mthodes exposes la vue
main.showPage1 = function () {
$location.path("/page1");
};
main.showPage2 = function () {
$location.path("/page2");
};
main.showPage3 = function () {
$location.path("/page3");
}

ligne 3 : le contrleur [mainCtrl] a besoin de l'objet [$location] fourni par le module de routage [ngRoute]. Cet objet
permet de changer de vue (lignes 16, 19, 22) ;

Revenons au code du conteneur :


1.
<div class="container" ng-controller="mainCtrl">
2.
<!-- la barre de navigation -->
3.
<ng-include src="'views/navbar.html'"></ng-include>
4.
5.
<!-- la vue courante -->
6.
<ng-view></ng-view>
7. </div>

le contrleur [mainCtrl] construit le modle de la zone 1-7 ;


la vue incluse en ligne 6 a galement un contrleur. Par exemple la vue [page1] a le contrleur [page1Ctrl]. Celui-ci
construit le modle de la zone affiche ligne 6. On a alors dans cette zone deux modles :
le modle construit par le contrleur [mainCtrl] ;
le modle construit par le contrleur [page1Ctrl] ;
Il y a hritage des modles. Dans la vue affiche ligne 6, les modles des contrleurs [mainCtrl] et [pagexCtrl] sont
visibles tous les deux. Si deux variables de ces modles portent le mme nom, l'une va cacher l'autre. Pour viter cette
collision des noms, nous crons quatre modles sous quatre noms :

page

contrleur modle ligne du code

conteneur mainCtrl

main

11

page1

page1Ctrl

page1

page2

page2Ctrl

page2

page3

page3Ctrl

page3

ligne 12 : dfinit un lment [text] dans le modle [main] ;

Les lignes 7-11 ont une consquence trs particulire : elles dfinissent le [$scope] du contrleur [mainCtrl] et dans celui-ci, elles
crent quatre variables [main, page1, page2, page3]. Ces quatre variables vont tre utilises comme modles respectifs du conteneur
et des trois vues qu'il va contenir tour tour.

3.7.15.6

La barre de navigation

La barre de navigation est dfinie de la faon suivante dans le conteneur :


1.
<div class="container" ng-controller="mainCtrl">
2.
<!-- la barre de navigation -->
3.
<ng-include src="'views/navbar.html'"></ng-include>
4.
5.
<!-- la vue courante -->
6.
<ng-view></ng-view>
7. </div>

http://tahe.developpez.com

264/325

La barre de navigation est dfinie ligne 3. Cela signifie qu'elle ne connat que le modle [main]. Son code est le suivant :
1. <div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
2.
<div class="container">
3.
<div class="navbar-header">
4.
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbarcollapse">
5.
<span class="sr-only">Toggle navigation</span>
6.
<span class="icon-bar"></span>
7.
<span class="icon-bar"></span>
8.
<span class="icon-bar"></span>
9.
</button>
10.
<a class="navbar-brand" href="#">RdvMedecins</a>
11.
</div>
12.
<div class="collapse navbar-collapse">
13.
<ul class="nav navbar-nav">
14.
<li class="active">
15.
<a href="">
16.
<span ng-click="main.showPage1()">Page 1</span>
17.
</a>
18.
</li>
19.
<li class="active">
20.
<a href="">
21.
<span ng-click="main.showPage2()">Page 2</span>
22.
</a>
23.
</li>
24.
<li class="active">
25.
<a href="">
26.
<span ng-click="main.showPage3()">Page 3</span>
27.
</a>
28.
</li>
29.
</ul>
30.
</div>
31. </div>
32. </div>

aux lignes 16, 21, 26, ce sont des mthodes du modle [main] qui sont utilises ;
ligne 16 : un clic sur le lien [Page1] va lancer l'excution de la mthode [$scope.main.showPage1]. Celle-ci est dfinie
dans le contrleur [mainCtrl] de la faon suivante :

1.
2.
3.
4.
5.
6.
7.
8. };

// modle global
var main = $scope.main = {};
main.text = "[Modle global]";
// mthodes exposes la vue
main.showPage1 = function () {
$location.path("/page1");

ligne 6 : du code qui prcde, on voit que la mthode [main.showPage1] est en ralit la mthode
[$scope.main.showPage1]. C'est donc bien celle-ci qui va s'excuter ;
ligne 7 : on change l'URL de l'application qui devient [/page1]. Revenons au routage qui a t dfini dans le module
principal :
1.
$routeProvider.when("/page1",
2.
{
3.
templateUrl: "views/page1.html",
4.
controller: 'page1Ctrl'
5. });

on voit que le fragment [views/page1.html] va tre insr dans le conteneur et que son contrleur est [page1Ctrl].

3.7.15.7

La vue [/page1] et son contrleur

Le fragment [views/page1.html] est le suivant :

http://tahe.developpez.com

265/325

1. <h1>Page 1</h1>
2. <div class="alert alert-info">
3.
<ul>
4.
<li>Modle global : {{main.text}}</li>
5.
<li>Modle local : {{page1.text}}</li>
6.
</ul>
7. </div>

On se rappelle que dans la vue insre dans le conteneur, le modle [main] est visible. C'est ce qu'on veut vrifier ligne 4. Par
ailleurs, le contrleur [page1Ctrl] du fragment [views/page1.html] dfinit un modle [page1]. C'est lui qui est utilis ligne 5.
Le code du contrleur [page1Ctrl] est le suivant :
1. angular.module("rdvmedecins")
2.
.controller('page1Ctrl', ['$scope',
3.
function ($scope) {
4.
5.
// modle de la page 1
6.
var page1=$scope.page1;
7.
page1.text="[Modle local dans page 1]";
8. }]);

ligne 2 : le [$scope] inject ici n'est pas vide. Puisque le contrleur [page1Ctrl] contrle une zone insre dans un
conteneur contrl par [mainCtrl], le [$scope] de la ligne 2 contient les lments du [$scope] dfini par le contrleur
[mainCtrl]. Il est important de le comprendre. Le [$scope] dfini par le contrleur [mainCtrl] contient les lments suivants
[main, page1, page2, page3]. Cela signifie qu'on a accs aux modles de toutes les vues. Ce n'est pas forcment
dsirable mais c'est le cas ici. Dans la version finale du client Angular, nous utiliserons cette particularit pour stocker dans
le modle [main] les informations qui doivent tre partages entre vues. On aura l, un concept analogue au concept de
'session' ct serveur ;
ligne 6 : on rcupre dans le [$scope] le modle [page1] de la page 1 et ensuite on travaille avec (ligne 7). On obtient alors
l'affichage suivant :

Les vues [/page2] et [/page3] sont construits sur le mme modle que la vue [/page1] (voir les copies d'cran page 261).

3.7.15.8

Contrle de la navigation

Nous souhaitons maintenant contrler la navigation de la faon suivante [page1 --> page2 --> page3 --> page1]. Ainsi si l'utilisateur
est sur la page 1 [/page1] et qu'il tape dans son navigateur l'URL [/page3] alors cette navigation ne doit pas tre accepte et on doit
rester sur la page 1.
Pour obtenir ce rsultat, nous modifions les contrleurs des pages de la faon suivante :
1. angular.module("rdvmedecins")
2.
.controller('page1Ctrl', ['$scope', '$location',
3.
function ($scope, $location) {

http://tahe.developpez.com

266/325

4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.

// navigation autorise ?
var main = $scope.main;
if (main.lastUrl && main.lastUrl != '/page3') {
// on revient la dernire URL
$location.path(main.lastUrl);
return;
}
// on mmorise l'URL de la page
main.lastUrl = '/page1';
// modle de la page
var page1 = $scope.page1;
page1.text = "[Modle local dans page 1]";
}]);

ligne 12 : lorsqu'une page sera affiche, on mmorisera son URL dans le modle [main.lastUrl]. Nous utilisons ici le
concept dont nous avons parl prcdemment : utiliser le modle [main] pour stocker des informations partages par
toutes les vues. Ici, c'est la dernire URL consulte ;
le code des lignes 4-12 est dupliqu et adapt aux trois vues. Ici on est dans la vue [/page1] ;
ligne 5 : on rcupre le modle [main] ;
ligne 6 : si le modle [main.lastUrl] existe et s'il est diffrent de [/page3] alors la navigation est interdite (la dernire URL
visite existe et n'est pas /page3) ;
ligne 8 : on revient alors sur la dernire URL visite ;

Faisons un essai :

3.7.16

en [1], on est sur la page 1 et on tape l'URL de la page 3 en [2] ;


en [3], la navigation n'a pas eu lieu et on est revenu sur l'URL de la page 1 ;

Conclusion

Nous avons balay tous les cas d'utilisation que nous allons rencontrer dans la version finale du client Angular. Lorsque nous allons
prsenter celui-ci, nous commenterons davantage les fonctionnalits de l'application que ses dtails d'implmentation. Pour ces
derniers, nous nous contenterons de faire rfrence l'exemple illustrant le cas d'utilisation alors tudi.

3.8
3.8.1

Le client final Angular


Structure du projet

Le projet final a l'allure suivante :

http://tahe.developpez.com

267/325

1
2

3.8.2

en [1], l'ensemble du projet. [app.html] est la page matre de l'application ;


en [2], les contrleurs ;
en [3], les directives ;
en [4], les services et le module Angular [main.js] de l'application ;
en [5], les diffrentes vues qui viennent s'insrer dans la page matre [app.html] ;

Les dpendances du projet

Les dpendances du projet sont les suivantes :

Le rle de ces diffrents lments a t expliqu au paragraphe 3.4, page 146.

http://tahe.developpez.com

268/325

3.8.3

La page matre [app.html]

La page matre est la suivante :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.

<!DOCTYPE html>
<html ng-app="rdvmedecins">
<head>
<title>RdvMedecins</title>
<!-- META -->
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Angular client for RdvMedecins">
<meta name="author" content="Serge Tah">
<!-- le CSS -->
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.min.css"/>
<link href="bower_components/bootstrap/dist/css/bootstrap-theme.min.css" rel="stylesheet"/>
<link href="bower_components/bootstrap-select/bootstrap-select.min.css" rel="stylesheet"/>
<link href="assets/css/rdvmedecins.css" rel="stylesheet"/>
<link href="assets/css/footable.core.min.css" rel="stylesheet"/>
</head>
<!-- contrleur [appCtrl], modle [app] -->
<body ng-controller="appCtrl">
<div class="container">
...
</div>
<!-- Bootstrap core JavaScript ================================================== -->
<script type="text/javascript" src="bower_components/jquery/dist/jquery.min.js"></script>
<script type="text/javascript" src="bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
<script type="text/javascript" src="bower_components/bootstrap-select/bootstrap-select.min.js"></script>
<script src="bower_components/footable/js/footable.js" type="text/javascript"></script>
<!-- angular js -->
<script type="text/javascript" src="bower_components/angular/angular.min.js"></script>
<script type="text/javascript" src="bower_components/angular-ui-bootstrap-bower/ui-bootstraptpls.min.js"></script>
<script type="text/javascript" src="bower_components/angular-route/angular-route.min.js"></script>
<script type="text/javascript" src="bower_components/angular-translate/angular-translate.min.js"></script>
<script type="text/javascript" src="bower_components/angular-base64/angular-base64.min.js"></script>
<!-- modules -->
<script type="text/javascript" src="modules/main.js"></script>
<!-- services -->
<script type="text/javascript" src="services/config.js"></script>
<script type="text/javascript" src="services/dao.js"></script>
<script type="text/javascript" src="services/utils.js"></script>
<!-- directives -->
<script type="text/javascript" src="directives/waiting.js"></script>
<script type="text/javascript" src="directives/errors.js"></script>
<script type="text/javascript" src="directives/footable.js"></script>
<script type="text/javascript" src="directives/debug.js"></script>
<script type="text/javascript" src="directives/list.js"></script>
<!-- controllers -->
<script type="text/javascript" src="controllers/appController.js"></script>
<script type="text/javascript" src="controllers/loginController.js"></script>
<script type="text/javascript" src="controllers/homeController.js"></script>
<script type="text/javascript" src="controllers/agendaController.js"></script>
<script type="text/javascript" src="controllers/resaController.js"></script>
</body>
</html>

ligne 18 : on notera que [appCtrl] est le contrleur de la page matre ;


lignes 19-21 : le contenu de la page matre ;

Ce contenu est le suivant :


1. <div class="container">
2.
<!-- les barres de navigation -->
3.
<ng-include src="'views/navbar-start.html'" ng-show="app.navbarstart.show"></ng-include>
4.
<ng-include src="'views/navbar-run.html'" ng-show="app.navbarrun.show"></ng-include>
5.
<!-- le jumbotron -->
6.
<ng-include src="'views/jumbotron.html'"></ng-include>
7.
<!-- le titre de la page -->
8.
<div class="alert alert-info" ng-show="app.titre.show" translate="{{app.titre.text}}"
9.
translate-values="{{app.titre.model}}"></div>

http://tahe.developpez.com

269/325

10. <!-- les erreurs de la page -->


11. <errors model="app.errors" ng-show="app.errors.show"></errors>
12. <!-- le message d'attente -->
13. <waiting model="app.waiting" ng-show="app.waiting.show"></waiting>
14. <!-- la vue courante -->
15. <ng-view></ng-view>
16. <!-- debug -->
17. <debug model="app" ng-show="app.debug.on"></debug>
18. </div>

Quelque soit la vue affiche, elle aura toujours les lments suivants :

lignes 3-4 : une barre de commande. Les deux barres des lignes 3 et 4 sont exclusives l'une de l'autre ;

ligne 6 : un logo / texte de l'application :

ligne 8 : un titre

ligne 11 : un message d'erreurs :

ligne 13 : un message d'attente :

http://tahe.developpez.com

270/325

ligne 17 : une information de dbogage :

Tous les lments prcdents sont contrls par une directive [ng-show / ng-hide] qui fait que s'ils sont bien prsents, ils ne sont pas
forcment visibles.

3.8.4

Les vues de l'application

Dans le code de la page matre, on a :


1. <div class="container">
2.
...
3.
<!-- la vue courante -->
4.
<ng-view></ng-view>
5.
...
6. </div>

La ligne 4 reoit les diffrentes vues de l'application. Celles-ci sont dfinies dans le module [main.js] :

http://tahe.developpez.com

271/325

Le rle de la configuration des diffrentes routes a t expliqu au paragraphe 3.7.15.4, page 263.
La vue [login.html] est vide, --d qu'elle ne rajoute aucun lment ceux dj prsents dans la page matre.
La vue [home.html] rajoute l'lment suivant la page matre :

La vue [agenda.html] rajoute l'lment suivant la page matre :

http://tahe.developpez.com

272/325

La vue [resa.html] rajoute l'lment suivant la page matre :

3.8.5

Fonctionnalits de l'application

Les vues du client Angular ont dj t prsentes au paragraphe 1.3.3, page 11. Pour faciliter la lecture de ce nouveau chapitre,
nous les redonnons ici. La premire vue est la suivante :

http://tahe.developpez.com

273/325

9
10

11

12

13

14

en [6], la page d'entre de l'application. Il s'agit d'une application de prise de rendez-vous pour des mdecins ;
en [7], une case cocher qui permet d'tre ou non en mode [debug]. Ce dernier se caractrise par la prsence du cadre [8]
qui affiche le modle de la vue courante ;
en [9], une dure d'attente artificielle en millisecondes. Elle vaut 0 par dfaut (pas d'attente). Si N est la valeur de ce temps
d'attente, toute action de l'utilisateur sera excute aprs un temps d'attente de N millisecondes. Cela permet de voir la
gestion de l'attente mise en place par l'application ;
en [10], l'URL du serveur Spring 4. Si on suit ce qui a prcd, c'est [http://localhost:8080];
en [11] et [12], l'identifiant et le mot de passe de celui qui veut utiliser l'application. Il y a deux utilisateurs : admin/admin
(login/password) avec un rle (ADMIN) et user/user avec un rle (USER). Seul le rle ADMIN a le droit d'utiliser
l'application. Le rle USER n'est l que pour montrer ce que rpond le serveur dans ce cas d'utilisation ;
en [13], le bouton qui permet de se connecter au serveur ;
en [14], la langue de l'application. Il y en a deux : le franais par dfaut et l'anglais.

en [1], on se connecte ;

http://tahe.developpez.com

274/325

une fois connect, on peut choisir le mdecin avec lequel on veut un rendez-vous [2] et le jour de celui-ci [3] ;
on demande en [4] voir l'agenda du mdecin choisi pour le jour choisi ;

http://tahe.developpez.com

275/325

une fois obtenu l'agenda du mdecin, on peut rserver un crneau [5] ;

en [6], on choisit le patient pour le rendez-vous et on valide ce choix en [7] ;

http://tahe.developpez.com

276/325

Une fois le rendez-vous valid, on est ramen automatiquement l'agenda o le nouveau rendez-vous est dsormais inscrit. Ce
rendez-vous pourra tre ultrieurement supprim [7].
Les principales fonctionnalits ont t dcrites. Elles sont simples. Celles qui n'ont pas t dcrites sont des fonctions de navigation
pour revenir une vue prcdente. Terminons par la gestion de la langue :

http://tahe.developpez.com

277/325

en [1], on passe du franais l'anglais ;

3.8.6

en [2], la vue est passe en anglais, y-compris le calendrier ;

Le module [main.js]

Le module [main.js] dfinit le module Angular qui va contrler l'application :

ligne 4 : le module s'appelle [rdvmedecins] ;


ligne 5 : le module [ngRoute] est utilis pour le routage des URL ;
ligne 6 : le module [translate] est utilis pour l'internationalisation des textes ;
ligne 7 : le module [base64] est utilis pour coder en Base64 la chane 'login:password' ;
ligne 8 : le module [ngLocale] est utilis pour internationaliser le calendrier ;
ligne 9 : le module [ui.bootstrap] est utilis pour le calendrier ;
ligne 12 : la configuration des routes ;
ligne 40 : l'internationalisation des messages ;

http://tahe.developpez.com

278/325

3.8.7

Le contrleur de la page matre

Rappelons le code HTML de la page matre [app.html] :


1. <body ng-controller="appCtrl">
2. <div class="container">
3. ...

Ligne 1, tout le corps (body) de la page matre est contrl par le contrleur [appCtrl]. De par sa position, cela en fait un contrleur
gnral et principal de l'application. Comme il a t expliqu au paragraphe 3.7.15, page 261, le modle construit par ce contrleur
est hrit par toutes les vues qui viendront s'insrer dans la page matre.
Son code est le suivant :
1. angular.module("rdvmedecins")
2.
.controller("appCtrl", ['$scope', 'config', 'utils', '$location', '$locale',
3.
function ($scope, config, utils, $location, $locale) {
4.
5.
// debug
6.
utils.debug("[app] init");
7.
8.
// ----------------------------------------initialisation page
9.
// les modles des # pages
10.
$scope.app = {waitingTimeBeforeTask: config.waitingTimeBeforeTask};
11.
$scope.login = {};
12.
$scope.home = {};
13.
$scope.agenda = {};
14.
$scope.resa = {};
15.
// modle de la page courante
16.
var app = $scope.app;
17.
...
18.
19.
// ---------------------------------- mthodes
20.
21.
// annulation tche courante
22.
app.cancel = function () {
23. ...
24.
};
25.
26.
// dconnexion
27.
app.deconnecter = function () {
28.
...
29.
};
30.
31.
// ce code doit rester l car il rfrence la fonction [cancel] qui prcde
32.
app.waiting = {title: {text: config.msgWaitingInit, values: {}}, cancel: app.cancel, show:
true};
33.
}])
34. ;

Les lignes 10-14 dfinissent les cinq modles qui sont utiliss dans l'application :
Modle

Vue

Contrleur

$scope.app

app.html

appCtrl

$scope.login

login.html

loginCtrl

$scope.home

home.html

homeCtrl

$scope.resa

resa.html

resaCtrl

$scope.agenda agenda.html agendaCtrl

Ce qu'il est important de comprendre est que l'objet [$scope] tant le modle du contrleur de la page matre, est hrit par toutes
les vues et contrleurs. Ainsi le contrleur [loginCtrl] a accs aux lments [$scope.app, $scope.login, $scope.home, $scope.resa,

http://tahe.developpez.com

279/325

$scope.agenda]. Dit autrement un contrleur a accs aux modles des autres contrleurs. L'application tudie vite
soigneusement d'utiliser cette possibilit. Ainsi, par exemple, le contrleur [loginCtrl] travaille avec deux modles seulement :

le sien [$scope.login] ;

et celui du contrleur parent [$scope.app] ;


Il en est de mme pour tous les autres contrleurs. Le modle [$scope.app] sera utilis comme mmoire partage entre les diffrents
contrleurs. Lorsqu'un contrleur C1 devra transmettre de l'information au contrleur C2, on procdera ainsi :
Dans [C1] :
$scope.app.info=value ;

Dans [C2] :
var value=$scope.app.info ;

Dans les deux cas, $scope est hrit du contrleur [appCtrl] et est donc identique (c'est un pointeur) dans [C1] et [C2]. L'objet
[$scope.app] qui sert de mmoire partage entre les contrleurs sera souvent appel session dans les commentaires, par mimtisme
avec la session utilise dans les applications web classiques qui dsigne la mmoire partage entre requtes HTTP successives.
Revenons au code du contrleur [appCtrl] :
1.
// les modles des # pages
2.
$scope.app = {waitingTimeBeforeTask: config.waitingTimeBeforeTask};
3.
$scope.login = {};
4.
$scope.home = {};
5.
$scope.agenda = {};
6.
$scope.resa = {};
7.
// modle de la page courante
8.
var app = $scope.app;
9.
// [app.debug] et [utils.verbose] doivent toujours tre synchroniss
10.
app.debug = utils.verbose;
11.
app.debug.on = config.debug;
12.
// pas de titre de page pour l'instant
13.
app.titre = {show: false};
14.
// pas de barres de navigation
15.
app.navbarrun = {show: false};
16.
app.navbarstart = {show: false};
17.
// pas d'erreurs
18.
app.errors = {show: false};
19.
// locale par dfaut
20.
angular.copy(config.locales['fr'], $locale);
21.
// la vue courante
22.
app.view = {url: undefined, model: {}, done: false};
23.
// la tche courante
24. app.task = app.view.model.task = {action: utils.waitForSomeTime(app.waitingTimeBeforeTask),
isFinished: false};

ligne 8 : [$scope.app] sera le modle de la page matre. Ce sera aussi la mmoire partage entre les diffrents contrleurs.
Plutt que d'crire partout [$scope.app.champ=value], le pointeur [$scope.app] est affect la variable [app] et on crira
alors [app.champ=value]. Il faut simplement se souvenir que [app] est le modle expos la page matre ;
ligne 11 : [app.debug.on] est un boolen qui contrle le mode debug de l'application. Par dfaut il est true. Sa valeur est lie
la case cocher [debug] des barres de navigation ;
ligne 15 : [app.navbarrun.show] contrle l'affichage de la barre de navigation suivante :

ligne 16 : [app.navbarstart.show] contrle l'affichage de la barre de navigation suivante :

http://tahe.developpez.com

280/325

ligne 18 : [app.errors] est le modle du bandeau des erreurs ;

ligne 22 : [app.view] contiendra des informations sur la vue courante, celle qui est actuellement affiche par la balise [ngview] de la page matre. Nous y noterons les information suivantes :
[url] : l'URL de la vue courante, par exemple [/agenda] ;
[model] : le modle de la vue courante, par exemple [$scope.agenda] ;
[done] : vrai indique que la vue courante a termin son travail et qu'on est en train de passer une autre vue ;
Ces informations servent au contrle de la navigation.

ligne 24 : lance une tche asynchrone, une attente simule. La tche asynchrone est rfrence par deux pointeurs
[app.view.model.task.action] et [app.task] ;

Deux mthodes ont t factorises dans le contrleur [appCtrl] :


1.
2.
3. ...
4.
5.
6.
7.
8.
9. };

// annulation tche courante


app.cancel = function () {
};
// dconnexion
app.deconnecter = function () {
...

ligne 2 : la fonction [app.cancel] sert annuler la tche courante pour laquelle un message d'attente est actuellement
affich. Toutes les vues offrent ce message et donc l'annulation de la tche se fera ici ;
ligne 7 : la fonction [app.deconnecter] ramne l'utilisateur la page d'authentification. Toutes les vues, sauf la vue
[/login] offrent cette possibilit ;

La fonction [app.deconnecter] est la suivante :


1.
2.
3.
4.
5. };

3.8.8

// dconnexion
app.deconnecter = function () {
// on revient la page de login
$location.path(config.urlLogin);

ligne 4 : on revient la page de login d'URL [/login] ;

Gestion de la tche asynchrone

Dans notre application, un moment donn, une seule tche asynchrone sera en cours d'excution. Il est possible d'en avoir
plusieurs. Par exemple, au dmarrage de l'application, celle-ci demande au service web la liste des mdecins et puis celle des clients
avec deux requtes HTTP successives. On pourrait faire la mme chose avec deux requtes HTTP simultanes. Angular offre les
outils pour cette gestion. Ici, nous n'avons pas fait ce choix.
La tche en cours d'excution est annule avec le code suivant dans le contrleur [appCtrl] :
1.
2.
3.
4.
5.
6.
7.
8.

// annulation tche courante


app.cancel = function () {
utils.debug("[app] cancel task");
// on annule la tche asynchrone de la vue courante
var task = app.view.model.task;
task.isFinished = true;
task.action.reject();

http://tahe.developpez.com

281/325

9.
10. };

...

ligne 5 : la tche est cherche dans [app.view.model.task]. Aussi, tous les contrleurs feront en sorte que leurs tches
asynchrones soient rfrences par cet objet ;
ligne 6 : pour indiquer que la tche est finie ;
ligne 7 : pour terminer la tche avec un chec. Cette notation est diffrente de celle utilise dans les exemples Angular
tudis :
dans les exemples, l'objet [task] tait un objet [$q.defer()] qu'on pouvait terminer ;
dans la version finale, l'objet [task] est un objet avec les champs [action, isFinished] o [action] est l'objet [$q.defer()]
qu'on peut terminer et [isFinished] un boolen qui indique que l'action est termine ;

Examinons le cycle de vie de l'objet [task] sur un exemple. Au dmarrage, aprs le contrleur [appCtrl], c'est le contrleur
[loginCtrl] qui prend la main pour afficher la vue [views/login.html]. Son code d'initilisation est le suivant :
1.
// on rcupre le modle parent
2.
var login = $scope.login;
3.
var app = $scope.app;
4.
// vue courante
5. app.view = {url: config.urlLogin, model: login, done: false};

Ligne 5, on a [model=login]. Ceci signifie que lorsqu'on modifie l'objet [login], on modifie l'objet [app.view.model] donc
[$scope.app.view.model]. Lorsque dans le contrleur [loginCtrl], on veut faire une attente simule, on crit :
// attente simule
var task = login.task = {action: utils.waitForSomeTime(app.waitingTimeBeforeTask), isFinished: false};

En ajoutant le champ [task] l'objet [login], c'est donc l'objet [$scope.app.view.model] qu'il a t ajout. Si l'utilisateur annule
l'attente, le code dans [appCtrl.cancel] :
1.
2.
3.
4.
5.
6.

// modle de la page courante


var app = $scope.app;
...
var task = app.view.model.task;
task.isFinished = true;
task.action.reject();

va bien terminer l'attente simule (lignes 4-6).

3.8.9

Contrle de la navigation

Les rgles de navigation utilises dans l'application sont les suivantes :


URL cible URL prcdente

Navigation autorise

/login

quelconque

oui

/home

/login

oui si le contrleur [loginCtrl] a indiqu qu'il avait fini son travail

/home

oui

/agenda

oui

/home

oui si le contrleur [homeCtrl] a indiqu qu'il avait fini son travail

/resa

oui

/agenda

oui

/agenda

oui si le contrleur [homeCtrl] a indiqu qu'il avait fini son travail

/resa

oui

/agenda

/resa

Cela est implment avec le code suivant :


Pour [agendaCtrl] :

http://tahe.developpez.com

282/325

lignes 11-20 : implmentation de la rgle de navigation ;


ligne 26 : nouvelle vue courante ;

Pour [resaCtrl] :

lignes 12-20 : implmentation de la rgle de navigation :


ligne 27 : nouvelle vue courante ;

Pour [loginCtrl] :

il n'y a ici aucun contrle de navigation puisque la rgle dit qu'on peut venir l'URL [/login] de n'importe o. Donc si
l'utilisateur tape cette URL dans son navigateur, cela marchera quelque soit la vue courante du moment ;
ligne 16 : la nouvelle vue courante ;

http://tahe.developpez.com

283/325

Le code pour le contrleur [homeCtrl] a t donn page 281.


Enfin pour une rgle telle que :
/agenda

/home

oui si le contrleur [homeCtrl] a indiqu qu'il avait fini son travail

voici un exemple de code qui fait passer de l'URL [/home] l'URL [/agenda] :

Ci-dessus, on est dans la mthode [afficherAgenda] du contrleur [homeCtrl]. L'utilisateur a demand l'agenda d'un mdecin.

ligne 107 : la promesse de la tche HTTP ;


ligne 109 : la variable [app] a t initialise avec [$scope.app]. Ce dernier objet est, nous l'avons vu, utilis comme modle
de la vue [app.html]. Ce modle [$scope.app] est galement utilis pour stocker l'information qui doit tre partage entre
les vues ;
ligne 111 : on analyse le code d'erreur renvoy par la tche ;
ligne 113 : le rsultat [result.data] est mis dans le modle [app] ;
ligne 116 : le contrleur [homeCtrl] va passer la main au contrleur [agendaCtrl]. Il lui indique qu'il a termin son travail
avec le code de la ligne 115. Ce code va tre exploit par le contrleur [agendaCtrl] de la faon suivante :

ligne 11 : l'objet [$scope.app.view] est rcupr ;


ligne 15 : exploitation du champ [$scope.app.view.done] initialis par [homeCtrl] ;

http://tahe.developpez.com

284/325

3.8.10

Les services

Les services [config, utils, dao] sont ceux dj dcrits lors de la prsentation d'Angular :

le service [config] a t introduit au paragraphe 3.7.4, page 180 ;

le service [utils] a t introduit au paragraphe 3.7.5, page 184 ;

le service [dao] a t introduit au paragraphe 3.7.6, page 191 ;


Pour mmoire, on rappelle la structure de ces services :
Service [config]
2
1

en [1] : on voit que le code fait environ 250 lignes. L'essentiel de ce code est l'externalisation des cls des messages
internatiomaliss [2]. On vite de mettre ces cls en dur dans le code ;

Service [utils]

http://tahe.developpez.com

285/325

ligne 8 : nous n'avions pas encore rencontr la variable [verbose]. Elle contrle la fonction [debug] de la faon suivante :

lignes 22-25 : la fonction [utils.debug] ne fait rien si [verbose.on] est valu false. Cette variable est lie une variable du
contrleur [appCtrl] :

ligne 21 : [app.debug] prend la valeur du pointeur [utils.verbose]. Donc toute modification faite sur [app.debug] sera faite
galement sur [utils.verbose] ;
ligne 22 : la valeur initiale de [app.debug.on] est prise dans le fichier de configuration. Par dfaut, c'est la valeur true. Cette
valeur peut changer dans le temps. L'utilisateur a en effet la possibilit de la changer dans les barres de navigation :

http://tahe.developpez.com

286/325

ligne 45 : une case cocher (type=checkbox) permet de changer la valeur de [app.debug.on] (attribut ng-model) ;

Service [dao]

3.8.11

Les directives

Les directives [errors, footable, list, waiting] sont celles dj dcrites lors de la prsentation d'Angular :

http://tahe.developpez.com

287/325

la directive [footable] a t introduite page 233;


la directive [list] a t introduite au paragraphe 3.7.12, page 249 ;
les directives [errors] et [waiting] ont t introduites au paragraphe 3.7.14, page 258 ;

Nous n'avions pas rencontr la directive [debug]. C'est la suivante :

Le fichier [debug.html] rfrenc ligne 11 est le suivant :

ligne 2 : la directive [debug] affiche son modle au format JSON dans un bandeau Bootstrap (ligne 1) ;

Cette directive n'est utilise que dans la page matre [app.html] :

la directive [debug] est utilise ligne 35. Elle affiche donc la forme JSON du modle [$scope.app] lorsqu'on est en mode
debug (attribut ng-show). Cela donne des choses comme celle-ci :

http://tahe.developpez.com

288/325

Cela ncessite une bonne connaissance du code pour tre interprt mais lorsque celle-ci est acquise, l'information ci-dessus devient
utile pour le dbogage. On a surlign ici les lments du modle [$scope.app] affich. On rappelle que [$scope.app] est la mmoire
partage par les contrleurs ;

[waitingBeforeTask] : le temps d'attente simul avant toute requte HTTP ;


[debug] : le mode debug - est forcment true si ce bandeau est affich ;
[navbarrun] : boolen qui contrle l'affichage de la barre de navigation suivante :

[navbarstart] : boolen qui contrle l'affichage de la barre de navigation suivante :

[errors] : modle de la directive [errors] ;


[view] : encapsule des informations sur la vue actuellement affiche ;
[waiting] : modle de la directive [waiting] ;
[serverUrl, username, password] : les informations de connexion au service web ;
[medecins] : modle pour la directive [list] applique aux mdecins ;
[clients] : idem pour les clients ;
[menu] : contrle les options de menu affiches. Celles-ci sont dfinies dans [navbar-run.html] :

http://tahe.developpez.com

289/325

Les options de menu sont aux lignes 16, 23, 29 et 36.

3.8.12

[formattedJour] : le jour choisi dans le calendrier au format 'aaaa-mm-jj' ;


[agenda] : l'agenda du mdecin. Dans celui-ci, il y a des crneaux libres (rv==null) et rservs. Pour ces derniers, il y alors
le nom du client qui a rserv ;
[selectedCreneau] : le crneau horaire choisi pour faire une rservation ;

Le contrleur [loginCtrl]

Le contrleur [loginCtrl] est associ la vue [views/login.html] qui associe la page matre produit la page suivante :

http://tahe.developpez.com

290/325

Le contrleur [loginCtrl] est le suivant :

ligne 13 : [login] sera le modle de la vue courante ;


ligne 14 : [app] est la mmoire partage entre les contrleurs ;
ligne 16 : on renseigne [app.view] avec les informations de la vue courante ;

Ce code d'initialisation se retrouvera dans chaque contrleur. Pour le contrleur C1 d'une vue V1 ayant le modle M1 on aura le
code d'initialisation suivant :
1. var app=$scope.app;
2. var M1=$scope.M1;
3. app.view={url: config.urlV1, model:M1, done:false};

ligne 18 : on se rappelle peut-tre que [appCtrl] a lanc une attente simule rfrence par l'objet [app.task.action]. On
utilise la [promise] de cette tche pour attendre sa fin ;
ligne 39 : la mthode [login.setLang] gre le changement de langues ;
ligne 47 : la mthode [login.authenticate] gre l'authentification de l'utilisateur ;

Regardons les grands mouvements de la mthode d'authentification :

http://tahe.developpez.com

291/325

lignes 50-51 : [app.waiting] est le modle du bandeau d'attente ;


ligne 53 : [app.errors] est le modle du bandeau d'erreurs ;
ligne 55 : une attente simule est lance. L'objet [action, isFinished] est rfrenc par [login.task] et donc puisque
[app.view.model=login], par [app.view.model.task]. On rappelle que c'est la condition pour que la tche puisse tre
annule ;
ligne 57 : aprs la fin de l'attente simule, on charge les mdecins ;
ligne 62 : lorsque la demande des mdecins a t obtenue, on analyse cette demande. Si les mdecins ont t obtenus, on
demande alors les clients ;
ligne 83 : on analyse la rponse obtenue et on affiche la vue finale. Cela se fait avec le code suivant :

ligne 87 : le boolen [task.isFinished] est positionn true dans les cas suivants :
l'utilisateur a annul l'attente ;
la demande des mdecins s'est termine avec une erreur ;
lignes 91-98 : le cas o on a eu les clients ;
ligne 93 : [app.clients] est le modle de la directive [list] qui va afficher les clients dans une liste droulante ;
lignes 97-98 : on se prpare changer de vue (ligne 98) mais auparavant on indique que le contrleur a termin son travail
(ligne 97). On rappelle que [$scope.app.view.done] est utilis pour le contrle de navigation ;

Le point important noter ici est que les mdecins et les clients ont t mis en cache sur le navigateur. Ils ne seront dsormais plus
demands au service web.

http://tahe.developpez.com

292/325

3.8.13

Le contrleur [homeCtrl]

Le contrleur [homeCtrl] est associ la vue [views/home.html] qui associe la page matre produit la page suivante :

La structure du contrleur [homeCtrl] est la suivante :

http://tahe.developpez.com

293/325

lignes 12-20 : c'est le contrle de navigation. Tous les contrleurs l'ont sauf [loginCtrl] car la page [/login.html] est
accessible sans conditions ;

lignes 25-28 : on retrouve l des lignes analogues celles rencontres dans le contrleur [loginCtrl]. [home] est ainsi le
modle de la vue associe au contrleur ;
ligne 33 : un attribut que nous n'avions pas encore rencontr. C'est le modle du bandeau de titre de la vue :

ligne 36 : [home.datepicker] est le modle du calendrier ;


ligne 38 : [app.menu] est le modle du menu de la barre de navigation. Ici l'option [Agenda] sera prsente. C'est elle qui
permet de demander l'agenda d'un mdecin ;

Enfin, le contrleur a deux mthodes :

L'affichage de l'agenda (ligne 51) a t trait au paragraphe 3.7.8, page 225.

http://tahe.developpez.com

294/325

3.8.14

Le contrleur [agendaCtrl]

Le contrleur [agendaCtrl] est associ la vue [views/agenda.html] qui associe la page matre produit la page suivante :

La structure du contrleur [agendaCtrl] est la suivante :

les lignes 10-20 assurent le contrle de navigation ;

http://tahe.developpez.com

295/325

lignes 23-26 : [agenda] sera le modle de la vue associe au contrleur [agendaCtrl] ;


lignes 36-44 : [app.titre] est le modle du bandeau de titre suivant :

ligne 46 : le menu aura l'option [Home / Accueil] :

Les mthodes du contrleur sont les suivantes :

ligne 95 : la mthode [agenda.supprimer] a t traite au paragraphe 3.7.9, page 234 ;

La mthode [agenda.home] est une mthode de pure navigation :

http://tahe.developpez.com

296/325

La mthode [agenda.reserver] est la suivante :

3.8.15

ligne 73 : le paramtre de la fonction [reserver] est le n du crneau (id) ;


lignes 77-86 : visent retrouver le crneau horaire ayant cet identifiant ;
ligne 82 : le crneau trouv est mis dans la mmoire partage [app]. Le contrleur [resaCtrl] qui va prendre la main (ligne
90) va exploiter cette information pour afficher son bandeau de titre ;
lignes 89-90 : navigation vers [/resa.html] ;

Le contrleur [resaCtrl]

Le contrleur [resaCtrl] est associ la vue [views/resa.html] qui associe la page matre produit la page suivante :

http://tahe.developpez.com

297/325

La structure du contrleur [resaCtrl] est la suivante :

lignes 12-20 : le contrle de navigation ;

http://tahe.developpez.com

298/325

lignes 24-27 : [resa] sera le modle de la vue courante ;


lignes 38-45 : [app.titre] est le modle du bandeau de titre suivant :

ligne 47 : deux options de menu sont affiches :

Les mthodes du contrleur sont les suivantes :

La mthode [resa.valider] a t tudie au paragraphe 3.7.9, page 234.

3.8.16

La gestion des langues

Tous les contrleurs offrent la mthode [setLang] suivante :

Elle aurait pu tre factorise dans le contrleur [appCtrl].

http://tahe.developpez.com

299/325

4 Exploitation de l'application
Nous souhaitons maintenant exploiter l'application en-dehors des IDE STS (pour le serveur) et Webstorm (pour le client).

4.1

Dploiement du service web sur un serveur Tomcat

Nous avons vu au paragraphe 2.11.9, page 71, comment crer une archive war pour Tomcat. Nous rptons l'opration ici. Tout
d'abord, pour prserver l'existant, nous dupliquons le projet Eclipse [rdvmedecins-webapi-v3] dans [rdvmedecins-webapi-v4].

Le fichier [pom.xml] est modifi de la faon suivante :


1.
<modelVersion>4.0.0</modelVersion>
2.
<groupId>istia.st.spring4.mvc</groupId>
3.
<artifactId>rdvmedecins-webapi-v4</artifactId>
4.
<version>0.0.1-SNAPSHOT</version>
5.
<packaging>war</packaging>
6.
7.
<name>rdvmedecins-webapi-v3</name>
8.
<description>Gestion de RV Mdecins</description>
9.
<parent>
10.
<groupId>org.springframework.boot</groupId>
11.
<artifactId>spring-boot-starter-parent</artifactId>
12.
<version>1.0.0.RELEASE</version>
13.
</parent>
14.
<dependencies>
15.
<dependency>
16.
<groupId>org.springframework.boot</groupId>
17.
<artifactId>spring-boot-starter-web</artifactId>
18.
</dependency>
19.
<dependency>
20.
<groupId>org.springframework.boot</groupId>
21.
<artifactId>spring-boot-starter-security</artifactId>
22.
</dependency>
23.
<dependency>
24.
<groupId>org.springframework.boot</groupId>
25.
<artifactId>spring-boot-starter-tomcat</artifactId>
26.
<scope>provided</scope>
27.
</dependency>
28.
<dependency>
29.
<groupId>istia.st.spring4.rdvmedecins</groupId>
30.
<artifactId>rdvmedecins-metier-dao-v2</artifactId>
31.
<version>0.0.1-SNAPSHOT</version>
32.
</dependency>
33. </dependencies>

Les modifications sont faire deux endroits :

ligne 5 : il faut indiquer qu'on va gnrer une archive war (Web ARchive) ;
lignes 23-27 : il faut ajouter une dpendance sur l'artifact [spring-boot-starter-tomcat]. Cet artifact amne toutes les classes
de Tomcat dans les dpendances du projet ;
ligne 26 : cet artifact est [provided], --d que les archives correspondantes ne seront pas places dans le war gnr. En
effet, ces archives seront trouves sur le serveur Tomcat sur lequel s'excutera l'application ;

http://tahe.developpez.com

300/325

Il faut par ailleurs configurer l'application web. En l'absence de fichier [web.xml], cela se fait avec une classe hritant de
[SpringBootServletInitializer] :

La classe [ApplicationInitializer] est la suivante :


1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.

package rdvmedecins.web.config;

ligne 6 : la classe [ApplicationInitializer] tend la classe [SpringBootServletInitializer] ;


ligne 8 : la mthode [configure] est redfinie (ligne 7) ;
ligne 9 : on fournit la classe [AppConfig] qui configure le projet ;

import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;
public class ApplicationInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(AppConfig.class);
}
}

Ceci fait, il peut tre ncessaire de mettre jour le projet Maven (j'ai du le faire) : [clic droit sur projet / Maven / Update project] ou
[Alt-F5].
Pour excuter le projet, on peut procder ainsi :

en [1], on excute le projet sur l'un des serveurs enregistrs dans l'IDE Eclipse ;
en [2], on choisit [tc Server Developer] qui est prsent par dfaut. C'est une variante de Tomcat ;

On obtient le rsultat suivant :

http://tahe.developpez.com

301/325

C'est normal. Rappelons que le service web n'a pas URL [/] dans ses mthodes. Lorsqu'on essaie l'URL [/getAllMedecins], on a la
rponse suivante :

C'est normal. Le service web est protg.


Maintenant lanons le client [rdvmedecins-angular-v2] dans Webstorm :

En [1], on met l'URL du nouveau service web [http://localhost:8080/rdvmedecins-webapi-v4]. On obtient le rsultat suivant :

http://tahe.developpez.com

302/325

Pour excuter l'application en-dehors de l'IDE STS, il existe diverses solutions. En voici une.
Tlchargez une version de Tomcat [http://tomcat.apache.org/download-80.cgi] (juillet 2014) :
2

On choisit en [1] une version zippe qu'on dzippe en [2]. On revient dans STS :

http://tahe.developpez.com

303/325

dans l'onglet [Servers], on clique droit sur l'application [rdvmedecins-webapi-v4] et on slectionne l'option [Browse
Deployment Location] ;
en [4] : on copie le dossier [rdvmedecins-webapi-v4] ;

en [5], on colle le dossier [rdvmedecins-webapi-v4] dans le dossier [webapps] de Tomcat ;


en [6], on excute le fichier de commande [startup.bat] (le serveur Tomcat intgr STS doit lui tre arrt). Une fentre
DOS s'ouvre pour afficher les logs de Tomcat. Ils doivent montrer que l'application [rdvmedecins-webapi-v4] a t lance.

Pour le vrifier, on excute de nouveau le client Angular [rdvmedecins-angular-v2] dans Webstorm :

En [1], on met l'URL du nouveau service web [http://localhost:8080/rdvmedecins-webapi-v4]. On obtient le rsultat suivant :

http://tahe.developpez.com

304/325

4.2

Dploiement du client Angular sur le serveur Tomcat

Maintenant que le service web a t dploy sur Tomcat, nous allons maintenant dployer le client Angular sur un serveur lui aussi.
Ce peut trs bien tre le serveur qui hberge dj le service web. Nous prenons cette voie.
Tout d'abord nous dupliquons le client [rdvmedecins-angular-v2] dans [rdvmedecins-angular-v3] et nous faisons les modifications
suivantes :

en [1], tout a t dplac dans un dossier [app] ;


en [1], on a supprim le dossier [bower-components] qui contenait les diverses bibliothques CSS et JS ncessaires au
projet. Tous ces lments ont t recopis dans le dossier [lib] [2] ;
en [1], le fichier [app.html] a t renomm [index.html] ;

http://tahe.developpez.com

305/325

Le fichier [index.html] a t modifi pour prendre en compte les changements de chemin des ressources utilises :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.

<!DOCTYPE html>
<html ng-app="rdvmedecins">
<head>
<title>RdvMedecins</title>
...
<!-- le CSS -->
...
<link href="lib/bootstrap-theme.min.css" rel="stylesheet"/>
<link href="lib/bootstrap-select.min.css" rel="stylesheet"/>
</head>
<!-- contrleur [appCtrl], modle [app] -->
<body ng-controller="appCtrl">
<div class="container">
...
</div>
<!-- Bootstrap core JavaScript ================================================== -->
<script type="text/javascript" src="lib/jquery.min.js"></script>
<script type="text/javascript" src="lib/bootstrap.min.js"></script>
<script type="text/javascript" src="lib/bootstrap-select.min.js"></script>
<script type="text/javascript" src="lib/footable.js"></script>
<!-- angular js -->
<script type="text/javascript" src="lib/angular.min.js"></script>
<script type="text/javascript" src="lib/ui-bootstrap-tpls.min.js"></script>
<script type="text/javascript" src="lib/angular-route.min.js"></script>
<script type="text/javascript" src="lib/angular-translate.min.js"></script>
<script type="text/javascript" src="lib/angular-base64.min.js"></script>
<!-- modules -->
...
<!-- services -->
...
<!-- directives -->
...
<!-- controllers -->
....
</body>

</html>

Par ailleurs, le contrleur [loginCtrl] a t modifi pour pointer sur le bon serveur afin d'viter l'utilisateur de taper son URL :
1.
2.
3.
4.

// credentials
app.serverUrl = "http://localhost:8080/rdvmedecins-webapi-v4";
app.username = "admin";
app.password = "admin";

Ceci fait, excutons le fichier [index.html] :

http://tahe.developpez.com

306/325

Puis connectons-nous au service web. Ca doit marcher. Ceci vrifi, arrtons le serveur Tomcat. Nous allons rutiliser le serveur
intgr de STS.
Dans STS, copions tout le contenu du dossier [rdvmedecins-angular-v3/app] dans le dossier [webapp] du projet [rdvmedecinswebapi-v4] (onglet Navigator) [1] :

2
1

Ceci fait, lanons [2], le serveur VMware de STS, puis demandons l'URL [http://localhost:8080/rdvmedecins-webapiv4/app/index.html] :

http://tahe.developpez.com

307/325

On a un problme de droits en [3]. Ce n'est pas tonnant car on a protg le service web. Il faut qu'on dclare que l'accs au fichier
[/app/index.html] est libre. Revenons dans Eclipse :

On se rappelle que les droits d'accs ont t dclars dans la classe [SecurityConfig]. Modifions celle-ci de la faon suivante :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17. }

@Override
protected void configure(HttpSecurity http) throws Exception {
// CSRF
http.csrf().disable();
// le mot de passe est transmis par le header Authorization: Basic xxxx
http.httpBasic();
// la mthode HTTP OPTIONS doit tre autorise pour tous
http.authorizeRequests() //
.antMatchers(HttpMethod.OPTIONS, "/", "/**").permitAll();
// le dossier [app] est accessible tous
http.authorizeRequests() //
.antMatchers(HttpMethod.GET, "/app", "/app/**").permitAll();
// seul le rle ADMIN peut utiliser l'application
http.authorizeRequests() //
.antMatchers("/", "/**") // toutes les URL
.hasRole("ADMIN");

lignes 11-12 : on autorise tout le monde lire le dossier [app] et son contenu. On s'inspire, pour le faire, des lignes
prcdentes.

http://tahe.developpez.com

308/325

Maintenant, relanons le serveur Tomcat de STS puis demandons de nouveau l'URL [http://localhost:8080/rdvmedecins-webapiv4/app/index.html] :

Cette fois-ci, c'est bon.

4.3

Les enttes CORS

On se souvient peu-tre que nous avons bataill dur pour grer les enttes CORS. Dans l'exemple prcdent :

le service web est l'URL [http://localhost:8080/rdvmedecins-webapi-v4];

le client HTML est l'URL [http://localhost:8080/rdvmedecins-webapi-v4/app];


Le client HTML et le service web sont donc sur le mme serveur [http://localhost:8080]. Il n'y a alors pas de conflits CORS car
ceux-ci n'interviennent que lorsque le client et le serveur ne sont pas dans le mme domaine. On devrait pouvoir le vrifier. Nous
revenons dans STS :

La gnration ou non des enttes CORS est contrle par un boolen dfini dans la classe [ApplicationModel] :
// donnes de configuration
private boolean CORSneeded = true;

Nous passons le boolen ci-dessus false, nous relanons le service web et nous redemandons
[http://localhost:8080/rdvmedecins-webapi-v4/app/index.html]. On constate que l'application fonctionne.

4.4

l'URL

Dploiement du client Angular sur une tablette Android

L'outil [Phonegap] [http://phonegap.com/] permet de produire un excutable pour mobile (Android, IoS, Windows 8, ...) partir
d'une application HTML / JS / CSS. Il y a diffrentes faons d'arriver ce but. Nous utilisons le plus simple : un outil prsent en
ligne sur le site de Phonegap [http://build.phonegap.com/apps].

http://tahe.developpez.com

309/325

avant [1], vous aurez peut-tre crer un compte ;


en [1], on dmarre ;
en [2], on choisit un plan gratuit n'autorisant qu'une application Phonegap ;
3

en [3], on tlcharge l'application zippe [4] (le dossier [app] cr page 305 est zipp) ;

5
6

en [5], on donne un nom l'application ;


en [6], on la construit. Cette opration peut prendre 1 minute. Patientez jusqu' ce que les icnes des diffrentes plateformes mobiles indiquent que la construction est termine ;

http://tahe.developpez.com

310/325

seuls les binaires Android [7] et Windows [8] ont t gnrs ;


on clique sur [7] pour tlcharger le binaire d'Android ;

en [9] le binaire [apk] tlcharg ;

Lancez un mulateur [GenyMotion] pour une tablette Android (voir paragraphe 6.4, page 323) :

Ci-dessus, on lance un mulateur de tablette avec l'API 16 d'Android. Une fois l'mulateur lanc,

dverrouillez-le en tirant le verrou (s'il est prsent) sur le ct puis en le lchant ;

avec la souris, tirez le fichier [PGBuildApp-debug.apk] que vous avez tlcharg et dposez-le sur l'mulateur. Il va tre
alors install et excut ;

http://tahe.developpez.com

311/325

Il faut changer l'URL en [1]. Pour cela, dans une fentre de commande, tapez la commande [ipconfig] (ligne 1 ci-dessous) qui va
afficher les diffrentes adresses IP de votre machine :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.

C:\Users\Serge Tah>ipconfig
Configuration IP de Windows
Carte rseau sans fil Connexion au rseau local* 15 :
Statut du mdia. . . . . . . . . . . . : Mdia dconnect
Suffixe DNS propre la connexion. . . :
Carte Ethernet Connexion au rseau local :
Suffixe DNS propre la connexion. . . :
Adresse IPv6 de liaison locale. . . . .:
Adresse IPv4. . . . . . . . . . . . . .:
Masque de sous-rseau. . . . . . . . . :
Passerelle par dfaut. . . . . . . . . :

ad.univ-angers.fr
fe80::698b:455a:925:6b13%4
172.19.81.34
255.255.0.0
172.19.0.254

Carte rseau sans fil Wi-Fi :


Statut du mdia. . . . . . . . . . . . : Mdia dconnect
Suffixe DNS propre la connexion. . . :
...

Notez soit l'adresse IP Wifi (lignes 6-9), soit l'adresse IP sur le rseau local (lignes 11-17). Puis utilisez cette adresse IP dans l'URL
du serveur web :

Ceci fait, connectez-vous au service web :

http://tahe.developpez.com

312/325

Testez l'application sur l'mulateur. Elle doit fonctionner. Ct serveur, on peut ou non autoriser les enttes CORS dans la classe
[ApplicationModel] :
// donnes de configuration
private boolean CORSneeded = false;

Cela n'a pas d'importance pour l'application Android. Celle-ci ne s'excute pas dans un navigateur. Or l'exigence des enttes CORS
vient du navigateur et non pas du serveur.

4.5

Dploiement du client Angular sur l'mulateur d'un smartphone Android

On rpte l'opration prcdente avec un mulateur pour smartphone. On veut vrifier comment se comporte notre client sur des
petits crans :

http://tahe.developpez.com

313/325

en [1], on lance un mulateur de smartphone ;


en [2] et [3], la barre de navigation a t replie dans un menu ;

en [4], on se connecte ;
en [5], la liste et le calendrier sont l'un sous l'autre au lieu d'tre l'un ct de l'autre ;

http://tahe.developpez.com

314/325

en [6], on demande l'agenda ;


en [7], l'cran tant trop petit, les crneaux ont une partie cache. C'est la bibliothque [footable] qui a fait ce travail ;

en [8], la mme vue que prcdemment avec cette fois, un rendez-vous.

Au final, notre application s'adapte plutt bien au smartphone. Cela pourrait tre srement mieux mais cela reste utilisable.

http://tahe.developpez.com

315/325

5 Conclusion
Nous avons construit l'application client / serveur suivante :

Pour arriver la version finale du code, nous avons du expliquer de nombreux points des frameworks AngularJS et Spring 4. Ce
document peut donc tre utilis pour se former l'utilisation de ces deux frameworks. Le paragraphe 1.3, page 7, explique o
trouver les codes et comment les exploiter.
Nous avons montr que l'application client / serveur tait utilisable dans divers environnements :

comme une application web classique ;


comme un binaire excutable sur des mulateurs Android ;

Encore une fois, ce tutoriel n'est pas exhaustif quant l'tude des deux frameworks. Pour Angular, il faudrait certainement
prsenter les outils de tests qui l'accompagnent. Les tests sont des tapes indispensables lors de l'criture d'une application. Les
outils qui gravitent autour d'Angular permettent de les automatiser et des inclure dans un processus d'intgration continue.
De ce travail, je retiendrai deux points :

l'criture du service web Spring a t moyennement complique. Ds le dpart, je connaissais bien les concepts de Spring.
Je n'ai rencontr de difficults qu'avec la scurisation du service web puis plus tard avec la gestion des enttes HTTP
CORS, deux domaines que je ne connaissais pas ;
l'criture du client Angular a t beaucoup plus complexe pour diffrentes raisons :
j'avais une connaissance insuffisante du langage Javascript et de ses possibilits ;
j'ai eu du mal comprendre comment fonctionnait la programmation asynchrone au sein du navigateur. Je raisonnais
comme sur un serveur o cet asynchronisme est obtenu avec l'utilisation simultane de plusieurs threads. Dans le
navigateur, il n'y a qu'un thread, et les tches asynchrones sont traites successivement et non pas en parallle. Plus
prcisment, des tches asynchrones peuvent s'excuter en parallle (requtes HTTP multiples par exemple) mais les
vnements qu'elles produisent lorsqu'elles sont termines, sont eux traits squentiellement. Il n'y a donc pas
d'exccution concurrente grer avec les nombreux problmes qui vont avec ;

http://tahe.developpez.com

316/325

Angular est un framework riche avec de nombreuses notions (MVC, directives, services, porte des modles, ...). Son
apprentissage est long ;
Angular n'impose pas de mthode de dveloppement. Ainsi pour arriver un mme rsultat, on peut utiliser
diffrentes architectures. C'est droutant. Je suis plus l'aise avec des frameworks ferms o tout le monde utilise les
mmes patrons de conception (design pattern). J'ai donc constamment cherch reproduire les modles de
conception que j'utilise cte serveur. Je suis satisfait du rsultat car je pense qu'il est reproductible. C'est ce que je
cherchais. Mais je ne sais pas du tout si je me suis cart ou non des bonnes pratiques d'Angular ;

Serge Tah, juillet 2014.

http://tahe.developpez.com

317/325

6 Annexes
Nous prsentons ici comment installer les outils utiliss dans ce document sur des machines windows 7.

6.1

Installation de STS (Spring Tool Suite)

Nous allons installer SpringSource Tool Suite [http://www.springsource.com/developer/sts], un Eclipse pr-quip avec de
nombreux plugins lis au framework Spring et galement avec une configuration Maven pr-installe.

2A

aller sur le site de SpringSource Tool Suite (STS) [1], pour tlcharger la version courante de STS [2A] [2B],

2B
3A

http://tahe.developpez.com

318/325

3B
5

le fichier tlcharg est un installateur qui cre l'arborescence de fichiers [3A] [3B]. En [4], on lance l'excutable,
en [5], la fentre de travail de l'IDE aprs avoir ferm la fentre de bienvenue. En [6], on fait afficher la fentre des
serveurs d'applications,

en [7], la fentre des serveurs. Un serveur est enregistr. C'est un serveur VMware compatible Tomcat.

L'utilisation de STS dans le cadre de l'application est explique au paragraphe 1.3.2, page 9.

6.2

Installation de [WampServer]

[WampServer] est un ensemble de logiciels pour dvelopper en PHP / MySQL / Apache sur une machine Windows. Nous
l'utiliserons uniquement pour le SGBD MySQL.

sur le site de [WampServer] [1], choisir la version qui convient [2],


l'excutable tlcharg est un installateur. Diverses informations sont demandes au cours de l'installation. Elles ne
concernent pas MySQL. On peut donc les ignorer. La fentre [3] s'affiche la fin de l'installation. On lance [WampServer],

http://tahe.developpez.com

319/325

4
5

en [4], l'icne de [WampServer] s'installe dans la barre des tches en bas et droite de l'cran [4],
lorsqu'on clique dessus, le menu [5] s'affiche. Il permet de grer le serveur Apache et le SGBD MySQL. Pour grer celuici, on utiliser l'option [PhpPmyAdmin],
on obtient alors la fentre ci-dessous,

Nous donnerons peu de dtails sur l'utilisation de [PhpMyAdmin]. Nous montrons au paragraphe 1.3.1, page 8, comment l'utiliser
pour crer la base de donnes de l'application.

6.3

Installation de [Webstorm]

[WebStorm] (WS) est l'IDE de JetBrains pour dvelopper des applications HTML / CSS / JS. Je l'ai trouv parfait pour dvelopper
des applications Angular. Le site de tlchargement est [http://www.jetbrains.com/webstorm/download/]. C'est un IDE payant
mais une version d'valuation de 30 jours est tlchargeable. Il existe une version personnelle et une version tudiante peu
onreuses.
Son utilisation dans le cadre de l'application est dcrite au paragraphe 1.3.3, page 11. Pour installer des bibliothques JS au sein
d'une application, WS utilise un outil appel [bower]. Cet outil est un module de [node.js], un ensemble de bibliothques JS. Par
ailleurs, les bibliothques JS sont cherches sur un site Git, ncessitant un client Git sur le poste qui tlcharge.

http://tahe.developpez.com

320/325

6.3.1

Installation de [node.js]

Le site de tlchargement de [node.js] est [http://nodejs.org/]. Tlchargez l'installateur puis excutez-le. Il n'y a rien de plus faire
pour le moment.

6.3.2

Installation de l'outil [bower]

L'installation de l'outil [bower] qui va permettre le tlchargement des bibliothques Javascript peut se faire de diffrentes faons.
Nous allons la faire partir de la console :
1. C:\Users\Serge Tah>npm install -g bower
2. C:\Users\Serge Tah\AppData\Roaming\npm\bower -> C:\Users\Serge
Tah\AppData\Roaming\npm\node_modules\bower\bin\bower
3. bower@1.3.7 C:\Users\Serge Tah\AppData\Roaming\npm\node_modules\bower
4. stringify-object@0.2.1
5. is-root@0.1.0
6. junk@0.3.0
7. ...
8. insight@0.3.1 (object-assign@0.1.2, async@0.2.10, lodash.debounce@2.4.1, req
9. uest@2.27.0, configstore@0.2.3, inquirer@0.4.1)
10. mout@0.9.1
11. inquirer@0.5.1 (readline2@0.1.0, mute-stream@0.0.4, through@2.3.4, async@0.8
12. .0, lodash@2.4.1, cli-color@0.3.2)

6.3.3

ligne 1 : la commande [node.js] qui installe le module [bower]. Pour que la commande marche, il faut que l'excutable
[npm] soit dans le PATH de la machine (voir paragraphe ci-aprs) ;

Installation de [Git]

Git est un systme de gestion de versions de logiciel. Il existe une version windows appele [msysgit] et disponible l'URL
[http://msysgit.github.io/]. Nous n'allons pas utiliser [msysgit] pour grer des versions de notre application mais simplement pour
tlcharger des bibliothques JS qui se trouvent sur des sites de type [https://github.com] qui ncessitent un protocole d'accs
spcial et qui est fourni par le client [msysgit]
L'assistant d'installation propose diffrentes tapes dont les suivantes :

Pour les autres tapes de l'installation, vous pouvez accepter les valeurs par dfaut proposes.
Une fois, l'installation de Git termine, vrifiez que l'excutable est dans le PATH de votre machine : [Panneau de configuration /
Systme et scurit / Systme / Paramtres systmes avancs] :

http://tahe.developpez.com

321/325

La variable PATH ressemble ceci :


D:\Programs\devjava\java\jdk1.7.0\bin;D:\Programs\ActivePerl\Perl64\site\bin;D:\Programs\ActivePerl\Per
l64\bin;D:\Programs\sgbd\OracleXE\app\oracle\product\11.2.0\client;D:\Programs\sgbd\OracleXE\app\oracle
\product\11.2.0\client\bin;D:\Programs\sgbd\OracleXE\app\oracle\product\11.2.0\server\bin;...;D:\Progra
ms\javascript\node.js\;D:\Programs\utilitaires\Git\cmd

Vrifiez que :

le chemin du dossier d'installation de [node.js] est bien prsent (ici D:\Programs\javascript\node.js) ;

le chemin de l'excutable du client Git est bien prsent (ici D:\Programs\utilitaires\Git\cmd) ;

6.3.4

Configuration de [Webstorm]

Vrifions maintenant la configuration de [Webstorm]

http://tahe.developpez.com

322/325

Ci-dessus, slectionnez l'option [1]. La liste des modules [node.js] dj installs apparat en [2]. Cette liste ne devrait contenir que la
ligne [3] du module [bower] si vous avez suivi le processus d'installation prcdent.

6.4

Installation d'un mulateur pour Android

Les mulateurs fournis avec le SDK d'Android sont lents ce qui dcourage de les utiliser. L'entreprise [Genymotion] offre un
mulateur beaucoup plus performant. Celui-ci est disponible l'URL [https://cloud.genymotion.com/page/launchpad/download/]
(fvrier 2014).
Vous aurez vous enregistrer pour obtenir une version usage personnel. Tlchargez le produit [Genymotion] avec la machine
virtuelle VirtualBox ;

Installez puis lancez [Genymotion]. Tlchargez ensuite une image pour une tablette ou un tlphone :

http://tahe.developpez.com

323/325

3
4
2

en [1], ajoutez un terminal virtuel ;


en [2], choisissez un ou plusieurs terminaux installer. Vous pouvez affiner la liste affiche en prcisant la version
d'Android dsire [3] ainsi que le modle de terminal [4] ;

6.5

une fois le tlchargement termin, vous obtenez en [5] la liste des terminaux virtuels dont vous disposez pour tester vos
applications Android ;

Installation du plugin Chrome [Advanced Rest Client]

Dans ce document, on utilise le navigateur Chrome de Google (http://www.google.fr/intl/fr/chrome/browser/ ). On lui ajoutera


l'extension [Advanced Rest Client]. On pourra procder ainsi :

aller sur le site de [Google Web store] (https://chrome.google.com/webstore) avec le navigateur Chrome ;
chercher l'application [Advanced Rest Client] :

l'application est alors disponible au tlchargement :

http://tahe.developpez.com

324/325

pour l'obtenir, il vous faudra crer un compte Google. [Google Web Store] demande ensuite confirmation [1] :

en [2], l'extension ajoute est disponible dans l'option [Applications] [3]. Cette option est affiche sur chaque nouvel onglet
que vous crez (CTRL-T) dans le navigateur.

http://tahe.developpez.com

325/325

Vous aimerez peut-être aussi