XX. Améliorations du client Vue.js▲
XX-A. Introduction▲
Nous allons tester le projet [vuejs-21] avec le serveur de développement. Nous allons donc avoir besoin de nouveau que le serveur envoie les entêtes CORS. Il faut donc que le fichier [config.json] de la version 14 du serveur de calcul de l’impôt autorise ces entêtes :
Le projet [vuejs-21] est créé initialement par duplication du projet [vuejs-20]. Il est ensuite modifié [3].
De nouveaux fichiers apparaissent :
- [session.js] : exporte un objet [session] qui va encapsuler des informations sur la session courante ;
- [pluginSession] : rend disponible l’objet [session] précédent dans la propriété [$session] des vues ;
- [NotFound.vue] : une nouvelle vue affichée lorsque l’utilisateur demande manuellement une URL qui n’existe pas ;
Des fichiers seront modifiés :
- [main.js] : va initialiser la session courante puis, lorsque l’utilisateur va taper manuellement des URL va la restaurer ;
- [router.js] : des contrôles sont ajoutés pour traiter le cas des URL tapées par l’utilisateur ;
- [store.js] : une nouvelle mutation est ajoutée ;
- [config.js] : une nouvelle configuration est ajoutée ;
- différentes vues essentiellement pour sauver la session courante à des moments clés de la vie de l’application. Celle-ci est ensuite restaurée à chaque fois que l’utilisateur tape manuellement des URL ;
XX-B. Le store [Vuex]▲
Le script [./store] évolue de la façon suivante :
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.
// plugin Vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use
(
Vuex);
// store Vuex
const store =
new Vuex.Store
({
state
:
{
// le tableau des simulations
simulations
:
[],
// le n° de la dernière simulation
idSimulation
:
0
},
mutations
:
{
// suppression ligne n° index
deleteSimulation
(
state,
index) {
...
},
// ajout d'une simulation
addSimulation
(
state,
simulation) {
...
},
// nettoyage state
clear
(
state) {
// plus de simulations
state.
simulations =
[];
// la numérotation des simulations repart de 0
state.
idSimulation =
0
;
}
}
}
);
// export de l'objet [store]
export default store;
- lignes 24-29 : la mutation [clear] supprime la liste des simulations enregistrées et remet à 0 le n° de la dernière simulation.
XX-C. La session▲
Le besoin d’une session vient du fait que lorsque l’utilisateur tape une URL dans le champ adresse du navigateur, le script [main.js] est exécuté de nouveau. Or celui-ci contient l’instruction :
Cette instruction importe le fichier [./store] suivant :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
// plugin Vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use
(
Vuex);
// store Vuex
const store =
new Vuex.Store
({
state
:
{
// le tableau des simulations
simulations
:
[],
// le n° de la dernière simulation
idSimulation
:
0
},
mutations
:
{
...
}
}
);
// export de l'objet [store]
export default store;
On voit, lignes 7-13, qu’on importe un tableau de simulations vide. Si donc on avait des simulations avant que l’utilisateur ne tape une URL dans le champ adresse du navigateur, après on n’en a plus. L’idée est :
- d’utiliser une session qui stockerait les informations qu’on veut conserver si l’utilisateur tape manuellement des URL ;
- de la sauvegarder à des moments clés de l’application ;
- de la restaurer dans [main.js] qui est toujours exécuté lorsqu’une URL est tapée manuellement ;
Le script [./session] est le suivant :
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.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
// on importe le store Vuex
import store from './store'
// on importe la configuration
import config from './config'
;
// l'objet [session]
const session =
{
// session démarrée
started
:
false,
// authentification
authenticated
:
false,
// heure de sauvegarde
saveTime
:
""
,
// couche [métier]
métier:
null,
// état Vuex
state
:
null,
// sauvegarde de la session dans une chaîne jSON
save
(
) {
// on ajoute à la session quelques proprités
this.
saveTime =
Date.now
(
);
this.
state =
store.
state;
// on la transforme en jSON
const json =
JSON.stringify
(
this);
// on la stocke sur le navigateur
localStorage.setItem
(
"session"
,
json);
// eslint-disable-next-line no-console
console.log
(
"session save"
,
json);
},
// restauration de la session
restore
(
) {
// on récupère la session jSON à partir du navigateur
const json =
localStorage.getItem
(
"session"
)
// si on a récupéré qq chose
if (
json) {
// on restaure toutes les clés de la session
const restore =
JSON.parse
(
json);
for (
var key in restore) {
if (
restore.hasOwnProperty
(
key)) {
this[
key]
=
restore[
key];
}
}
// si on a dépassé une certaine durée d'inactivité depuis le début de la session, on repart de zéro
let durée =
Date.now
(
) -
this.
saveTime;
if (
durée >
config.
duréeSession) {
// on vide la session - elle sera également sauvegardée
session.clear
(
);
}
else {
// on régénère le store Vuex
store.replaceState
(
JSON.parse
(
JSON.stringify
(
this.
state)));
}
}
// eslint-disable-next-line no-console
console.log
(
"session restore"
,
this);
},
// on nettoie la session
clear
(
) {
// eslint-disable-next-line no-console
console.log
(
"session clear"
);
// raz de certains champs de la session
this.
authenticated =
false;
this.
saveTime =
""
;
this.
started =
false;
if (
this.
métier) {
// on réinitialise le champ [taxAdminData]
this.
métier.
taxAdminData =
null;
}
// le store Vuex est nettoyé également
store.commit
(
"clear"
);
// on sauvegarde la nouvelle session
this.save
(
);
},
}
// export de l'objet [session]
export default session;
Commentaires
- ligne 2 : la session va encapsuler également le store [Vuex] (liste des simulations, n° de la dernière simulation faite) ;
-
lignes 7-17 : les informations conservées par la session :
- [started] : la session jSON avec le serveur a démarré ou non ;
- [authenticated] : l’utilisateur s’est authentifié ou pas ;
- [saveTime] : la date en millisecondes de la dernière sauvegarde ;
- [métier] : une référence sur la couche [métier]. Celle-ci contient la donnée [taxAdminData] qui permet le calcul de l’impôt ;
- [state] : le state du store [Vuex](liste des simulations, n° de la dernière simulation faite) ;
-
lignes 20-30 : la méthode [save] sauvegarde la session localement sur le navigateur exécutant l’application ;
- ligne 22 : on note l’heure de sauvegarde ;
- ligne 23 : on récupère le [state] du store [Vuex] ;
- ligne 25 : on crée la chaîne jSON de la session ;
- ligne 27 : on la stocke localement sur le navigateur associée à la clé [session] ;
-
lignes 33-57 : la méthode [restore] permet de restaurer une session à partir de sa sauvegarde locale sur le navigateur ;
- ligne 35 : on récupère la sauvegarde jSON locale ;
- ligne 37 : si on a récupéré quelque chose ;
- lignes 39-44 : l’objet [session] est reconstitué ;
- ligne 46 : on calcule la durée qui nous sépare de la dernière sauvegarde ;
- lignes 47-50 : si cette durée est supérieure à une valeur [config.duréeSession] fixée par configuration, la session est réinitialisée (ligne 49) et à cette occasion sauvegardée ;
- ligne 52 : sinon on régénère l’attribut [state] du store [Vuex] ;
-
lignes 60-75 : la méthode [clear] réinitialise la session ;
- lignes 64-70 : les propriétés de la session sont réinitialisées à leurs valeurs initiales ;
- ligne 72 : ainsi que le store [Vuex] ;
- ligne 74 : la nouvelle session est sauvegardée ;
XX-D. Le fichier de configuration [config]▲
Le fichier [./config] évolue de la façon suivante :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
// utilisation de la bibliothèque [axios]
const axios =
require
(
'axios'
);
// timeout des requêtes HTTP
axios.
defaults.
timeout =
2000
;
...
// export de la configuration
export default {
// objet [axios]
axios
:
axios,
// délai maximal d'inactivité de la session : 5 mn = 300 s = 300000 ms
duréeSession:
300000
}
- ligne 12 : on va gérer la session de l’application un peu comme on gère une session web. On fixe ici une durée d’inactivité maximale de 5 minutes ;
XX-E. Le plugin [pluginSession]▲
Comme il a été fait déjà de nombreuses fois, le plugin [pluginSession] va permettre aux vues d’avoir accès à la session via la propriété [this.$session] :
XX-F. Le script principal [main]▲
Le script principal [./main.js] évolue de la façon suivante :
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.
// log de démarrage
// eslint-disable-next-line no-console
console.log
(
"main started"
);
// imports
import Vue from 'vue'
...
// instanciation couche [métier]
import Métier from './couches/Métier'
;
const métier =
new Métier
(
);
// plugin [métier]
import pluginMétier from './plugins/pluginMétier'
Vue.use
(
pluginMétier,
métier)
// store Vuex
import store from './store'
// session
import session from './session'
;
import pluginSession from './plugins/pluginSession'
Vue.use
(
pluginSession,
session)
// on restore la session avant de redémarrer
session.restore
(
);
// on restaure la couche [métier]
if (
session.
métier &&
session.
métier.
taxAdminData) {
métier.setTaxAdminData
(
session.
métier.
taxAdminData);
}
// démarrage de l'UI
new Vue
({
el
:
'#app'
,
// le routeur
router
:
router,
// le store Vuex
store
:
store,
// la vue principale
render
:
h =>
h
(
Main),
}
)
// log de fin
// eslint-disable-next-line no-console
console.log
(
"main terminated, session="
,
session);
- ligne 19 : on importe la session ;
- ligne 20 : on importe son plugin ;
- ligne 21 : le plugin [pluginSession] est intégré à [Vue]. Après cette instruction toutes les vues disposent de la session dans leur attribut [$session] ;
- ligne 27 : la session est restaurée. La session importée ligne 11 est alors initialisée avec le contenu de sa dernière sauvegarde ;
- après la ligne 16, les vues disposent d’une propriété [$métier] initialisée ligne 12. Cette propriété n’a pas l’information [taxAdminData] qui permet de calculer l’impôt ;
- lignes 30-32 : si la restauration qui vient d’être faite a restauré la propriété [session.métier.taxAdminData] alors la propriété [$métier] des vues est initialisée avec cette valeur ;
XX-G. Le fichier de routage [router]▲
Le fichier de routage [./router] évolue comme suit :
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.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
// imports
import Vue from 'vue'
import VueRouter from 'vue-router'
// les vues
import Authentification from './views/Authentification'
import CalculImpot from './views/CalculImpot'
import ListeSimulations from './views/ListeSimulations'
import NotFound from './views/NotFound'
// la session
import session from './session'
// plugin de routage
Vue.use
(
VueRouter)
// les routes de l'application
const routes =
[
// authentification
{
path
:
'/'
,
name
:
'authentification'
,
component
:
Authentification },
{
path
:
'/authentification'
,
name
:
'authentification'
,
component
:
Authentification },
// calcul de l'impôt
{
path
:
'/calcul-impot'
,
name
:
'calculImpot'
,
component
:
CalculImpot,
meta
:
{
authenticated
:
true }
},
// liste des simulations
{
path
:
'/liste-des-simulations'
,
name
:
'listeSimulations'
,
component
:
ListeSimulations,
meta
:
{
authenticated
:
true }
},
// fin de session
{
path
:
'/fin-session'
,
name
:
'finSession'
},
// page inconnue
{
path
:
'*'
,
name
:
'notFound'
,
component
:
NotFound,
},
]
// le routeur
const router =
new VueRouter
({
// les routes
routes,
// le mode d'affichage des URL
mode
:
'history'
,
// l'URL de base de l'application
base
:
'/client-vuejs-impot/'
}
)
// vérification des routes
router.beforeEach
((
to,
from,
next) =>
{
// eslint-disable-next-line no-console
console.log
(
"router to="
,
to,
"from="
,
from);
// route réservée aux utilisateurs authentifiés ?
if (
to.
meta.
authenticated &&
!
session.
authenticated) {
next
({
// on passe à l'authentification
name
:
'authentification'
,
}
)
// retour à la boucle événementielle
return;
}
// cas particulier de la fin de session
if (
to.
name
===
"finSession"
) {
// on nettoie la session
session.clear
(
);
// on va sur la vue [authentification]
next
({
name
:
'authentification'
,
}
)
// retour à la boucle événementielle
return;
}
// autres cas - vue suivante normale du routage
next
(
);
}
)
// export du router
export default router
Commentaires
- lignes 16-38 : certaines routes ont été enrichies d’informations supplémentaires ;
- ligne 19 : on a créé une nouvelle route pour aller à la vue [Authentification] ;
- lignes 21-24 : la route qui mène à la vue [CalculImpot] a maintenant une propriété [meta] (ce nom est obligatoire). Le contenu de cet objet peut être quelconque et est fixé par le développeur ;
- ligne 23 : on met dans [meta], la propriété [authenticated] (ce nom peut être quelconque). Il signifiera pour nous que pour aller à la vue [CalculImpot], l’utilisateur doit être authentifié ;
- lignes 26-29 : on fait la même chose pour la route qui mène à la vue [ListeSimulations]. Là aussi, l’utilisateur doit être authentifié ;
- la propriété [meta.authenticated] va nous permettre de vérifier qu’un utilisateur qui tape manuellement les URL des vues [CalculImpot, ListeSimulations] ne peut pas les obtenir s’il n’est pas authentifié ;
-
lignes 51-76 : la méthode [beforeEach] est exécutée avant qu’une vue ne soit routée. C’est le bon moment pour faire des vérifications ;
- [to] : la prochaine route si on ne fait rien ;
- [from] : la dernière route affichée ;
- [next] : fonction permettant de changer la prochaine route affichée ;
- ligne 55 : on regarde si la prochaine route demande à ce que l’utilisateur soit authentifié ;
- lignes 56-59 : si oui et que l’utilisateur n’est pas authentifié, on change la prochaine route vers la vue [Authentification] ;
-
lignes 64-73 : on traite le cas particulier de la route [finSession] des lignes 30-32. Celle-ci n’a pas de vue associée ;
- ligne 66 : on réinitialise la session à sa valeur initiale ;
- lignes 68-70 : on programme la vue [Authentification] comme prochaine vue ;
- ligne 75 : si on n’est pas dans les deux cas précédents, on se contente de passer à la route prévue par le fichier de routage ;
- lignes 35-37 : on prévoit une vue [NotFound] si la route tapée par l’utilisateur ne correspond à aucune route connue. Cette vue est importée ligne 8. Les routes sont vérifiées dans l’ordre du fichier de routage. Si donc on arrive à la ligne 36, c’est que la route demandée n’est aucune des routes des lignes 18-33 ;
XX-H. La vue [NotFound]▲
La vue [NotFound] est affichée si la route tapée par l’utilisateur ne correspond à aucune route connue :
Le code de la vue est le suivant :
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.
<!-- définition HTML de la vue -->
<
template>
<!-- mise en page -->
<
Layout :left
=
"
true
"
:right
=
"
true
"
>
<!-- alerte dans la colonne de droite -->
<
template slot
=
"
right
"
>
<!-- message sur fond jaune -->
<
b-alert show variant
=
"
danger
"
align
=
"
center
"
>
<
h4>
Cette page n'existe pas<
/h4
>
<
/b-alert
>
<
/template
>
<!-- menu de navigation dans la colonne de gauche -->
<
Menu slot
=
"
left
"
:options
=
"
options
"
/
>
<
/Layout
>
<
/template
>
<script>
// imports
import
Layout from
"
./Layout
"
;
import
Menu from
"
./Menu
"
;
export
default {
// composants
components
:
{
Layout,
Menu
},
// état interne du composant
data
(
) {
return
{
// options du menu de navigation
options
:
[
{
text
:
"
Authentification
"
,
path
:
"
/
"
}
]
};
},
// cycle de vie
created
(
) {
// eslint-disable-next-line
console.log
(
"
NotFound created
"
);
// on regarde quelles options de menu offrir
if
(
this
.
$session.
authenticated &&
this
.
$métier.
taxAdminData) {
// l'utilisateur peut faire des simulations
Array
.
prototype.
push.apply
(
this
.
options,
[
{
text
:
"
Calcul de l'impôt
"
,
path
:
"
/calcul-impot
"
},
{
text
:
"
Liste des simulations
"
,
path
:
"
/liste-des-simulations
"
}
]
);
}
}
};
</
script>
Commentaires
- ligne 4 : elle utilise les deux colonnes des vues routées ;
- lignes 6-11 : un message d’erreur ;
- ligne 13 : le menu de navigation occupe la colonne de gauche ;
- lignes 31-36 : les options par défaut du menu ;
- lignes 40-57 : code exécuté lorsque la vue est créée ;
- ligne 44 : on regarde si l’utilisateur peut faire des simulations ;
- lignes 45-55 : si oui, on ajoute deux options au menu de navigation, celles où il faut être authentifié et avoir une couche [métier] opérationnelle (lignes 46-55) ;
XX-I. La vue [Authentification]▲
La vue [Authentification] évolue comme suit :
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.
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.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
<!-- définition HTML de la vue -->
<
template>
<
Layout :left
=
"
false
"
:right
=
"
true
"
>
...
<
/Layout
>
<
/template
>
<!-- dynamique de la vue -->
<script>
import
Layout from
"
./Layout
"
;
export
default {
// état du composant
data
(
) {
return
{
// utilisateur
user
:
""
,
// son mot de passe
password
:
""
,
// contrôle l'affichage d'un msg d'erreur
showError
:
false
,
// le message d'erreur
message
:
""
};
},
// composants utilisés
components
:
{
Layout
},
// propriétés calculées
computed
:
{
// saisies valides
valid
(
) {
return
this
.
user &&
this
.
password
&&
this
.
$session.
started;
}
},
// gestionnaires d'évts
methods
:
{
// ----------- authentification
async
login
(
) {
try
{
// début attente
this
.
$emit
(
"
loading
"
,
true
);
// on n'est pas encore authentifié
this
.
$session.
authenticated =
false
;
// authentification bloquante auprès du serveur
const
response =
await
this
.
$dao.authentifierUtilisateur
(
this
.
user,
this
.
password
);
// fin du chargement
this
.
$emit
(
"
loading
"
,
false
);
// analyse de la réponse du serveur
if
(
response.
état !=
200
) {
// on affiche l'erreur
this
.
message =
response.
réponse;
this
.
showError =
true
;
// retour à la boucle événementielle
return
;
}
// pas d'erreur
this
.
showError =
false
;
// on est authentifié
this
.
$session.
authenticated =
true
;
// --------- on demande maintenant les données de l'administration fiscale
// au départ, pas de donnée
this
.
$métier.setTaxAdminData
(
null
);
// début attente
this
.
$emit
(
"
loading
"
,
true
);
// demande bloquante auprès du serveur
const
response2 =
await
this
.
$dao.getAdminData
(
);
// fin du chargement
this
.
$emit
(
"
loading
"
,
false
);
// analyse de la réponse
if
(
response2.
état !=
1000
) {
// on affiche l'erreur
this
.
message =
response2.
réponse;
this
.
showError =
true
;
// retour à la boucle événementielle
return
;
}
// pas d'erreur
this
.
showError =
false
;
// on mémorise dans la couche [métier] la donnée reçue
this
.
$métier.setTaxAdminData
(
response2.
réponse);
// on peut passer au calcul de l'impôt
this
.
$router.push
({
name
:
"
calculImpot
"
}
);
}
catch
(
error) {
// on remonte l'erreur au composant principal
this
.
$emit
(
"
error
"
,
error);
}
finally
{
// maj session
this
.
$session.
métier =
this
.
$métier;
// on sauvegarde la session
this
.
$session.save
(
);
}
}
},
// cycle de vie : le composant vient d'être créé
created
(
) {
// eslint-disable-next-line
console.log
(
"
Authentification created
"
);
// l'utilisateur peut-il faire des simulations ?
if
(
this
.
$session.
started &&
this
.
$session.
authenticated &&
this
.
$métier.
taxAdminData
) {
// alors l'utilisateur peut faire des simulations
this
.
$router.push
({
name
:
"
calculImpot
"
}
);
// retour à la boucle événementielle
return
;
}
// si la session jSON a déjà été démarrée, on ne la redémarre pas de nouveau
if
(!
this
.
$session.
started) {
// début attente
this
.
$emit
(
"
loading
"
,
true
);
// on initialise la session avec le serveur - requête asynchrone
// on utilise la promesse rendue par les méthodes de la couche [dao]
this
.
$dao
// on initialise une session jSON
.initSession
(
)
// on a obtenu la réponse
.then
(
response =>
{
// fin attente
this
.
$emit
(
"
loading
"
,
false
);
// analyse de la réponse
if
(
response.
état !=
700
) {
// on affiche l'erreur
this
.
message =
response.
réponse;
this
.
showError =
true
;
// retour à la boucle événementielle
return
;
}
// la session a démarré
this
.
$session.
started =
true
;
}
)
// en cas d'erreur
.catch
(
error =>
{
// on remonte l'erreur à la vue [Main]
this
.
$emit
(
"
error
"
,
error);
}
)
// dans tous les cas
.finally
((
) =>
{
// on sauvegarde la session
this
.
$session.save
(
);
}
);
}
}
};
</
script>
Commentaires
- on a surligné en jaune les instructions qui utilisent la session introduite dans cette version du client [Vue.js] ;
- lignes 97, 148 : à la fin des méthodes [login, created], la session est sauvegardée quelque soit le résultat des requêtes HTTP qui ont lieu dans ces méthodes (clause [finally] dans les deux cas) ;
- la méthode [created] des lignes 102-150 est exécutée à chaque fois que la vue [Authentification] est créée. Si c’est l’utilisateur qui a tapé l’URL de la vue, la session va nous permettre de savoir quoi faire ;
- lignes 106-115 : si la session jSON est démarrée, l’utilisateur authentifié et la donnée [this.$métier.taxAdminData] initialisée alors l’utilisateur peut directement aller au formulaire de calcul de l’impôt (ligne 112) ;
- ligne 117 : la méthode [created] était utilisée dans la version précédente pour initialiser une session jSON avec le serveur. Cette phase est inutile si elle a déjà eu lieu ;
- lignes 42-66 : la méthode d’authentification ;
- ligne 66 : si l’authentification réussit, on le note dans la session ;
- lignes 67-92 : la demande au serveur des données de l’administration fiscale [taxAdminData] ;
- ligne 95 : à la fin de cette phase, on met à jour la propriété [métier] de la session que l’opération ait réussi ou pas ;
XX-J. La vue [CalculImpot]▲
Le code de la vue [CalculImpot] évolue comme suit :
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.
<!-- définition HTML de la vue -->
<
template>
...
<
/template
>
<script>
// imports
import
FormCalculImpot from
"
./FormCalculImpot
"
;
import
Menu from
"
./Menu
"
;
import
Layout from
"
./Layout
"
;
export
default {
// état interne
data
(
) {
return
{
// options du menu
options
:
[
{
text
:
"
Liste des simulations
"
,
path
:
"
/liste-des-simulations
"
},
{
text
:
"
Fin de session
"
,
path
:
"
/fin-session
"
}
],
// résultat du calcul de l'impôt
résultat:
""
,
résultatObtenu:
false
};
},
// composants utilisés
components
:
{
Layout,
FormCalculImpot,
Menu
},
// méthodes de gestion des évts
methods
:
{
// résultat du calcul de l'impôt
handleResultatObtenu
(
résultat) {
// on construit le résultat en chaîne HTML
...
// une simulation de +
this
.
$store.commit
(
"
addSimulation
"
,
résultat);
// on sauvegarde la session
this
.
$session.save
(
);
}
},
// cycle de vie
created
(
) {
// eslint-disable-next-line
console.log
(
"
CalculImpot created
"
);
}
};
</
script>
Commentaires
- ligne 45 : la simulation calculée est ajoutée au store [Vuex]. Cela a un impact sur la session qui englobe la propriété [state] du store. Aussi sauvegarde-t-on la session (ligne 47) ;
- ligne 51 : on crée une méthode [created] pour suivre dans les logs les créations des vues ;
XX-K. La vue [ListeSimulations]▲
La vue [ListeSimulations] évolue comme suit :
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.
<!-- définition HTML de la vue -->
<
template>
...
<
/div
>
<
/template
>
<script>
// imports
import
Layout from
"
./Layout
"
;
import
Menu from
"
./Menu
"
;
export
default {
// composants
components
:
{
Layout,
Menu
},
// état interne
data
(
) {
...
},
// état interne calculé
computed
:
{
// liste des simulations prise dans le store Vuex
simulations
(
) {
return
this
.
$store.
state.
simulations;
}
},
// méthodes
methods
:
{
supprimerSimulation
(
index) {
// eslint-disable-next-line
console.log
(
"
supprimerSimulation
"
,
index);
// suppression de la simulation n° [index]
this
.
$store.commit
(
"
deleteSimulation
"
,
index);
// on sauvegarde la session
this
.
$session.save
(
);
}
},
// cycle de vie
created
(
) {
// eslint-disable-next-line
console.log
(
"
ListeSimulations created
"
);
}
};
</
script>
Commentaires
- ligne 36 : après la suppression d’une simulation ligne 34, on sauvegarde la session pour tenir compte de ce changement d’état ;
- lignes 40-43 : on continue à suivre la création des vues ;
XX-L. Exécution du projet▲
Lors des tests vérifiez les points suivants :
- si l’utilisateur ‘utilise’ l’application via les liens du menu de navigation et les boutons / liens d’action, celle-ci fonctionne ;
-
si l’utilisateur tape manuellement des URL, l’application continue à fonctionner. Faites en particulier le test suivant :
- faites simulations ;
- une fois sur la vue [ListeSimulations], rechargez (F5) la vue. Dans l’application précédente [vuejs-20], on perdait alors les simulations. Ici ce n’est pas le cas : on retrouve bien les simulations déjà faites ;
-
regardez les logs pour comprendre :
- à quel moment le script [main] est exécuté. Vous devez voir qu’il l’est à chaque fois que l’utilisateur tape une URL à la main ;
- à quels moments les vues sont créées. Vous devez voir qu’elles le sont à chaque fois qu’elles vont être affichées ;
-
le fonctionnement du routage. Avant chaque routage un log est fait qui vous indique :
- la route d’où vous venez ;
- la route où vous allez ;
XX-M. Déploiement de l’application sur un serveur local▲
Comme exercice, suivez le paragraphe Déploiement sur un serveur localDéploiement de l’application sur un serveur local, pour déployer le projet [vuejs-21] sur le serveur Laragon local. Puis testez-le.
XX-N. Mise au point de la version mobile▲
Théoriquement, l’utilisation de Bootstrap devrait nous permettre d’avoir une application utilisable sur différents média : smartphone, tablette, ordinateurs portable et de bureau. Cequi différencie ces média c’est la taille de leur écran.
Si on teste la version [vuejs-21] sur un mobile, on constate que c’est le chaos dans l’affichage des vues. La version [vuejs-22] corrige ce point. Les modifications ont toutes lieu dans les templates des vues. Elles ont consisté essentiellement à mettre au point un affichage pour un écran de smartphone. Lorsque celui-ci est au point, l’affichage sur des écrans de taille plus importante se passe de façon fluide grâce à Bootstrap.
XX-N-1. La vue [Main]▲
La vue [Main] évolue de la façon suivante :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
<!-- définition HTML de la vue -->
<
template>
<
div class
=
"
container
"
>
<
b-card>
<!-- jumbotron -->
<
b-jumbotron>
<
b-row>
<
b-col sm
=
"
4
"
>
<
img src
=
"
../assets/logo.jpg
"
alt
=
"
Cerisier en fleurs
"
/
>
<
/b-col
>
<
b-col sm
=
"
8
"
>
<
h1>
Calculez votre impôt<
/h1
>
<
/b-col
>
<
/b-row
>
<
/b-jumbotron
>
....
<
/b-card
>
<
/div
>
<
/template
>
Commentaires
- ligne 8 : là où il y avait [cols=’4’] on écrit [sm=’4’]. [sm] signifie [small]. Les écrans des smartphones tombent dans cette catégorie. Les autres catégories sont [xs=extra small, md=medium, lg=large, xl=extra large] ;
- ligne 11 : idem ;
XX-N-2. La vue [Layout]▲
La vue [Layout] évolue comme suit :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
<!-- définition HTML de la mise en page de la vue routée -->
<
template>
<!-- ligne -->
<
div>
<
b-row>
<!-- zone de trois colonnes à gauche -->
<
b-col sm
=
"
3
"
v-if
=
"
left
"
>
<
slot name
=
"
left
"
/
>
<
/b-col
>
<!-- zone de neuf colonnes à droite -->
<
b-col sm
=
"
9
"
v-if
=
"
right
"
>
<
slot name
=
"
right
"
/
>
<
/b-col
>
<
/b-row
>
<
/div
>
<
/template
>
XX-N-3. La vue [Authentification]▲
La vue [Authentification] évolue comme suit :
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.
<!-- définition HTML de la vue -->
<
template>
<
Layout :left
=
"
false
"
:right
=
"
true
"
>
<
template slot
=
"
right
"
>
<!-- formulaire HTML - on poste ses valeurs avec l'action [authentifier-utilisateur] -->
<
b-form @submit
.prevent
=
"
login
"
>
<!-- titre -->
<
b-alert show variant
=
"
primary
"
>
<
h4>
Bienvenue. Veuillez vous authentifier pour vous connecter<
/h4
>
<
/b-alert
>
<!-- 1ère ligne -->
<
b-form-group label
=
"
Nom d'utilisateur
"
label-for
=
"
user
"
description
=
"
Tapez admin
"
>
<!-- zone de saisie user -->
<
b-col sm
=
"
6
"
>
<
b-form-input type
=
"
text
"
id
=
"
user
"
placeholder
=
"
Nom d'utilisateur
"
v-model
=
"
user
"
/
>
<
/b-col
>
<
/b-form-group
>
<!-- 2ième ligne -->
<
b-form-group label
=
"
Mot de passe
"
label-for
=
"
password
"
description
=
"
Tapez admin
"
>
<!-- zone de saisie password -->
<
b-col sm
=
"
6
"
>
<
b-input type
=
"
password
"
id
=
"
password
"
placeholder
=
"
Mot de passe
"
v-model
=
"
password
"
/
>
<
/b-col
>
<
/b-form-group
>
<!-- 3ième ligne -->
<
b-alert
show
variant
=
"
danger
"
v-if
=
"
showError
"
class
=
"
mt-3
"
>
L'erreur suivante s'est produite : {{
message}}
<
/b-alert
>
<!-- bouton de type [submit] sur une 3ième ligne -->
<
b-row>
<
b-col sm
=
"
2
"
>
<
b-button variant
=
"
primary
"
type
=
"
submit
"
:disabled
=
"
!valid
"
>
Valider<
/b-button
>
<
/b-col
>
<
/b-row
>
<
/b-form
>
<
/template
>
<
/Layout
>
<
/template
>
Commentaires
- lignes 11 et 19 : on a supprimé l’attribut [label-cols] qui fixait un nombre de colonnes au label de la saisie. En l’absence de cet attribut, le label est au-dessus de la zone de saisie. Cela convient mieux aux écrans des smartphones ;
XX-N-4. La vue [CalculImpot]▲
La vue [CalculImpot] évolue comme suit :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
<!-- définition HTML de la vue -->
<
template>
<
div>
<
Layout :left
=
"
true
"
:right
=
"
true
"
>
<!-- formulaire de calcul de l'impôt à droite -->
<
FormCalculImpot slot
=
"
right
"
@resultatObtenu
=
"
handleResultatObtenu
"
/
>
<!-- menu de navigation à gauche -->
<
Menu slot
=
"
left
"
:options
=
"
options
"
/
>
<
/Layout
>
<!-- zone d'affichage des résultat du calcul de l'impôt sous le formulaire -->
<
b-row v-if
=
"
résultatObtenu
"
class
=
"
mt-3
"
>
<!-- zone de trois colonnes vide -->
<
b-col sm
=
"
3
"
/
>
<!-- zone de neuf colonnes -->
<
b-col sm
=
"
9
"
>
<
b-alert show variant
=
"
success
"
>
<
span v-html
=
"
résultat
"
></span
>
<
/b-alert
>
<
/b-col
>
<
/b-row
>
<
/div
>
<
/template
>
XX-N-5. La vue [FormCalculImpot]▲
La vue [FormCalculImpot] évolue comme suit :
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.
<!-- définition HTML de la vue -->
<
template>
<!-- formulaire HTML -->
<
b-form @submit
.prevent
=
"
calculerImpot
"
class
=
"
mb-3
"
>
<!-- message sur 12 colonnes sur fond bleu -->
<
b-row>
<
b-col sm
=
"
12
"
>
<
b-alert show variant
=
"
primary
"
>
<
h4>
Remplissez le formulaire ci-dessous puis validez-le<
/h4
>
<
/b-alert
>
<
/b-col
>
<
/b-row
>
<!-- éléments du formulaire -->
<!-- première ligne -->
<
b-form-group label
=
"
Etes-vous marié(e) ou pacsé(e) ?
"
>
<!-- boutons radio sur 5 colonnes-->
<
b-col sm
=
"
5
"
>
<
b-form-radio v-model
=
"
marié
"
value
=
"
oui
"
>
Oui<
/b-form-radio
>
<
b-form-radio v-model
=
"
marié
"
value
=
"
non
"
>
Non<
/b-form-radio
>
<
/b-col
>
<
/b-form-group
>
<!-- deuxième ligne -->
<
b-form-group label
=
"
Nombre d'enfants à charge
"
label-for
=
"
enfants
"
>
<
b-form-input
type
=
"
text
"
id
=
"
enfants
"
placeholder
=
"
Indiquez votre nombre d'enfants
"
v-model
=
"
enfants
"
:state
=
"
enfantsValide
"
></b-form-input
>
<!-- message d'erreur éventuel -->
<
b-form-invalid-feedback :state
=
"
enfantsValide
"
>
Vous devez saisir un nombre positif ou nul<
/b-form-invalid-feedback
>
<
/b-form-group
>
<!-- troisème ligne -->
<
b-form-group
label
=
"
Salaire annuel net imposable
"
label-for
=
"
salaire
"
description
=
"
Arrondissez à l'euro inférieur
"
>
<
b-form-input
type
=
"
text
"
id
=
"
salaire
"
placeholder
=
"
Salaire annuel
"
v-model
=
"
salaire
"
:state
=
"
salaireValide
"
></b-form-input
>
<!-- message d'erreur éventuel -->
<
b-form-invalid-feedback :state
=
"
salaireValide
"
>
Vous devez saisir un nombre positif ou nul<
/b-form-invalid-feedback
>
<
/b-form-group
>
<!-- quatrième ligne, bouton [submit] -->
<
b-col sm
=
"
3
"
>
<
b-button type
=
"
submit
"
variant
=
"
primary
"
:disabled
=
"
formInvalide
"
>
Valider<
/b-button
>
<
/b-col
>
<
/b-form
>
<
/template
>
Commentaires
- lignes 15, 23, 35 : on a supprimé l’attribut [label-cols] ;
Par ailleurs, on fait évoluer les tests de validité :
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.
...
// état interne calculé
computed
:
{
// validation du formulaire
formInvalide
(
) {
return (
// salaire invalide
!
this.
salaire.match
(/^
\s*
\d+
\s*
$/
) ||
// ou enfants invalide
!
this.
enfants.match
(/^
\s*
\d+
\s*
$/
) ||
// ou données fiscales pas obtenues
!
this.
$métier.
taxAdminData
);
},
// validation du salaire
salaireValide
(
) {
// doit être numérique >=0
return Boolean(
this.
salaire.match
(/^
\s*
\d+
\s*
$/
) ||
this.
salaire.match
(/^
\s*
$/
)
);
},
// validation des enfants
enfantsValide
(
) {
// doit être numérique >=0
return Boolean(
this.
enfants.match
(/^
\s*
\d+
\s*
$/
) ||
this.
enfants.match
(/^
\s*
$/
)
);
}
},
...
Commentaires
- ligne 19 : lorsque rien n’a été saisi, la saisie est considérée comme valide. Cela permet d’avoir une saisie valide lorsque la vue est initialement affichée. Dans la version précédente, la saisie apparaissait initialement comme erronée ;
- ligne 26 : idem ;
- lignes 5-14 : le bouton de validation n’est actif que si les deux saisies contiennent quelque chose et sont valides ;
XX-N-6. La vue [Menu]▲
La vue [Menu] évolue comme suit :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
<!-- définition HTML de la vue -->
<
template>
<
b-card class
=
"
mb-3
"
>
<!-- menu Bootstrap vertical -->
<
b-nav vertical>
<!-- options du menu -->
<
b-nav-item
v-for
=
"
(option,index) of options
"
:key
=
"
index
"
:to
=
"
option.path
"
exact
exact-active-class
=
"
active
"
>
{{
option.
text}}
<
/b-nav-item
>
<
/b-nav
>
<
/b-card
>
<
/template
>
Commentaires
- ligne 3 : on ajoute la balise <b-card> pour entourer le menu d’une fine bordure. Cela permet de mieux localiser le menu sur le smartphone ;
XX-N-7. La vue [ListeSimulations]▲
La vue [ListeSimulations] reste inchangée :
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.
<!-- définition HTML de la vue -->
<
template>
<
div>
<!-- mise en page -->
<
Layout :left
=
"
true
"
:right
=
"
true
"
>
<!-- simulations dans colonne de droite -->
<
template slot
=
"
right
"
>
<
template v-if
=
"
simulations.length==0
"
>
<!-- pas de simulations -->
<
b-alert show variant
=
"
primary
"
>
<
h4>
Votre liste de simulations est vide<
/h4
>
<
/b-alert
>
<
/template
>
<
template v-if
=
"
simulations.length!=0
"
>
<!-- il y a des simulations -->
<
b-alert show variant
=
"
primary
"
>
<
h4>
Liste de vos simulations<
/h4
>
<
/b-alert
>
<!-- tableau des simulations -->
<
b-table striped hover responsive :items
=
"
simulations
"
:fields
=
"
fields
"
>
<
template v-slot:cell(action)=
"
data
"
>
<
b-button variant
=
"
link
"
@click
=
"
supprimerSimulation(data.index)
"
>
Supprimer<
/b-button
>
<
/template
>
<
/b-table
>
<
/template
>
<
/template
>
<!-- menu de navigation dans colonne de gauche -->
<
Menu slot
=
"
left
"
:options
=
"
options
"
/
>
<
/Layout
>
<
/div
>
<
/template
>
Commentaires
- ligne 20 : on notera l’attribut [responsive] qui fait que l’affichage de la table s’adapte à la taille de l’écran :
- en [2], sur les petits écrans, une barre de défilement horizontal permet d’afficher la table ;
XX-N-8. La vue [NotFound]▲
Elle reste inchangée.
XX-N-9. Les vues sur mobile▲
Note : il y a sûrement possibilité d’obtenir des vues encore mieux adaptées au mobile. Je pense notamment au menu de navigation qui pourrait être amélioré mais il y a d’autres points. Ce document n’avait pas pour objectif premier la création d’une application mobile. Dans ce cas, on se serait peut-être tourné vers un framework comme Ionic https://ionicframework.com/.