]> git.wh0rd.org Git - 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 });