]>
git.wh0rd.org - tt-rss.git/blob - lib/dojo/router/RouterBase.js.uncompressed.js
1 define("dojo/router/RouterBase", [
5 ], function(declare
, hash
, topic
){
8 // dojo/router/RouterBase
10 // Creating a basic trim to avoid needing the full dojo/string module
11 // similarly to dojo/_base/lang's trim
13 if(String
.prototype.trim
){
14 trim = function(str
){ return str
.trim(); };
16 trim = function(str
){ return str
.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); };
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
;
25 queue
= this.callbackQueue
;
29 stopImmediatePropagation: function(){ isStopped
= true; },
30 preventDefault: function(){ isPrevented
= true; },
36 for(i
=0, l
=queue
.length
; i
<l
; ++i
){
45 // Our actual class-like object
46 var RouterBase
= declare(null, {
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.
52 // | var router = new RouterBase({});
53 // | router.register("/widgets/:id", function(evt){
54 // | // If "/widgets/3" was matched,
55 // | // evt.params.id === "3"
57 // | url: "/some/path/" + evt.params.id,
58 // | load: function(data){
69 idMatch
: /:(\w[\w\d]*)/g,
70 idReplacement
: "([^\\/]+)",
71 globMatch
: /\*(\w[\w\d]*)/,
72 globReplacement
: "(.+)",
74 constructor: function(kwArgs
){
75 // A couple of safety initializations
77 this._routeIndex
= {};
79 // Simple constructor-style "Decorate myself all over" for now
81 if(kwArgs
.hasOwnProperty(i
)){
87 register: function(/*String|RegExp*/ route
, /*Function*/ callback
){
89 // Registers a route to a handling callback
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.
95 // When provided a regex for the route:
97 // - Matching is performed, and the resulting capture groups
98 // are passed through to the callback as an array.
100 // When provided a string for the route:
102 // - The string is parsed as a URL-like structure, like
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.
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"
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
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:
132 // - params: Either an array or object of properties pulled
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.
148 return this._registerRoute(route
, callback
);
151 registerBefore: function(/*String|RegExp*/ route
, /*Function*/ callback
){
153 // Registers a route to a handling callback, except before
154 // any previously registered callbacks
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.
161 return this._registerRoute(route
, callback
, true);
164 go: function(path
, replace
){
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.
170 // | router.go("/foo/bar");
175 applyChange
= this._handlePathChange(path
);
186 // This method must be called to activate the router. Until
187 // startup is called, no hash changes will trigger route
190 if(this._started
){ return; }
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
);
202 _handlePathChange: function(newPath
){
203 var i
, j
, li
, lj
, routeObj
, result
,
204 allowChange
, parameterNames
, params
,
205 routes
= this._routes
,
206 currentPath
= this._currentPath
;
208 if(!this._started
|| newPath
=== currentPath
){ return allowChange
; }
212 for(i
=0, li
=routes
.length
; i
<li
; ++i
){
213 routeObj
= routes
[i
];
214 result
= routeObj
.route
.exec(newPath
);
217 if(routeObj
.parameterNames
){
218 parameterNames
= routeObj
.parameterNames
;
221 for(j
=0, lj
=parameterNames
.length
; j
<lj
; ++j
){
222 params
[parameterNames
[j
]] = result
[j
+1];
225 params
= result
.slice(1);
227 allowChange
= routeObj
.fire(params
, currentPath
, newPath
);
232 this._currentPath
= newPath
;
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
+ "$";
245 return new RegExp(route
);
248 _getParameterNames: function(route
){
249 var idMatch
= this.idMatch
,
250 globMatch
= this.globMatch
,
251 parameterNames
= [], match
;
253 idMatch
.lastIndex
= 0;
255 while((match
= idMatch
.exec(route
)) !== null){
256 parameterNames
.push(match
[1]);
258 if((match
= globMatch
.exec(route
)) !== null){
259 parameterNames
.push(match
[1]);
262 return parameterNames
.length
> 0 ? parameterNames
: null;
265 _indexRoutes: function(){
266 var i
, l
, route
, routeIndex
, routes
= this._routes
;
268 // Start a new route index
269 routeIndex
= this._routeIndex
= {};
272 for(i
=0, l
=routes
.length
; i
<l
; ++i
){
274 routeIndex
[route
.route
] = i
;
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
;
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";
288 routeObj
= routes
[index
];
291 // If we didn't get one, make a default start point
300 callbackQueue
= routeObj
.callbackQueue
;
302 if(typeof route
== "string"){
303 routeObj
.parameterNames
= this._getParameterNames(route
);
304 routeObj
.route
= this._convertRouteToRegExp(route
);
308 callbackQueue
.unshift(callback
);
310 callbackQueue
.push(callback
);
314 index
= routes
.length
;
315 routeIndex
[route
] = index
;
316 routes
.push(routeObj
);
319 // Useful in a moment to keep from re-removing routes
326 if(removed
){ return; }
328 for(i
=0, l
=callbackQueue
.length
; i
<l
; ++i
){
329 if(callbackQueue
[i
] === callback
){
330 callbackQueue
.splice(i
, 1);
335 if(callbackQueue
.length
=== 0){
336 routes
.splice(index
, 1);
342 register: function(callback
, isBefore
){
343 return self
.register(route
, callback
, isBefore
);