forked from DiscoverMeteor/DiscoverMeteor_fr
-
Notifications
You must be signed in to change notification settings - Fork 0
/
05-routing.md.erb
340 lines (226 loc) · 19.4 KB
/
05-routing.md.erb
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
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
---
title: Le Routage
slug: routing
date: 0005/01/01
number: 5
points: 5
photoUrl: http://www.flickr.com/photos/ikewinski/9517814403/
photoAuthor: Mike Lewinski
contents: Apprendre le routage dans Meteor.|Créer des pages pour discuter des posts.|Apprendre à faire des liens vers ces URLs.
paragraphs: 72
---
Maintenant que nous avons une liste de posts (qui peuvent avoir été éventuellement envoyés par des utilisateurs), nous avons besoin d'une page pour chaque post où les utilisateurs auront la possibilité de laisser des commentaires.
Nous aimerions rendre ces pages accessible par un *permalien*, une URL de la forme `http://myapp.com/posts/xyz` (où `xyz` est un identifiant MongoDB `_id`) qui est unique pour chaque post.
Cela signifie que nous allons avoir besoin d'un *routage* pour voir ce qu'il y a dans la barre URL du navigateur et afficher le contenu correspondant.
### Ajout du package Iron Router
[Iron Router](https://github.com/EventedMind/iron-router) est un package de routage qui a été créé spécialement pour les applications Meteor.
Non seulement c'est une aide pour le routage (la mise en place des chemins), mais le package s'occupe aussi des filtres (l'assignation de ces chemins à des actions) et il s'occupe même des abonnements (savoir quel chemin permet d'accéder à quelle donnée). (Note : Iron Router a été développer par un des co-auteurs de *Discover Meteor*, Tom Coleman.)
Commençons par installer le package depuis Atmosphere :
~~~bash
$ mrt add iron-router
~~~
<%= caption "Terminal" %>
Cette commande va télécharger et installer le package iron-router dans votre application. Notez que vous devrez probablement redémarrer votre application Meteor (avec `ctrl+c` pour terminer le processus, puis `mrt` pour le redémarrer) avant que le package ne soit utilisable.
Notez que Iron Router est un package tier, ce qui signifie qu'il vous faudra Meteorite pour l'installer (`meteor add iron-router` ne marchera pas).
<% note do %>
### Vocabulaire sur le routage
Nous allons aborder plusieurs fonctionnalités du routage dans ce chapitre. Si vous avez déjà utilisé un Framework comme Rails vous connaissez probablement la pluspart de ces concepts. Si ce n'est pas la cas, voici un glossaire pour vous aider :
- **Routes** : une route est le bloc de base du routage. C'est un jeu d'instruction qui dit à l'application où aller et quoi faire pour chaque URL.
- **Chemins** : un chemin (ou Path) est une URL de l'application. Elle peut être statique (`/information_legales`) ou dynamique (`/posts/xyz`). Il peut même y avoir des paramètres (`/search?keyword=meteor`).
- **Segments** : ce sont les différentes parties qui composent un chemin, séparées par un slash (`/`).
- **Hooks** : Les Hooks sont les actions qui seront effectuées avant, après ou même pendant le processus de routage. Un exemple typique serait de vérifier si l'utilisateur a les droits nécessaire pour afficher une page.
- **Filtres** : Les filtres sont des hooks qui sont définis globalement pour une ou plusieurs routes.
- **Template de routes** : Chaque route doit pointer vers un template. Si vous n'en précisez pas un, le routeur cherchera le template avec le même nom que la route.
- **Layouts** : On peut voir les layouts comme des cadres photo numérique. Ils contiennent tout le code HTML qui entoure les templates et qui ne bougera pas même si le template est modifié.
- **Controlleurs** : Quelques fois, vous réaliserez que beaucoup de templates réutilisent les mêmes paramètres. Plutôt que de dupliquer votre code, vous pouvez faire hériter toutes ces routes d'un même *controlleur de routage* qui contient toute la logique de routage.
Pour plus d'information sur Iron Router, consultez [la documentation complète sur GitHub](https://github.com/EventedMind/iron-router).
<% end %>
### Routage : Relier des URLS à des templates
Jusqu'à présent nous avons construit notre layout en utilisant des inclusions codées en dur (comme `{{>postsList}}`). Bien que le contenu de notre application puisse changer, la structure de la page est toujours la même : un titre avec une liste de posts en dessous.
Iron Router nous laisse sortir du cadre en nous laissant changer ce qui est affiché dans la balise HTML `<body>`. Donc nous n'allons pas définir le contenu de cette balise nous-même comme dans une page HTML classique. A la place, nous allons pointer le routeur vers un template spécial qui contient un helper de template `{{> yield}}`.
Ce helper `{{> yield}}` va définir une zone dynamique qui va automatiquement afficher le template correspondant à la route courante (par convention, nous désignerons à partir de maintenant ces templates spéciaux les "templates de routage") :
<%= diagram "router-diagram", "Layouts et templates.", "pull-center" %>
Nous allons commencer par créer notre layout et ajouter le helper `{{> yield}}`. Premièrement, nous allons supprimer l'élément HTML `<body>` de `main.html`, et déplacer son contenu vers son propre template, `layout.html`.
Notre `main.html` réduit ressemble maintenant à ça :
~~~html
<head>
<title>Microscope</title>
</head>
~~~
<%= caption "client/main.html" %>
Le fichier `layout.html` nouvellement créé contiendra maintenant le layout extérieur de notre application :
~~~html
<template name="layout">
<div class="container">
<header class="navbar">
<div class="navbar-inner">
<a class="brand" href="/">Microscope</a>
</div>
</header>
<div id="main" class="row-fluid">
{{> yield}}
</div>
</div>
</template>
~~~
<%= caption "client/views/application/layout.html" %>
Vous noterez que nous avons remplacé l'inclusion du template `postsList` avec un appel du helper `yield`. Vous noterez qu'après ce changement, nous ne voyons rien à l'écran. C'est parce que nous n'avons pas encore dit au routeur quoi faire avec l'URL `/`, donc il renvoie un template vide.
Pour démarrer, nous pouvons retrouver notre ancien comportement en assignant l'URL racine `/` au template `postList`. Nous allons créer un répertoire `/lib` dans la racine du projet, et à l'intérieur créer `router.js` :
~~~js
Router.configure({
layoutTemplate: 'layout'
});
Router.map(function() {
this.route('postsList', {path: '/'});
});
~~~
<%= caption "lib/router.js"%>
Nous avons effectué deux choses importantes. Premièrement, nous avons dit au routeur d'utiliser le layout que nous venons juste de créer comme layout par défaut pour toutes les routes. Deuxièmement, nous avons défini une nouvelle route appelée `postList` et on l'a assignée au chemin `/`.
<% note do %>
### Le répertoire `/lib`
Quel que soit ce que vous mettez dans le répertoire `/lib`, c'est garanti d'être chargé en premier avant tous les autres fichiers de votre application (avec comme exception possible les paquets intelligents). Ceci en fait une place de choix pour y mettre un helper qui a besoin d'être disponible en permanence.
Une petite mise en garde : notez que le répertoire `/lib` n'est ni dans `/client` ni dans `/server`, cela signifie que le contenu sera disponible dans les deux environnements.
<% end %>
### Routes nommées
Eclaircissons un peu l'ambiguité ici. Nous avons nommé notre route `postList`, mais nous avons également un *template* appelé `postList`. Donc qu'est-ce qu'il va se passer ici?
Par défaut, Iron Router va chercher un template avec le même nom que la route. En fait, il va même chercher un *chemin* basé sur le nom de la route, ce qui signifie que si on ne définit pas un chemin personnalisé (ce que nous avons fait en fournissant une option `path` dans la définition de notre route), notre template ne sera pas accessible à l'URL `/postList` par défaut.
Vous pouvez demander pourquoi nous avons quand même besoin de nommer nos routes dans un premier temps. Nommer les routes nous laisse utiliser quelques fonctionnalités de Iron Router qui nous rend plus facile la création de liens dans notre application. La plus utile est le helper Handlebars `{{pathFor}}`, qui retourne l'URL du composant chemin de la route.
Nous voulons que notre lien d'accueil principal pointe vers la liste d'articles, donc au lieu de spécifier une URL statique `/`, nous allons pouvoir utiliser le helper Handlebars. Le résultat final sera le même, mais cela nous donne plus de flexibilité quand le helper nous renverra toujours la bonne URL même si nous changeons le chemin de la route dans le routeur.
~~~html
<header class="navbar">
<div class="navbar-inner">
<a class="brand" href="{{pathFor 'postsList'}}">Microscope</a>
</div>
</header>
//...
~~~
<%= caption "client/views/application/layout.html"%>
<%= highlight "3" %>
<%= commit "5-1", "Routage très basique." %>
### Attente de données
Si vous déployez la version courante de l'application (ou lancez l'instance en utilisant le lien au-dessus), vous noterez que la liste apparaît vide un petit moment avant que les articles apparaissent. C'est parce que quand la page se charge la première fois, il n'y a pas d'articles à afficher jusqu'à que la souscription aux `articles` soit terminée, récupérant les données des articles du serveur.
Ce serait une bien meilleure experience de fournir un indicateur visuel que quelque chose est en train de se passer, et que l'utilisateur doit attendre un moment.
Par chance, Iron Router nous donne un moyen facile de faire ça -- nous allons `waitOn` la souscription :
~~~js
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
waitOn: function() { return Meteor.subscribe('posts'); }
});
Router.map(function() {
this.route('postsList', {path: '/'});
});
Router.onBeforeAction('loading');
~~~
<%= caption "lib/router.js" %>
<%= highlight "3,4, 11" %>
Décomposons les choses. Premièrement, nous avons modifié le bloc `Router.configure()` pour fournir au routeur le nom du template de chargement (que nous allons créer plus tard) à rediriger pendant que notre application attend les données.
Deuxièmement, nous avons également une fonction `waitOn`, qui retourne notre souscription à `posts`. Finalement, nous avons activé une accroche `loading` intégrée. Ce que cela signifie est que le routeur va s'assurer que la souscription à `posts` est chargée avant l'envoi à l'utilisateur au travers de la route qui a été demandée.
Notez que depuis que nous définissons notre fonction `waitOn` globalement au niveau du routeur, la séquence se passera seulement une fois que l'utilisateur accèdera la première fois à l'application. Après ça, les données seront déjà chargées dans la mémoire du navigateur et le routeur n'aura pas besoin de les attendre une nouvelle fois.
Et maintenant que nous laissons le routeur gérer notre souscription, vous pouvez tranquillement la supprimer de `main.js` (lequel doit maintenant être vide).
C'est habituellement une bonne idée d'attendre vos souscriptions, pas juste pour l'expérience utilisateur, mais également parce que vous supposez sans problème que les données seront toujours disponibles à l'intérieur du template. Cela élimine le besoin d'échanger avec des templates en train d'être rendu avant que leur données associées soit disponibles, ce qui requiert toujours des moyens de contournements hasardeux.
Nous allons également ajouter un filtre `onBeforeAction` pour déclencher le grappin `loading` natif d'Iron Router et nous assurer que nous affichons bien le template de chargement durant l'attente.
La pièce finale du puzzle est le template de chargement. Nous allons utiliser le paquet `spin` pour créer un joli indicateur de chargement animé. Ajoutez le avec `mrt add spin`, et créer le template de `chargement` comme suit :
~~~html
<template name="loading">
{{>spinner}}
</template>
~~~
<%= caption "client/views/includes/loading.html" %>
Notez que `{{>spinner}}` est un partial contenu dans le paquet `spin`. Quand bien même ce partial "ne provient pas" de notre application, nous pouvons l'inclure comme n'importe quel autre template.
<%= commit "5-2", "Attendre la souscription aux articles." %>
<% note do %>
### Un premier aperçu sur la réactivité
La réativité est une partie essentielle de Meteor, et bien que nous n'y avons pas encore vraiment touché, notre template de chargement nous donne un premier aperçu de ce concept.
Rediriger vers un template de chargement si les données ne sont pas encore chargées est vraiment bien, mais comment le routeur sait quand rediriger l'utilisateur vers la bonne page une fois que les données arrivent?
Pour l'instant, disons juste que c'est exactement où la réactivité intervient, et restons-en là. Mais ne vous inquiétez pas, vous en apprendrez plus bientôt!
<% end %>
### Router vers un article spécifique
Maintenant que nous avons vu comment router vers le template `postsList`, ajoutons une route pour afficher le détail d'un seul article.
Il n'y a pas qu'un seul article : nous ne pouvons continuer et définir une route par article, sinon il y en aurait des milliers. Donc nous allons avoir besoin de mettre une seule route *dynamique*, et permettre à la route d'afficher n'importe quel article que l'on souhaite.
Pour commencer, nous allons créer un template qui renvoie simplement le même template d'article que nous avons utilisé dans la liste d'articles.
~~~html
<template name="postPage">
{{> postItem}}
</template>
~~~
<%= caption "client/views/posts/post_page.html" %>
Nous allons ajouter plus d'éléments dans le template plus tard (tels que les commentaires), mais pour l'instant il va simplement servir de coquille pour notre inclusion `postItem`.
Nous allons créer une autre route nommée, cette fois en associant les chemins d'URL de la forme `/posts/<ID>` au template `postPage` :
~~~js
Router.map(function() {
this.route('postsList', {path: '/'});
this.route('postPage', {
path: '/posts/:_id'
});
});
~~~
<%= caption "lib/router.js" %>
<%= highlight "4~6" %>
La syntaxe spéciale `:_id` dit au routeur deux choses : premièrement, faire correspondre n'importe quelle route de la forme `/posts/xyz/`, où "xyz" peut être n'importe quoi. Deuxièmement, mettre ce qui trouve à la place de xyz dans une propriété `_id` dans le tableau des `params` du routeur.
Notez que nous utilisons seulement `_id` par convention ici. Le routeur n'a pas de moyen de connaitre si ce que vous lui passez est un `_id`, ou juste une chaîne aléatoire de caractères.
Nous routons maintenant vers le template correct, mais il nous manque encore quelque chose : le routeur connaît l'`_id` de l'article que nous voulons afficher, mais le template n'a toujours pas d'indice. Donc comment peut-on combler ce fossé?
Heureusement, le routeur a une solution intégrée intelligente : il vous laisse spécifier un **contexte de données** (data context) de template. Vous pouvez imaginer le contexte de données comme l'intérieur d'un délicieux gateau fait de templates et de layouts. Tout simplement, c'est ce avec quoi vous remplissez votre template :
<%= diagram "router-diagram-2", "Le contexte de données.", "pull-center" %>
Dans notre cas, nous pouvons récupérer le bon contexte de données en regardant notre article basé sur l'`_id` récupéré dans l'URL :
~~~js
Router.map(function() {
this.route('postsList', {path: '/'});
this.route('postPage', {
path: '/posts/:_id',
data: function() { return Posts.findOne(this.params._id); }
});
});
~~~
<%= caption "lib/router.js" %>
<%= highlight "4~7" %>
A chaque fois qu'un utilisateur accède à cette route, nous trouverons l'article approprié et le passerons au template. Souvenez-vous que `findOne` retourne un seul article qui correspond à la requête, et que fournir juste un `_id` comme argument est un raccourci pour `{_id: id}`.
A l'intérieur de la fonction `data` d'une route, `this` correspond à la route courante correspondante, et nous pouvons utiliser `this.params` pour accéder aux parties nommées de la route (que nous avons indiqué en les préfixant avec `:` dans notre chemin).
<% note do %>
### En savoir plus à propos des contextes de données
En initialisant un *contexte de données* de template, nous pouvons contrôler la valeur de `this` dans les helpers de template.
C'est habituellement fait implicitement avec l'itérateur `{{#each}}`, qui renvoie automatiquement le contexte de données de chaque itération à l'item en cours d'itération :
~~~html
{{#each widgets}}
{{> widgetItem}}
{{/each}}
~~~
Mais nous pouvons également le faire explicitement en utilisant `{{#with}}`, qui dit simplement "prends cet objet, et applique lui le template suivant". Par exemple, nous pouvons écrire :
~~~html
{{#with myWidget}}
{{> widgetPage}}
{{/with}}
~~~
Il s'avère que vous pour obtenir le même résultat en passant le contexte comme *argument* dans l'appel de template. Et donc le code précédent peut être réécrit comme suit :
~~~js
{{> widgetPage myWidget}}
~~~
<% end %>
### En utilisant un Route Helper Dynamique Nommé
Enfin, nous devons nous assurer que nous pointons vers le bon endroit chaque fois que nous voulons joindre un article précis. De plus, nous pourrions faire quelque chose comme `<a href="/posts/{{_id}}">`, mais c'est plus fiable en utilisant un route helper.
Nous avons nommé la route article `postPage`, donc nous pouvons utiliser le helper `{{pathFor 'postPage'}}` :
~~~html
<template name="postItem">
<div class="post">
<div class="post-content">
<h3><a href="{{url}}">{{title}}</a><span>{{domain}}</span></h3>
</div>
<a href="{{pathFor 'postPage'}}" class="discuss btn">Discuss</a>
</div>
</template>
~~~
<%= caption "client/views/posts/post_item.html"%>
<%= highlight "6" %>
<%= commit "5-3", "Route vers la page d'un article." %>
Attendez, comment le routeur sait comment récupérer la partie `xyz` dans `/posts/xyz`? Après tout, nous ne lui passons aucun `_id`.
Il s'avère que Iron Router est assez intelligent pour le trouver par lui-même. Nous disons au routeur d'utiliser la route `postPage`, et le routeur sait que cette route requiert un `_id` de ce type (vu que c'est comment nous avons défini notre `path`).
Donc le routeur cherchera cet `_id` dans l'endroit disponible le plus logique : le data context du helper `{{pathFor postPage}}`, en d'autre mots `this`. Et il se trouve que notre `this` va correspondre à l'article, lequel (surprise!) possède une propriété `_id`.
Alternativement, nous pouvez également explicitement dire au routeur où vous aimeriez qu'il cherche la propriété `_id`, en passant un second argument au helper (i.e. `{{pathFor 'postPage' someOtherPost}}`). un usage pratique de ce modèle serait de récupérer le lien des articles précédents et suivants dans une liste, par exemple.
Pour voir si ça fonctionne correctement, naviguez dans la liste d'articles et cliquez sur un des liens 'Discuss'. Vous devriez voir quelque chose comme ça :
<%= screenshot "5-2", "Page d'un article." %>
<% note do %>
### HTML5 pushState
Une chose pour réaliser que ces changements d'URLs arrivent en utilisant [HTML5 pushState](https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Manipulating_the_browser_history?redirectlocale=en-US&redirectslug=Web%2FGuide%2FDOM%2FManipulating_the_browser_history).
Le routeur récupère les clics sur les URLs internes au site, et empêche le navigateur de naviguer à l'extérieur de l'application, en plus de faire les changements nécessaires à l'état de l'application.
Si tout fonctionne correctement la page devrait changer instantanément. En fait, parfois les choses changent si vite qu'une sorte de transition pourrait être nécessaire. C'est hors du champ de ce chapitre, mais un sujet tout de même interessant.
<% end %>