-
Notifications
You must be signed in to change notification settings - Fork 1
/
Router.js
198 lines (160 loc) · 5.95 KB
/
Router.js
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
// on fait notre IIFE en passant la window en paramètre
(function (w) {
// on définit nos routes vides
var routes = [];
// on définit une route 404
var route404;
// on définit les links
var $links;
w.Router = {
on: addRoute,
start: start,
refreshLinks: refreshLinks
}
function start() {
// on écoute si l'historique change et on gère
w.addEventListener('popstate', matchAndHandleRoutes);
// on établit les liens
setLinks();
// on essaye de matcher l'URL en cours
matchAndHandleRoutes();
// je return "this" pour pouvoir chainer sur le router
// genre : Router.on(...).on(...).start()
return this;
}
function setLinks() {
// on récupère les liens
$links = document.querySelectorAll('.router-link');
// on converti on Array
$links = Array.prototype.slice.call($links);
// ou $links = Array.prototype.slice.apply($links);
// ou $links = [].slice.apply($links);
// ou $links = Array.apply(null, $links);
$links.forEach(function($link) {
$link.addEventListener('click', handleLinkClick);
});
}
function refreshLinks() {
// si on n'a pas ecore de lien, on ne fait rien
if (!$links) {
return;
}
// on arrête d'écouter les clicks sur les anciens liens
$links.forEach(function($link) {
$link.removeEventListener('click', handleLinkClick);
});
setLinks();
}
function handleLinkClick(event) {
event.preventDefault();
// on récupère l'URL actuel
var currentPath = w.location.pathname;
// ici, "this" représente l'élément en cours
var href = this.getAttribute('href');
// on navigue vers le href
w.history.pushState(null, null, href);
if (currentPath !== w.location.pathname) {
// on essaye de matche la nouvelle URL
// si l'URL a changé
matchAndHandleRoutes();
}
}
function matchAndHandleRoutes() {
var hasMatched = false;
var path = w.location.pathname;
// Matcher les routes: finissant avec / (e.g '/test/')
// et sans / (e.g '/test')
if (path[path.length - 1] !== '/') {
path += '/';
}
routes.forEach(function(route) {
// Générer une regex en fonction du path de la route sauvegardée
var regex = createRegexFromPath(route.path);
// Tester la regex obtenue sur le path actuel
if (regex.test(path)) {
hasMatched = true;
// Récupérer les paramètres de la route par rapport au path
var parameters = getParametersFromPath(path, route.parameters);
// Exécuter le handler de la route en lui passant les paramètres
route.handler.apply(null, parameters);
}
});
if (!hasMatched && typeof route404 === 'function') {
route404();
}
}
function addRoute(path, handler) {
// on vérifie que :
// - path est une string et qu'elle n'est pas vide
// - handler est une fonction
if (typeof path !== 'string') {
throw 'PATH_NOT_STRING';
} else if (path.length < 1) {
throw 'PATH_EMPTY';
} else if (typeof handler !== 'function') {
throw 'HANDLER_NOT_FUNC';
}
// si notre route est une "catch all"
// on enregistre juste la fonction en 404
if (path === '*') {
route404 = handler;
} else {
// si on n'a pas de slash, on ajoute
if (path[0] !== '/') {
path = '/' + path;
}
// Récupérer les paramètres de forme :nom dans le path
parameters = parsePathParams(path);
// tout est bon on peut push
routes.push({
path: path,
handler: handler,
parameters: parameters
});
}
// je return "this" pour pouvoir chainer sur le router
// genre : Router.on(...).on(...).start()
return this;
}
//
function parsePathParams(path) {
// Nettoyer le tableau des valeurs vides après le split des caractères "/"
var pathArguments = cleanArray(path.split('/'));
return pathArguments.reduce(function(accumulator, value, index) {
if (value.indexOf(':') >= 0) {
accumulator.push(index);
}
return accumulator;
}, []);
}
// Récupérer les paramètres d'un path à partir des paramètres sauvés à l'initialisation de la route
// (lors de l'exécution de la fonction addRoute / on)
// routeParameters format ["indexInPath:Integer": "value:String"]
function getParametersFromPath(path, routeParameters) {
var pathSplit = cleanArray(path.split('/'));
return routeParameters.reduce(function(accumulator, value) {
accumulator.push(pathSplit[value]);
return accumulator;
}, []);
}
// 'test/\(\\w+)\x2F$'
// '(\\w+)\x2Ftasks/(\\w+)/$'
function createRegexFromPath(path) {
var pathSplit = cleanArray(path.split('/'));
var reg = ['^/'];
// Loop on Uri parameters to build regex according to whether parameter is dynamic or static
pathSplit.forEach(function(param) {
return param.indexOf(':') >= 0 ? reg.push('(\\w+)/') : reg.push(param + '/');
});
// checks that "String Ends with"
reg.push('$');
var regexString = reg.join('');
return RegExp(regexString);
}
// Récupérer un nouveau tableau, dont les valeurs 'vides' ont été supprmé
function cleanArray(array) {
return array.filter(function(element) {
return element ? true : false
});
}
})(window);