]> git.wh0rd.org - tt-rss.git/blob - lib/dojo/router/RouterBase.js.uncompressed.js
upgrade dojo to 1.8.3 (refs #570)
[tt-rss.git] / lib / dojo / router / RouterBase.js.uncompressed.js
1 define("dojo/router/RouterBase", [
2 "dojo/_base/declare",
3 "dojo/hash",
4 "dojo/topic"
5 ], function(declare, hash, topic){
6
7 // module:
8 // dojo/router/RouterBase
9
10 // Creating a basic trim to avoid needing the full dojo/string module
11 // similarly to dojo/_base/lang's trim
12 var trim;
13 if(String.prototype.trim){
14 trim = function(str){ return str.trim(); };
15 }else{
16 trim = function(str){ return str.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); };
17 }
18
19 // Firing of routes on the route object is always the same,
20 // no clean way to expose this on the prototype since it's for the
21 // internal router objects.
22 function fireRoute(params, currentPath, newPath){
23 var queue, isStopped, isPrevented, eventObj, i, l;
24
25 queue = this.callbackQueue;
26 isStopped = false;
27 isPrevented = false;
28 eventObj = {
29 stopImmediatePropagation: function(){ isStopped = true; },
30 preventDefault: function(){ isPrevented = true; },
31 oldPath: currentPath,
32 newPath: newPath,
33 params: params
34 };
35
36 for(i=0, l=queue.length; i<l; ++i){
37 if(!isStopped){
38 queue[i](eventObj);
39 }
40 }
41
42 return !isPrevented;
43 }
44
45 // Our actual class-like object
46 var RouterBase = declare(null, {
47 // summary:
48 // A module that allows one to easily map hash-based structures into
49 // callbacks. The router module is a singleton, offering one central
50 // point for all registrations of this type.
51 // example:
52 // | var router = new RouterBase({});
53 // | router.register("/widgets/:id", function(evt){
54 // | // If "/widgets/3" was matched,
55 // | // evt.params.id === "3"
56 // | xhr.get({
57 // | url: "/some/path/" + evt.params.id,
58 // | load: function(data){
59 // | // ...
60 // | }
61 // | });
62 // | });
63
64 _routes: null,
65 _routeIndex: null,
66 _started: false,
67 _currentPath: "",
68
69 idMatch: /:(\w[\w\d]*)/g,
70 idReplacement: "([^\\/]+)",
71 globMatch: /\*(\w[\w\d]*)/,
72 globReplacement: "(.+)",
73
74 constructor: function(kwArgs){
75 // A couple of safety initializations
76 this._routes = [];
77 this._routeIndex = {};
78
79 // Simple constructor-style "Decorate myself all over" for now
80 for(var i in kwArgs){
81 if(kwArgs.hasOwnProperty(i)){
82 this[i] = kwArgs[i];
83 }
84 }
85 },
86
87 register: function(/*String|RegExp*/ route, /*Function*/ callback){
88 // summary:
89 // Registers a route to a handling callback
90 // description:
91 // Given either a string or a regular expression, the router
92 // will monitor the page's hash and respond to changes that
93 // match the string or regex as provided.
94 //
95 // When provided a regex for the route:
96 //
97 // - Matching is performed, and the resulting capture groups
98 // are passed through to the callback as an array.
99 //
100 // When provided a string for the route:
101 //
102 // - The string is parsed as a URL-like structure, like
103 // "/foo/bar"
104 // - If any portions of that URL are prefixed with a colon
105 // (:), they will be parsed out and provided to the callback
106 // as properties of an object.
107 // - If the last piece of the URL-like structure is prefixed
108 // with a star (*) instead of a colon, it will be replaced in
109 // the resulting regex with a greedy (.+) match and
110 // anything remaining on the hash will be provided as a
111 // property on the object passed into the callback. Think of
112 // it like a basic means of globbing the end of a route.
113 // example:
114 // | router.register("/foo/:bar/*baz", function(object){
115 // | // If the hash was "/foo/abc/def/ghi",
116 // | // object.bar === "abc"
117 // | // object.baz === "def/ghi"
118 // | });
119 // returns: Object
120 // A plain JavaScript object to be used as a handle for
121 // either removing this specific callback's registration, as
122 // well as to add new callbacks with the same route initially
123 // used.
124 // route: String|RegExp
125 // A string or regular expression which will be used when
126 // monitoring hash changes.
127 // callback: Function
128 // When the hash matches a pattern as described in the route,
129 // this callback will be executed. It will receive an event
130 // object that will have several properties:
131 //
132 // - params: Either an array or object of properties pulled
133 // from the new hash
134 // - oldPath: The hash in its state before the change
135 // - newPath: The new hash being shifted to
136 // - preventDefault: A method that will stop hash changes
137 // from being actually applied to the active hash. This only
138 // works if the hash change was initiated using `router.go`,
139 // as changes initiated more directly to the location.hash
140 // property will already be in place
141 // - stopImmediatePropagation: When called, will stop any
142 // further bound callbacks on this particular route from
143 // being executed. If two distinct routes are bound that are
144 // different, but both happen to match the current hash in
145 // some way, this will *not* keep other routes from receiving
146 // notice of the change.
147
148 return this._registerRoute(route, callback);
149 },
150
151 registerBefore: function(/*String|RegExp*/ route, /*Function*/ callback){
152 // summary:
153 // Registers a route to a handling callback, except before
154 // any previously registered callbacks
155 // description:
156 // Much like the `register` method, `registerBefore` allows
157 // us to register route callbacks to happen before any
158 // previously registered callbacks. See the documentation for
159 // `register` for more details and examples.
160
161 return this._registerRoute(route, callback, true);
162 },
163
164 go: function(path, replace){
165 // summary:
166 // A simple pass-through to make changing the hash easy,
167 // without having to require dojo/hash directly. It also
168 // synchronously fires off any routes that match.
169 // example:
170 // | router.go("/foo/bar");
171
172 var applyChange;
173
174 path = trim(path);
175 applyChange = this._handlePathChange(path);
176
177 if(applyChange){
178 hash(path, replace);
179 }
180
181 return applyChange;
182 },
183
184 startup: function(){
185 // summary:
186 // This method must be called to activate the router. Until
187 // startup is called, no hash changes will trigger route
188 // callbacks.
189
190 if(this._started){ return; }
191
192 var self = this;
193
194 this._started = true;
195 this._handlePathChange(hash());
196 topic.subscribe("/dojo/hashchange", function(){
197 // No need to load all of lang for just this
198 self._handlePathChange.apply(self, arguments);
199 });
200 },
201
202 _handlePathChange: function(newPath){
203 var i, j, li, lj, routeObj, result,
204 allowChange, parameterNames, params,
205 routes = this._routes,
206 currentPath = this._currentPath;
207
208 if(!this._started || newPath === currentPath){ return allowChange; }
209
210 allowChange = true;
211
212 for(i=0, li=routes.length; i<li; ++i){
213 routeObj = routes[i];
214 result = routeObj.route.exec(newPath);
215
216 if(result){
217 if(routeObj.parameterNames){
218 parameterNames = routeObj.parameterNames;
219 params = {};
220
221 for(j=0, lj=parameterNames.length; j<lj; ++j){
222 params[parameterNames[j]] = result[j+1];
223 }
224 }else{
225 params = result.slice(1);
226 }
227 allowChange = routeObj.fire(params, currentPath, newPath);
228 }
229 }
230
231 if(allowChange){
232 this._currentPath = newPath;
233 }
234
235 return allowChange;
236 },
237
238 _convertRouteToRegExp: function(route){
239 // Sub in based on IDs and globs
240 route = route.replace(this.idMatch, this.idReplacement);
241 route = route.replace(this.globMatch, this.globReplacement);
242 // Make sure it's an exact match
243 route = "^" + route + "$";
244
245 return new RegExp(route);
246 },
247
248 _getParameterNames: function(route){
249 var idMatch = this.idMatch,
250 globMatch = this.globMatch,
251 parameterNames = [], match;
252
253 idMatch.lastIndex = 0;
254
255 while((match = idMatch.exec(route)) !== null){
256 parameterNames.push(match[1]);
257 }
258 if((match = globMatch.exec(route)) !== null){
259 parameterNames.push(match[1]);
260 }
261
262 return parameterNames.length > 0 ? parameterNames : null;
263 },
264
265 _indexRoutes: function(){
266 var i, l, route, routeIndex, routes = this._routes;
267
268 // Start a new route index
269 routeIndex = this._routeIndex = {};
270
271 // Set it up again
272 for(i=0, l=routes.length; i<l; ++i){
273 route = routes[i];
274 routeIndex[route.route] = i;
275 }
276 },
277
278 _registerRoute: function(/*String|RegExp*/route, /*Function*/callback, /*Boolean?*/isBefore){
279 var index, exists, routeObj, callbackQueue, removed,
280 self = this, routes = this._routes,
281 routeIndex = this._routeIndex;
282
283 // Try to fetch the route if it already exists.
284 // This works thanks to stringifying of regex
285 index = this._routeIndex[route];
286 exists = typeof index !== "undefined";
287 if(exists){
288 routeObj = routes[index];
289 }
290
291 // If we didn't get one, make a default start point
292 if(!routeObj){
293 routeObj = {
294 route: route,
295 callbackQueue: [],
296 fire: fireRoute
297 };
298 }
299
300 callbackQueue = routeObj.callbackQueue;
301
302 if(typeof route == "string"){
303 routeObj.parameterNames = this._getParameterNames(route);
304 routeObj.route = this._convertRouteToRegExp(route);
305 }
306
307 if(isBefore){
308 callbackQueue.unshift(callback);
309 }else{
310 callbackQueue.push(callback);
311 }
312
313 if(!exists){
314 index = routes.length;
315 routeIndex[route] = index;
316 routes.push(routeObj);
317 }
318
319 // Useful in a moment to keep from re-removing routes
320 removed = false;
321
322 return { // Object
323 remove: function(){
324 var i, l;
325
326 if(removed){ return; }
327
328 for(i=0, l=callbackQueue.length; i<l; ++i){
329 if(callbackQueue[i] === callback){
330 callbackQueue.splice(i, 1);
331 }
332 }
333
334
335 if(callbackQueue.length === 0){
336 routes.splice(index, 1);
337 self._indexRoutes();
338 }
339
340 removed = true;
341 },
342 register: function(callback, isBefore){
343 return self.register(route, callback, isBefore);
344 }
345 };
346 }
347 });
348
349 return RouterBase;
350 });