NOBUG: Fixed file access permissions
[moodle.git] / lib / yuilib / 3.13.0 / router / router.js
blobfcab9c62e67697e0b691a5c269bd2e18fb0f5e2a
1 /*
2 YUI 3.13.0 (build 508226d)
3 Copyright 2013 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
8 YUI.add('router', function (Y, NAME) {
10 /**
11 Provides URL-based routing using HTML5 `pushState()` or the location hash.
13 @module app
14 @submodule router
15 @since 3.4.0
16 **/
18 var HistoryHash = Y.HistoryHash,
19     QS          = Y.QueryString,
20     YArray      = Y.Array,
21     YLang       = Y.Lang,
22     YObject     = Y.Object,
24     win = Y.config.win,
26     // Holds all the active router instances. This supports the static
27     // `dispatch()` method which causes all routers to dispatch.
28     instances = [],
30     // We have to queue up pushState calls to avoid race conditions, since the
31     // popstate event doesn't actually provide any info on what URL it's
32     // associated with.
33     saveQueue = [],
35     /**
36     Fired when the router is ready to begin dispatching to route handlers.
38     You shouldn't need to wait for this event unless you plan to implement some
39     kind of custom dispatching logic. It's used internally in order to avoid
40     dispatching to an initial route if a browser history change occurs first.
42     @event ready
43     @param {Boolean} dispatched `true` if routes have already been dispatched
44       (most likely due to a history change).
45     @fireOnce
46     **/
47     EVT_READY = 'ready';
49 /**
50 Provides URL-based routing using HTML5 `pushState()` or the location hash.
52 This makes it easy to wire up route handlers for different application states
53 while providing full back/forward navigation support and bookmarkable, shareable
54 URLs.
56 @class Router
57 @param {Object} [config] Config properties.
58     @param {Boolean} [config.html5] Overrides the default capability detection
59         and forces this router to use (`true`) or not use (`false`) HTML5
60         history.
61     @param {String} [config.root=''] Root path from which all routes should be
62         evaluated.
63     @param {Array} [config.routes=[]] Array of route definition objects.
64 @constructor
65 @extends Base
66 @since 3.4.0
67 **/
68 function Router() {
69     Router.superclass.constructor.apply(this, arguments);
72 Y.Router = Y.extend(Router, Y.Base, {
73     // -- Protected Properties -------------------------------------------------
75     /**
76     Whether or not `_dispatch()` has been called since this router was
77     instantiated.
79     @property _dispatched
80     @type Boolean
81     @default undefined
82     @protected
83     **/
85     /**
86     Whether or not we're currently in the process of dispatching to routes.
88     @property _dispatching
89     @type Boolean
90     @default undefined
91     @protected
92     **/
94     /**
95     History event handle for the `history:change` or `hashchange` event
96     subscription.
98     @property _historyEvents
99     @type EventHandle
100     @protected
101     **/
103     /**
104     Cached copy of the `html5` attribute for internal use.
106     @property _html5
107     @type Boolean
108     @protected
109     **/
111     /**
112     Map which holds the registered param handlers in the form:
113     `name` -> RegExp | Function.
115     @property _params
116     @type Object
117     @protected
118     @since 3.12.0
119     **/
121     /**
122     Whether or not the `ready` event has fired yet.
124     @property _ready
125     @type Boolean
126     @default undefined
127     @protected
128     **/
130     /**
131     Regex used to break up a URL string around the URL's path.
133     Subpattern captures:
135       1. Origin, everything before the URL's path-part.
136       2. The URL's path-part.
137       3. The URL's query.
138       4. The URL's hash fragment.
140     @property _regexURL
141     @type RegExp
142     @protected
143     @since 3.5.0
144     **/
145     _regexURL: /^((?:[^\/#?:]+:\/\/|\/\/)[^\/]*)?([^?#]*)(\?[^#]*)?(#.*)?$/,
147     /**
148     Regex used to match parameter placeholders in route paths.
150     Subpattern captures:
152       1. Parameter prefix character. Either a `:` for subpath parameters that
153          should only match a single level of a path, or `*` for splat parameters
154          that should match any number of path levels.
156       2. Parameter name, if specified, otherwise it is a wildcard match.
158     @property _regexPathParam
159     @type RegExp
160     @protected
161     **/
162     _regexPathParam: /([:*])([\w\-]+)?/g,
164     /**
165     Regex that matches and captures the query portion of a URL, minus the
166     preceding `?` character, and discarding the hash portion of the URL if any.
168     @property _regexUrlQuery
169     @type RegExp
170     @protected
171     **/
172     _regexUrlQuery: /\?([^#]*).*$/,
174     /**
175     Regex that matches everything before the path portion of a URL (the origin).
176     This will be used to strip this part of the URL from a string when we
177     only want the path.
179     @property _regexUrlOrigin
180     @type RegExp
181     @protected
182     **/
183     _regexUrlOrigin: /^(?:[^\/#?:]+:\/\/|\/\/)[^\/]*/,
185     /**
186     Collection of registered routes.
188     @property _routes
189     @type Array
190     @protected
191     **/
193     // -- Lifecycle Methods ----------------------------------------------------
194     initializer: function (config) {
195         var self = this;
197         self._html5  = self.get('html5');
198         self._params = {};
199         self._routes = [];
200         self._url    = self._getURL();
202         // Necessary because setters don't run on init.
203         self._setRoutes(config && config.routes ? config.routes :
204                 self.get('routes'));
206         // Set up a history instance or hashchange listener.
207         if (self._html5) {
208             self._history       = new Y.HistoryHTML5({force: true});
209             self._historyEvents =
210                     Y.after('history:change', self._afterHistoryChange, self);
211         } else {
212             self._historyEvents =
213                     Y.on('hashchange', self._afterHistoryChange, win, self);
214         }
216         // Fire a `ready` event once we're ready to route. We wait first for all
217         // subclass initializers to finish, then for window.onload, and then an
218         // additional 20ms to allow the browser to fire a useless initial
219         // `popstate` event if it wants to (and Chrome always wants to).
220         self.publish(EVT_READY, {
221             defaultFn  : self._defReadyFn,
222             fireOnce   : true,
223             preventable: false
224         });
226         self.once('initializedChange', function () {
227             Y.once('load', function () {
228                 setTimeout(function () {
229                     self.fire(EVT_READY, {dispatched: !!self._dispatched});
230                 }, 20);
231             });
232         });
234         // Store this router in the collection of all active router instances.
235         instances.push(this);
236     },
238     destructor: function () {
239         var instanceIndex = YArray.indexOf(instances, this);
241         // Remove this router from the collection of active router instances.
242         if (instanceIndex > -1) {
243             instances.splice(instanceIndex, 1);
244         }
246         if (this._historyEvents) {
247             this._historyEvents.detach();
248         }
249     },
251     // -- Public Methods -------------------------------------------------------
253     /**
254     Dispatches to the first route handler that matches the current URL, if any.
256     If `dispatch()` is called before the `ready` event has fired, it will
257     automatically wait for the `ready` event before dispatching. Otherwise it
258     will dispatch immediately.
260     @method dispatch
261     @chainable
262     **/
263     dispatch: function () {
264         this.once(EVT_READY, function () {
265             var req, res;
267             this._ready = true;
269             if (!this.upgrade()) {
270                 req = this._getRequest('dispatch');
271                 res = this._getResponse(req);
273                 this._dispatch(req, res);
274             }
275         });
277         return this;
278     },
280     /**
281     Gets the current route path.
283     @method getPath
284     @return {String} Current route path.
285     **/
286     getPath: function () {
287         return this._getPath();
288     },
290     /**
291     Returns `true` if this router has at least one route that matches the
292     specified URL, `false` otherwise.
294     This method enforces the same-origin security constraint on the specified
295     `url`; any URL which is not from the same origin as the current URL will
296     always return `false`.
298     @method hasRoute
299     @param {String} url URL to match.
300     @return {Boolean} `true` if there's at least one matching route, `false`
301       otherwise.
302     **/
303     hasRoute: function (url) {
304         var path;
306         if (!this._hasSameOrigin(url)) {
307             return false;
308         }
310         if (!this._html5) {
311             url = this._upgradeURL(url);
312         }
314         // Get just the path portion of the specified `url`.
315         path = this.removeQuery(url.replace(this._regexUrlOrigin, ''));
317         return !!this.match(path).length;
318     },
320     /**
321     Returns an array of route objects that match the specified URL path.
323     If this router has a `root`, then the specified `path` _must_ be
324     semantically within the `root` path to match any routes.
326     This method is called internally to determine which routes match the current
327     path whenever the URL changes. You may override it if you want to customize
328     the route matching logic, although this usually shouldn't be necessary.
330     Each returned route object has the following properties:
332       * `callback`: A function or a string representing the name of a function
333         this router that should be executed when the route is triggered.
335       * `keys`: An array of strings representing the named parameters defined in
336         the route's path specification, if any.
338       * `path`: The route's path specification, which may be either a string or
339         a regex.
341       * `regex`: A regular expression version of the route's path specification.
342         This regex is used to determine whether the route matches a given path.
344     @example
345         router.route('/foo', function () {});
346         router.match('/foo');
347         // => [{callback: ..., keys: [], path: '/foo', regex: ...}]
349     @method match
350     @param {String} path URL path to match. This should be an absolute path that
351         starts with a slash: "/".
352     @return {Object[]} Array of route objects that match the specified path.
353     **/
354     match: function (path) {
355         var root = this.get('root');
357         if (root) {
358             // The `path` must be semantically within this router's `root` path
359             // or mount point, if it's not then no routes should be considered a
360             // match.
361             if (!this._pathHasRoot(root, path)) {
362                 return [];
363             }
365             // Remove this router's `root` from the `path` before checking the
366             // routes for any matches.
367             path = this.removeRoot(path);
368         }
370         return YArray.filter(this._routes, function (route) {
371             return path.search(route.regex) > -1;
372         });
373     },
375     /**
376     Adds a handler for a route param specified by _name_.
378     Param handlers can be registered via this method and are used to
379     validate/format values of named params in routes before dispatching to the
380     route's handler functions. Using param handlers allows routes to defined
381     using string paths which allows for `req.params` to use named params, but
382     still applying extra validation or formatting to the param values parsed
383     from the URL.
385     If a param handler regex or function returns a value of `false`, `null`,
386     `undefined`, or `NaN`, the current route will not match and be skipped. All
387     other return values will be used in place of the original param value parsed
388     from the URL.
390     @example
391         router.param('postId', function (value) {
392             return parseInt(value, 10);
393         });
395         router.param('username', /^\w+$/);
397         router.route('/posts/:postId', function (req) {
398         });
400         router.route('/users/:username', function (req) {
401             // `req.params.username` is an array because the result of calling
402             // `exec()` on the regex is assigned as the param's value.
403         });
405         router.route('*', function () {
406         });
408         // URLs which match routes:
409         router.save('/posts/1');     // => "Post: 1"
410         router.save('/users/ericf'); // => "User: ericf"
412         // URLs which do not match routes because params fail validation:
413         router.save('/posts/a');            // => "Catch-all no routes matched!"
414         router.save('/users/ericf,rgrove'); // => "Catch-all no routes matched!"
416     @method param
417     @param {String} name Name of the param used in route paths.
418     @param {Function|RegExp} handler Function to invoke or regular expression to
419         `exec()` during route dispatching whose return value is used as the new
420         param value. Values of `false`, `null`, `undefined`, or `NaN` will cause
421         the current route to not match and be skipped. When a function is
422         specified, it will be invoked in the context of this instance with the
423         following parameters:
424       @param {String} handler.value The current param value parsed from the URL.
425       @param {String} handler.name The name of the param.
426     @chainable
427     @since 3.12.0
428     **/
429     param: function (name, handler) {
430         this._params[name] = handler;
431         return this;
432     },
434     /**
435     Removes the `root` URL from the front of _url_ (if it's there) and returns
436     the result. The returned path will always have a leading `/`.
438     @method removeRoot
439     @param {String} url URL.
440     @return {String} Rootless path.
441     **/
442     removeRoot: function (url) {
443         var root = this.get('root'),
444             path;
446         // Strip out the non-path part of the URL, if any (e.g.
447         // "http://foo.com"), so that we're left with just the path.
448         url = url.replace(this._regexUrlOrigin, '');
450         // Return the host-less URL if there's no `root` path to further remove.
451         if (!root) {
452             return url;
453         }
455         path = this.removeQuery(url);
457         // Remove the `root` from the `url` if it's the same or its path is
458         // semantically within the root path.
459         if (path === root || this._pathHasRoot(root, path)) {
460             url = url.substring(root.length);
461         }
463         return url.charAt(0) === '/' ? url : '/' + url;
464     },
466     /**
467     Removes a query string from the end of the _url_ (if one exists) and returns
468     the result.
470     @method removeQuery
471     @param {String} url URL.
472     @return {String} Queryless path.
473     **/
474     removeQuery: function (url) {
475         return url.replace(/\?.*$/, '');
476     },
478     /**
479     Replaces the current browser history entry with a new one, and dispatches to
480     the first matching route handler, if any.
482     Behind the scenes, this method uses HTML5 `pushState()` in browsers that
483     support it (or the location hash in older browsers and IE) to change the
484     URL.
486     The specified URL must share the same origin (i.e., protocol, host, and
487     port) as the current page, or an error will occur.
489     @example
490         // Starting URL: http://example.com/
492         router.replace('/path/');
493         // New URL: http://example.com/path/
495         router.replace('/path?foo=bar');
496         // New URL: http://example.com/path?foo=bar
498         router.replace('/');
499         // New URL: http://example.com/
501     @method replace
502     @param {String} [url] URL to set. This URL needs to be of the same origin as
503       the current URL. This can be a URL relative to the router's `root`
504       attribute. If no URL is specified, the page's current URL will be used.
505     @chainable
506     @see save()
507     **/
508     replace: function (url) {
509         return this._queue(url, true);
510     },
512     /**
513     Adds a route handler for the specified `route`.
515     The `route` parameter may be a string or regular expression to represent a
516     URL path, or a route object. If it's a string (which is most common), it may
517     contain named parameters: `:param` will match any single part of a URL path
518     (not including `/` characters), and `*param` will match any number of parts
519     of a URL path (including `/` characters). These named parameters will be
520     made available as keys on the `req.params` object that's passed to route
521     handlers.
523     If the `route` parameter is a regex, all pattern matches will be made
524     available as numbered keys on `req.params`, starting with `0` for the full
525     match, then `1` for the first subpattern match, and so on.
527     Alternatively, an object can be provided to represent the route and it may
528     contain a `path` property which is a string or regular expression which
529     causes the route to be process as described above. If the route object
530     already contains a `regex` or `regexp` property, the route will be
531     considered fully-processed and will be associated with any `callacks`
532     specified on the object and those specified as parameters to this method.
533     **Note:** Any additional data contained on the route object will be
534     preserved.
536     Here's a set of sample routes along with URL paths that they match:
538       * Route: `/photos/:tag/:page`
539         * URL: `/photos/kittens/1`, params: `{tag: 'kittens', page: '1'}`
540         * URL: `/photos/puppies/2`, params: `{tag: 'puppies', page: '2'}`
542       * Route: `/file/*path`
543         * URL: `/file/foo/bar/baz.txt`, params: `{path: 'foo/bar/baz.txt'}`
544         * URL: `/file/foo`, params: `{path: 'foo'}`
546     **Middleware**: Routes also support an arbitrary number of callback
547     functions. This allows you to easily reuse parts of your route-handling code
548     with different route. This method is liberal in how it processes the
549     specified `callbacks`, you can specify them as separate arguments, or as
550     arrays, or both.
552     If multiple route match a given URL, they will be executed in the order they
553     were added. The first route that was added will be the first to be executed.
555     **Passing Control**: Invoking the `next()` function within a route callback
556     will pass control to the next callback function (if any) or route handler
557     (if any). If a value is passed to `next()`, it's assumed to be an error,
558     therefore stopping the dispatch chain, unless that value is: `"route"`,
559     which is special case and dispatching will skip to the next route handler.
560     This allows middleware to skip any remaining middleware for a particular
561     route.
563     @example
564         router.route('/photos/:tag/:page', function (req, res, next) {
565         });
567         // Using middleware.
569         router.findUser = function (req, res, next) {
570             req.user = this.get('users').findById(req.params.user);
571             next();
572         };
574         router.route('/users/:user', 'findUser', function (req, res, next) {
575             // The `findUser` middleware puts the `user` object on the `req`.
576         });
578     @method route
579     @param {String|RegExp|Object} route Route to match. May be a string or a
580       regular expression, or a route object.
581     @param {Array|Function|String} callbacks* Callback functions to call
582         whenever this route is triggered. These can be specified as separate
583         arguments, or in arrays, or both. If a callback is specified as a
584         string, the named function will be called on this router instance.
586       @param {Object} callbacks.req Request object containing information about
587           the request. It contains the following properties.
589         @param {Array|Object} callbacks.req.params Captured parameters matched
590           by the route path specification. If a string path was used and
591           contained named parameters, then this will be a key/value hash mapping
592           parameter names to their matched values. If a regex path was used,
593           this will be an array of subpattern matches starting at index 0 for
594           the full match, then 1 for the first subpattern match, and so on.
595         @param {String} callbacks.req.path The current URL path.
596         @param {Number} callbacks.req.pendingCallbacks Number of remaining
597           callbacks the route handler has after this one in the dispatch chain.
598         @param {Number} callbacks.req.pendingRoutes Number of matching routes
599           after this one in the dispatch chain.
600         @param {Object} callbacks.req.query Query hash representing the URL
601           query string, if any. Parameter names are keys, and are mapped to
602           parameter values.
603         @param {Object} callbacks.req.route Reference to the current route
604           object whose callbacks are being dispatched.
605         @param {Object} callbacks.req.router Reference to this router instance.
606         @param {String} callbacks.req.src What initiated the dispatch. In an
607           HTML5 browser, when the back/forward buttons are used, this property
608           will have a value of "popstate". When the `dispath()` method is
609           called, the `src` will be `"dispatch"`.
610         @param {String} callbacks.req.url The full URL.
612       @param {Object} callbacks.res Response object containing methods and
613           information that relate to responding to a request. It contains the
614           following properties.
615         @param {Object} callbacks.res.req Reference to the request object.
617       @param {Function} callbacks.next Function to pass control to the next
618           callback or the next matching route if no more callbacks (middleware)
619           exist for the current route handler. If you don't call this function,
620           then no further callbacks or route handlers will be executed, even if
621           there are more that match. If you do call this function, then the next
622           callback (if any) or matching route handler (if any) will be called.
623           All of these functions will receive the same `req` and `res` objects
624           that were passed to this route (so you can use these objects to pass
625           data along to subsequent callbacks and routes).
626         @param {String} [callbacks.next.err] Optional error which will stop the
627           dispatch chaining for this `req`, unless the value is `"route"`, which
628           is special cased to jump skip past any callbacks for the current route
629           and pass control the next route handler.
630     @chainable
631     **/
632     route: function (route, callbacks) {
633         // Grab callback functions from var-args.
634         callbacks = YArray(arguments, 1, true);
636         var keys, regex;
638         // Supports both the `route(path, callbacks)` and `route(config)` call
639         // signatures, allowing for fully-processed route configs to be passed.
640         if (typeof route === 'string' || YLang.isRegExp(route)) {
641             // Flatten `callbacks` into a single dimension array.
642             callbacks = YArray.flatten(callbacks);
644             keys  = [];
645             regex = this._getRegex(route, keys);
647             route = {
648                 callbacks: callbacks,
649                 keys     : keys,
650                 path     : route,
651                 regex    : regex
652             };
653         } else {
654             // Look for any configured `route.callbacks` and fallback to
655             // `route.callback` for back-compat, append var-arg `callbacks`,
656             // then flatten the entire collection to a single dimension array.
657             callbacks = YArray.flatten(
658                 [route.callbacks || route.callback || []].concat(callbacks)
659             );
661             // Check for previously generated regex, also fallback to `regexp`
662             // for greater interop.
663             keys  = route.keys;
664             regex = route.regex || route.regexp;
666             // Generates the route's regex if it doesn't already have one.
667             if (!regex) {
668                 keys  = [];
669                 regex = this._getRegex(route.path, keys);
670             }
672             // Merge specified `route` config object with processed data.
673             route = Y.merge(route, {
674                 callbacks: callbacks,
675                 keys     : keys,
676                 path     : route.path || regex,
677                 regex    : regex
678             });
679         }
681         this._routes.push(route);
682         return this;
683     },
685     /**
686     Saves a new browser history entry and dispatches to the first matching route
687     handler, if any.
689     Behind the scenes, this method uses HTML5 `pushState()` in browsers that
690     support it (or the location hash in older browsers and IE) to change the
691     URL and create a history entry.
693     The specified URL must share the same origin (i.e., protocol, host, and
694     port) as the current page, or an error will occur.
696     @example
697         // Starting URL: http://example.com/
699         router.save('/path/');
700         // New URL: http://example.com/path/
702         router.save('/path?foo=bar');
703         // New URL: http://example.com/path?foo=bar
705         router.save('/');
706         // New URL: http://example.com/
708     @method save
709     @param {String} [url] URL to set. This URL needs to be of the same origin as
710       the current URL. This can be a URL relative to the router's `root`
711       attribute. If no URL is specified, the page's current URL will be used.
712     @chainable
713     @see replace()
714     **/
715     save: function (url) {
716         return this._queue(url);
717     },
719     /**
720     Upgrades a hash-based URL to an HTML5 URL if necessary. In non-HTML5
721     browsers, this method is a noop.
723     @method upgrade
724     @return {Boolean} `true` if the URL was upgraded, `false` otherwise.
725     **/
726     upgrade: function () {
727         if (!this._html5) {
728             return false;
729         }
731         // Get the resolve hash path.
732         var hashPath = this._getHashPath();
734         if (hashPath) {
735             // This is an HTML5 browser and we have a hash-based path in the
736             // URL, so we need to upgrade the URL to a non-hash URL. This
737             // will trigger a `history:change` event, which will in turn
738             // trigger a dispatch.
739             this.once(EVT_READY, function () {
740                 this.replace(hashPath);
741             });
743             return true;
744         }
746         return false;
747     },
749     // -- Protected Methods ----------------------------------------------------
751     /**
752     Wrapper around `decodeURIComponent` that also converts `+` chars into
753     spaces.
755     @method _decode
756     @param {String} string String to decode.
757     @return {String} Decoded string.
758     @protected
759     **/
760     _decode: function (string) {
761         return decodeURIComponent(string.replace(/\+/g, ' '));
762     },
764     /**
765     Shifts the topmost `_save()` call off the queue and executes it. Does
766     nothing if the queue is empty.
768     @method _dequeue
769     @chainable
770     @see _queue
771     @protected
772     **/
773     _dequeue: function () {
774         var self = this,
775             fn;
777         // If window.onload hasn't yet fired, wait until it has before
778         // dequeueing. This will ensure that we don't call pushState() before an
779         // initial popstate event has fired.
780         if (!YUI.Env.windowLoaded) {
781             Y.once('load', function () {
782                 self._dequeue();
783             });
785             return this;
786         }
788         fn = saveQueue.shift();
789         return fn ? fn() : this;
790     },
792     /**
793     Dispatches to the first route handler that matches the specified _path_.
795     If called before the `ready` event has fired, the dispatch will be aborted.
796     This ensures normalized behavior between Chrome (which fires a `popstate`
797     event on every pageview) and other browsers (which do not).
799     @method _dispatch
800     @param {object} req Request object.
801     @param {String} res Response object.
802     @chainable
803     @protected
804     **/
805     _dispatch: function (req, res) {
806         var self      = this,
807             decode    = self._decode,
808             routes    = self.match(req.path),
809             callbacks = [],
810             matches, paramsMatch, routePath;
812         self._dispatching = self._dispatched = true;
814         if (!routes || !routes.length) {
815             self._dispatching = false;
816             return self;
817         }
819         routePath = self.removeRoot(req.path);
821         function next(err) {
822             var callback, name, route;
824             if (err) {
825                 // Special case "route" to skip to the next route handler
826                 // avoiding any additional callbacks for the current route.
827                 if (err === 'route') {
828                     callbacks = [];
829                     next();
830                 } else {
831                     Y.error(err);
832                 }
834             } else if ((callback = callbacks.shift())) {
835                 if (typeof callback === 'string') {
836                     name     = callback;
837                     callback = self[name];
839                     if (!callback) {
840                         Y.error('Router: Callback not found: ' + name, null, 'router');
841                     }
842                 }
844                 // Allow access to the number of remaining callbacks for the
845                 // route.
846                 req.pendingCallbacks = callbacks.length;
848                 callback.call(self, req, res, next);
850             } else if ((route = routes.shift())) {
851                 // Make a copy of this route's `callbacks` so the original array
852                 // is preserved.
853                 callbacks = route.callbacks.concat();
855                 // Decode each of the path matches so that the any URL-encoded
856                 // path segments are decoded in the `req.params` object.
857                 matches = YArray.map(route.regex.exec(routePath) || [],
858                         function (match) {
860                     // Decode matches, or coerce `undefined` matches to an empty
861                     // string to match expectations of working with `req.params`
862                     // in the content of route dispatching, and normalize
863                     // browser differences in their handling of regex NPCGs:
864                     // https://github.com/yui/yui3/issues/1076
865                     return (match && decode(match)) || '';
866                 });
868                 paramsMatch = true;
870                 // Use named keys for parameter names if the route path contains
871                 // named keys. Otherwise, use numerical match indices.
872                 if (matches.length === route.keys.length + 1) {
873                     matches    = matches.slice(1);
874                     req.params = YArray.hash(route.keys, matches);
876                     paramsMatch = YArray.every(route.keys, function (key, i) {
877                         var paramHandler = self._params[key],
878                             value        = matches[i];
880                         if (paramHandler && value && typeof value === 'string') {
881                             // Check if `paramHandler` is a RegExp, becuase this
882                             // is true in Android 2.3 and other browsers!
883                             // `typeof /.*/ === 'function'`
884                             value = YLang.isRegExp(paramHandler) ?
885                                     paramHandler.exec(value) :
886                                     paramHandler.call(self, value, key);
888                             if (value !== false && YLang.isValue(value)) {
889                                 req.params[key] = value;
890                                 return true;
891                             }
893                             return false;
894                         }
896                         return true;
897                     });
898                 } else {
899                     req.params = matches.concat();
900                 }
902                 // Allow access to current route and the number of remaining
903                 // routes for this request.
904                 req.route         = route;
905                 req.pendingRoutes = routes.length;
907                 // Execute this route's `callbacks` or skip this route because
908                 // some of the param regexps don't match.
909                 if (paramsMatch) {
910                     next();
911                 } else {
912                     next('route');
913                 }
914             }
915         }
917         next();
919         self._dispatching = false;
920         return self._dequeue();
921     },
923     /**
924     Returns the resolved path from the hash fragment, or an empty string if the
925     hash is not path-like.
927     @method _getHashPath
928     @param {String} [hash] Hash fragment to resolve into a path. By default this
929         will be the hash from the current URL.
930     @return {String} Current hash path, or an empty string if the hash is empty.
931     @protected
932     **/
933     _getHashPath: function (hash) {
934         hash || (hash = HistoryHash.getHash());
936         // Make sure the `hash` is path-like.
937         if (hash && hash.charAt(0) === '/') {
938             return this._joinURL(hash);
939         }
941         return '';
942     },
944     /**
945     Gets the location origin (i.e., protocol, host, and port) as a URL.
947     @example
948         http://example.com
950     @method _getOrigin
951     @return {String} Location origin (i.e., protocol, host, and port).
952     @protected
953     **/
954     _getOrigin: function () {
955         var location = Y.getLocation();
956         return location.origin || (location.protocol + '//' + location.host);
957     },
959     /**
960     Getter for the `params` attribute.
962     @method _getParams
963     @return {Object} Mapping of param handlers: `name` -> RegExp | Function.
964     @protected
965     @since 3.12.0
966     **/
967     _getParams: function () {
968         return Y.merge(this._params);
969     },
971     /**
972     Gets the current route path.
974     @method _getPath
975     @return {String} Current route path.
976     @protected
977     **/
978     _getPath: function () {
979         var path = (!this._html5 && this._getHashPath()) ||
980                 Y.getLocation().pathname;
982         return this.removeQuery(path);
983     },
985     /**
986     Returns the current path root after popping off the last path segment,
987     making it useful for resolving other URL paths against.
989     The path root will always begin and end with a '/'.
991     @method _getPathRoot
992     @return {String} The URL's path root.
993     @protected
994     @since 3.5.0
995     **/
996     _getPathRoot: function () {
997         var slash = '/',
998             path  = Y.getLocation().pathname,
999             segments;
1001         if (path.charAt(path.length - 1) === slash) {
1002             return path;
1003         }
1005         segments = path.split(slash);
1006         segments.pop();
1008         return segments.join(slash) + slash;
1009     },
1011     /**
1012     Gets the current route query string.
1014     @method _getQuery
1015     @return {String} Current route query string.
1016     @protected
1017     **/
1018     _getQuery: function () {
1019         var location = Y.getLocation(),
1020             hash, matches;
1022         if (this._html5) {
1023             return location.search.substring(1);
1024         }
1026         hash    = HistoryHash.getHash();
1027         matches = hash.match(this._regexUrlQuery);
1029         return hash && matches ? matches[1] : location.search.substring(1);
1030     },
1032     /**
1033     Creates a regular expression from the given route specification. If _path_
1034     is already a regex, it will be returned unmodified.
1036     @method _getRegex
1037     @param {String|RegExp} path Route path specification.
1038     @param {Array} keys Array reference to which route parameter names will be
1039       added.
1040     @return {RegExp} Route regex.
1041     @protected
1042     **/
1043     _getRegex: function (path, keys) {
1044         if (YLang.isRegExp(path)) {
1045             return path;
1046         }
1048         // Special case for catchall paths.
1049         if (path === '*') {
1050             return (/.*/);
1051         }
1053         path = path.replace(this._regexPathParam, function (match, operator, key) {
1054             // Only `*` operators are supported for key-less matches to allowing
1055             // in-path wildcards like: '/foo/*'.
1056             if (!key) {
1057                 return operator === '*' ? '.*' : match;
1058             }
1060             keys.push(key);
1061             return operator === '*' ? '(.*?)' : '([^/#?]*)';
1062         });
1064         return new RegExp('^' + path + '$');
1065     },
1067     /**
1068     Gets a request object that can be passed to a route handler.
1070     @method _getRequest
1071     @param {String} src What initiated the URL change and need for the request.
1072     @return {Object} Request object.
1073     @protected
1074     **/
1075     _getRequest: function (src) {
1076         return {
1077             path  : this._getPath(),
1078             query : this._parseQuery(this._getQuery()),
1079             url   : this._getURL(),
1080             router: this,
1081             src   : src
1082         };
1083     },
1085     /**
1086     Gets a response object that can be passed to a route handler.
1088     @method _getResponse
1089     @param {Object} req Request object.
1090     @return {Object} Response Object.
1091     @protected
1092     **/
1093     _getResponse: function (req) {
1094         return {req: req};
1095     },
1097     /**
1098     Getter for the `routes` attribute.
1100     @method _getRoutes
1101     @return {Object[]} Array of route objects.
1102     @protected
1103     **/
1104     _getRoutes: function () {
1105         return this._routes.concat();
1106     },
1108     /**
1109     Gets the current full URL.
1111     @method _getURL
1112     @return {String} URL.
1113     @protected
1114     **/
1115     _getURL: function () {
1116         var url = Y.getLocation().toString();
1118         if (!this._html5) {
1119             url = this._upgradeURL(url);
1120         }
1122         return url;
1123     },
1125     /**
1126     Returns `true` when the specified `url` is from the same origin as the
1127     current URL; i.e., the protocol, host, and port of the URLs are the same.
1129     All host or path relative URLs are of the same origin. A scheme-relative URL
1130     is first prefixed with the current scheme before being evaluated.
1132     @method _hasSameOrigin
1133     @param {String} url URL to compare origin with the current URL.
1134     @return {Boolean} Whether the URL has the same origin of the current URL.
1135     @protected
1136     **/
1137     _hasSameOrigin: function (url) {
1138         var origin = ((url && url.match(this._regexUrlOrigin)) || [])[0];
1140         // Prepend current scheme to scheme-relative URLs.
1141         if (origin && origin.indexOf('//') === 0) {
1142             origin = Y.getLocation().protocol + origin;
1143         }
1145         return !origin || origin === this._getOrigin();
1146     },
1148     /**
1149     Joins the `root` URL to the specified _url_, normalizing leading/trailing
1150     `/` characters.
1152     @example
1153         router.set('root', '/foo');
1154         router._joinURL('bar');  // => '/foo/bar'
1155         router._joinURL('/bar'); // => '/foo/bar'
1157         router.set('root', '/foo/');
1158         router._joinURL('bar');  // => '/foo/bar'
1159         router._joinURL('/bar'); // => '/foo/bar'
1161     @method _joinURL
1162     @param {String} url URL to append to the `root` URL.
1163     @return {String} Joined URL.
1164     @protected
1165     **/
1166     _joinURL: function (url) {
1167         var root = this.get('root');
1169         // Causes `url` to _always_ begin with a "/".
1170         url = this.removeRoot(url);
1172         if (url.charAt(0) === '/') {
1173             url = url.substring(1);
1174         }
1176         return root && root.charAt(root.length - 1) === '/' ?
1177                 root + url :
1178                 root + '/' + url;
1179     },
1181     /**
1182     Returns a normalized path, ridding it of any '..' segments and properly
1183     handling leading and trailing slashes.
1185     @method _normalizePath
1186     @param {String} path URL path to normalize.
1187     @return {String} Normalized path.
1188     @protected
1189     @since 3.5.0
1190     **/
1191     _normalizePath: function (path) {
1192         var dots  = '..',
1193             slash = '/',
1194             i, len, normalized, segments, segment, stack;
1196         if (!path || path === slash) {
1197             return slash;
1198         }
1200         segments = path.split(slash);
1201         stack    = [];
1203         for (i = 0, len = segments.length; i < len; ++i) {
1204             segment = segments[i];
1206             if (segment === dots) {
1207                 stack.pop();
1208             } else if (segment) {
1209                 stack.push(segment);
1210             }
1211         }
1213         normalized = slash + stack.join(slash);
1215         // Append trailing slash if necessary.
1216         if (normalized !== slash && path.charAt(path.length - 1) === slash) {
1217             normalized += slash;
1218         }
1220         return normalized;
1221     },
1223     /**
1224     Parses a URL query string into a key/value hash. If `Y.QueryString.parse` is
1225     available, this method will be an alias to that.
1227     @method _parseQuery
1228     @param {String} query Query string to parse.
1229     @return {Object} Hash of key/value pairs for query parameters.
1230     @protected
1231     **/
1232     _parseQuery: QS && QS.parse ? QS.parse : function (query) {
1233         var decode = this._decode,
1234             params = query.split('&'),
1235             i      = 0,
1236             len    = params.length,
1237             result = {},
1238             param;
1240         for (; i < len; ++i) {
1241             param = params[i].split('=');
1243             if (param[0]) {
1244                 result[decode(param[0])] = decode(param[1] || '');
1245             }
1246         }
1248         return result;
1249     },
1251     /**
1252     Returns `true` when the specified `path` is semantically within the
1253     specified `root` path.
1255     If the `root` does not end with a trailing slash ("/"), one will be added
1256     before the `path` is evaluated against the root path.
1258     @example
1259         this._pathHasRoot('/app',  '/app/foo'); // => true
1260         this._pathHasRoot('/app/', '/app/foo'); // => true
1261         this._pathHasRoot('/app/', '/app/');    // => true
1263         this._pathHasRoot('/app',  '/foo/bar'); // => false
1264         this._pathHasRoot('/app/', '/foo/bar'); // => false
1265         this._pathHasRoot('/app/', '/app');     // => false
1266         this._pathHasRoot('/app',  '/app');     // => false
1268     @method _pathHasRoot
1269     @param {String} root Root path used to evaluate whether the specificed
1270         `path` is semantically within. A trailing slash ("/") will be added if
1271         it does not already end with one.
1272     @param {String} path Path to evaluate for containing the specified `root`.
1273     @return {Boolean} Whether or not the `path` is semantically within the
1274         `root` path.
1275     @protected
1276     @since 3.13.0
1277     **/
1278     _pathHasRoot: function (root, path) {
1279         var rootPath = root.charAt(root.length - 1) === '/' ? root : root + '/';
1280         return path.indexOf(rootPath) === 0;
1281     },
1283     /**
1284     Queues up a `_save()` call to run after all previously-queued calls have
1285     finished.
1287     This is necessary because if we make multiple `_save()` calls before the
1288     first call gets dispatched, then both calls will dispatch to the last call's
1289     URL.
1291     All arguments passed to `_queue()` will be passed on to `_save()` when the
1292     queued function is executed.
1294     @method _queue
1295     @chainable
1296     @see _dequeue
1297     @protected
1298     **/
1299     _queue: function () {
1300         var args = arguments,
1301             self = this;
1303         saveQueue.push(function () {
1304             if (self._html5) {
1305                 if (Y.UA.ios && Y.UA.ios < 5) {
1306                     // iOS <5 has buggy HTML5 history support, and needs to be
1307                     // synchronous.
1308                     self._save.apply(self, args);
1309                 } else {
1310                     // Wrapped in a timeout to ensure that _save() calls are
1311                     // always processed asynchronously. This ensures consistency
1312                     // between HTML5- and hash-based history.
1313                     setTimeout(function () {
1314                         self._save.apply(self, args);
1315                     }, 1);
1316                 }
1317             } else {
1318                 self._dispatching = true; // otherwise we'll dequeue too quickly
1319                 self._save.apply(self, args);
1320             }
1322             return self;
1323         });
1325         return !this._dispatching ? this._dequeue() : this;
1326     },
1328     /**
1329     Returns the normalized result of resolving the `path` against the current
1330     path. Falsy values for `path` will return just the current path.
1332     @method _resolvePath
1333     @param {String} path URL path to resolve.
1334     @return {String} Resolved path.
1335     @protected
1336     @since 3.5.0
1337     **/
1338     _resolvePath: function (path) {
1339         if (!path) {
1340             return Y.getLocation().pathname;
1341         }
1343         if (path.charAt(0) !== '/') {
1344             path = this._getPathRoot() + path;
1345         }
1347         return this._normalizePath(path);
1348     },
1350     /**
1351     Resolves the specified URL against the current URL.
1353     This method resolves URLs like a browser does and will always return an
1354     absolute URL. When the specified URL is already absolute, it is assumed to
1355     be fully resolved and is simply returned as is. Scheme-relative URLs are
1356     prefixed with the current protocol. Relative URLs are giving the current
1357     URL's origin and are resolved and normalized against the current path root.
1359     @method _resolveURL
1360     @param {String} url URL to resolve.
1361     @return {String} Resolved URL.
1362     @protected
1363     @since 3.5.0
1364     **/
1365     _resolveURL: function (url) {
1366         var parts    = url && url.match(this._regexURL),
1367             origin, path, query, hash, resolved;
1369         if (!parts) {
1370             return Y.getLocation().toString();
1371         }
1373         origin = parts[1];
1374         path   = parts[2];
1375         query  = parts[3];
1376         hash   = parts[4];
1378         // Absolute and scheme-relative URLs are assumed to be fully-resolved.
1379         if (origin) {
1380             // Prepend the current scheme for scheme-relative URLs.
1381             if (origin.indexOf('//') === 0) {
1382                 origin = Y.getLocation().protocol + origin;
1383             }
1385             return origin + (path || '/') + (query || '') + (hash || '');
1386         }
1388         // Will default to the current origin and current path.
1389         resolved = this._getOrigin() + this._resolvePath(path);
1391         // A path or query for the specified URL trumps the current URL's.
1392         if (path || query) {
1393             return resolved + (query || '') + (hash || '');
1394         }
1396         query = this._getQuery();
1398         return resolved + (query ? ('?' + query) : '') + (hash || '');
1399     },
1401     /**
1402     Saves a history entry using either `pushState()` or the location hash.
1404     This method enforces the same-origin security constraint; attempting to save
1405     a `url` that is not from the same origin as the current URL will result in
1406     an error.
1408     @method _save
1409     @param {String} [url] URL for the history entry.
1410     @param {Boolean} [replace=false] If `true`, the current history entry will
1411       be replaced instead of a new one being added.
1412     @chainable
1413     @protected
1414     **/
1415     _save: function (url, replace) {
1416         var urlIsString = typeof url === 'string',
1417             currentPath, root, hash;
1419         // Perform same-origin check on the specified URL.
1420         if (urlIsString && !this._hasSameOrigin(url)) {
1421             Y.error('Security error: The new URL must be of the same origin as the current URL.');
1422             return this;
1423         }
1425         // Joins the `url` with the `root`.
1426         if (urlIsString) {
1427             url = this._joinURL(url);
1428         }
1430         // Force _ready to true to ensure that the history change is handled
1431         // even if _save is called before the `ready` event fires.
1432         this._ready = true;
1434         if (this._html5) {
1435             this._history[replace ? 'replace' : 'add'](null, {url: url});
1436         } else {
1437             currentPath = Y.getLocation().pathname;
1438             root        = this.get('root');
1439             hash        = HistoryHash.getHash();
1441             if (!urlIsString) {
1442                 url = hash;
1443             }
1445             // Determine if the `root` already exists in the current location's
1446             // `pathname`, and if it does then we can exclude it from the
1447             // hash-based path. No need to duplicate the info in the URL.
1448             if (root === currentPath || root === this._getPathRoot()) {
1449                 url = this.removeRoot(url);
1450             }
1452             // The `hashchange` event only fires when the new hash is actually
1453             // different. This makes sure we'll always dequeue and dispatch
1454             // _all_ router instances, mimicking the HTML5 behavior.
1455             if (url === hash) {
1456                 Y.Router.dispatch();
1457             } else {
1458                 HistoryHash[replace ? 'replaceHash' : 'setHash'](url);
1459             }
1460         }
1462         return this;
1463     },
1465     /**
1466     Setter for the `params` attribute.
1468     @method _setParams
1469     @param {Object} params Map in the form: `name` -> RegExp | Function.
1470     @return {Object} The map of params: `name` -> RegExp | Function.
1471     @protected
1472     @since 3.12.0
1473     **/
1474     _setParams: function (params) {
1475         this._params = {};
1477         YObject.each(params, function (regex, name) {
1478             this.param(name, regex);
1479         }, this);
1481         return Y.merge(this._params);
1482     },
1484     /**
1485     Setter for the `routes` attribute.
1487     @method _setRoutes
1488     @param {Object[]} routes Array of route objects.
1489     @return {Object[]} Array of route objects.
1490     @protected
1491     **/
1492     _setRoutes: function (routes) {
1493         this._routes = [];
1495         YArray.each(routes, function (route) {
1496             this.route(route);
1497         }, this);
1499         return this._routes.concat();
1500     },
1502     /**
1503     Upgrades a hash-based URL to a full-path URL, if necessary.
1505     The specified `url` will be upgraded if its of the same origin as the
1506     current URL and has a path-like hash. URLs that don't need upgrading will be
1507     returned as-is.
1509     @example
1510         app._upgradeURL('http://example.com/#/foo/'); // => 'http://example.com/foo/';
1512     @method _upgradeURL
1513     @param {String} url The URL to upgrade from hash-based to full-path.
1514     @return {String} The upgraded URL, or the specified URL untouched.
1515     @protected
1516     @since 3.5.0
1517     **/
1518     _upgradeURL: function (url) {
1519         // We should not try to upgrade paths for external URLs.
1520         if (!this._hasSameOrigin(url)) {
1521             return url;
1522         }
1524         var hash       = (url.match(/#(.*)$/) || [])[1] || '',
1525             hashPrefix = Y.HistoryHash.hashPrefix,
1526             hashPath;
1528         // Strip any hash prefix, like hash-bangs.
1529         if (hashPrefix && hash.indexOf(hashPrefix) === 0) {
1530             hash = hash.replace(hashPrefix, '');
1531         }
1533         // If the hash looks like a URL path, assume it is, and upgrade it!
1534         if (hash) {
1535             hashPath = this._getHashPath(hash);
1537             if (hashPath) {
1538                 return this._resolveURL(hashPath);
1539             }
1540         }
1542         return url;
1543     },
1545     // -- Protected Event Handlers ---------------------------------------------
1547     /**
1548     Handles `history:change` and `hashchange` events.
1550     @method _afterHistoryChange
1551     @param {EventFacade} e
1552     @protected
1553     **/
1554     _afterHistoryChange: function (e) {
1555         var self       = this,
1556             src        = e.src,
1557             prevURL    = self._url,
1558             currentURL = self._getURL(),
1559             req, res;
1561         self._url = currentURL;
1563         // Handles the awkwardness that is the `popstate` event. HTML5 browsers
1564         // fire `popstate` right before they fire `hashchange`, and Chrome fires
1565         // `popstate` on page load. If this router is not ready or the previous
1566         // and current URLs only differ by their hash, then we want to ignore
1567         // this `popstate` event.
1568         if (src === 'popstate' &&
1569                 (!self._ready || prevURL.replace(/#.*$/, '') === currentURL.replace(/#.*$/, ''))) {
1571             return;
1572         }
1574         req = self._getRequest(src);
1575         res = self._getResponse(req);
1577         self._dispatch(req, res);
1578     },
1580     // -- Default Event Handlers -----------------------------------------------
1582     /**
1583     Default handler for the `ready` event.
1585     @method _defReadyFn
1586     @param {EventFacade} e
1587     @protected
1588     **/
1589     _defReadyFn: function (e) {
1590         this._ready = true;
1591     }
1592 }, {
1593     // -- Static Properties ----------------------------------------------------
1594     NAME: 'router',
1596     ATTRS: {
1597         /**
1598         Whether or not this browser is capable of using HTML5 history.
1600         Setting this to `false` will force the use of hash-based history even on
1601         HTML5 browsers, but please don't do this unless you understand the
1602         consequences.
1604         @attribute html5
1605         @type Boolean
1606         @initOnly
1607         **/
1608         html5: {
1609             // Android versions lower than 3.0 are buggy and don't update
1610             // window.location after a pushState() call, so we fall back to
1611             // hash-based history for them.
1612             //
1613             // See http://code.google.com/p/android/issues/detail?id=17471
1614             valueFn: function () { return Y.Router.html5; },
1615             writeOnce: 'initOnly'
1616         },
1618         /**
1619         Map of params handlers in the form: `name` -> RegExp | Function.
1621         If a param handler regex or function returns a value of `false`, `null`,
1622         `undefined`, or `NaN`, the current route will not match and be skipped.
1623         All other return values will be used in place of the original param
1624         value parsed from the URL.
1626         This attribute is intended to be used to set params at init time, or to
1627         completely reset all params after init. To add params after init without
1628         resetting all existing params, use the `param()` method.
1630         @attribute params
1631         @type Object
1632         @default `{}`
1633         @see param
1634         @since 3.12.0
1635         **/
1636         params: {
1637             value : {},
1638             getter: '_getParams',
1639             setter: '_setParams'
1640         },
1642         /**
1643         Absolute root path from which all routes should be evaluated.
1645         For example, if your router is running on a page at
1646         `http://example.com/myapp/` and you add a route with the path `/`, your
1647         route will never execute, because the path will always be preceded by
1648         `/myapp`. Setting `root` to `/myapp` would cause all routes to be
1649         evaluated relative to that root URL, so the `/` route would then execute
1650         when the user browses to `http://example.com/myapp/`.
1652         @example
1653             router.set('root', '/myapp');
1654             router.route('/foo', function () { ... });
1657             // Updates the URL to: "/myapp/foo"
1658             router.save('/foo');
1660         @attribute root
1661         @type String
1662         @default `''`
1663         **/
1664         root: {
1665             value: ''
1666         },
1668         /**
1669         Array of route objects.
1671         Each item in the array must be an object with the following properties
1672         in order to be processed by the router:
1674           * `path`: String or regex representing the path to match. See the docs
1675             for the `route()` method for more details.
1677           * `callbacks`: Function or a string representing the name of a
1678             function on this router instance that should be called when the
1679             route is triggered. An array of functions and/or strings may also be
1680             provided. See the docs for the `route()` method for more details.
1682         If a route object contains a `regex` or `regexp` property, or if its
1683         `path` is a regular express, then the route will be considered to be
1684         fully-processed. Any fully-processed routes may contain the following
1685         properties:
1687           * `regex`: The regular expression representing the path to match, this
1688             property may also be named `regexp` for greater compatibility.
1690           * `keys`: Array of named path parameters used to populate `req.params`
1691             objects when dispatching to route handlers.
1693         Any additional data contained on these route objects will be retained.
1694         This is useful to store extra metadata about a route; e.g., a `name` to
1695         give routes logical names.
1697         This attribute is intended to be used to set routes at init time, or to
1698         completely reset all routes after init. To add routes after init without
1699         resetting all existing routes, use the `route()` method.
1701         @attribute routes
1702         @type Object[]
1703         @default `[]`
1704         @see route
1705         **/
1706         routes: {
1707             value : [],
1708             getter: '_getRoutes',
1709             setter: '_setRoutes'
1710         }
1711     },
1713     // Used as the default value for the `html5` attribute, and for testing.
1714     html5: Y.HistoryBase.html5 && (!Y.UA.android || Y.UA.android >= 3),
1716     // To make this testable.
1717     _instances: instances,
1719     /**
1720     Dispatches to the first route handler that matches the specified `path` for
1721     all active router instances.
1723     This provides a mechanism to cause all active router instances to dispatch
1724     to their route handlers without needing to change the URL or fire the
1725     `history:change` or `hashchange` event.
1727     @method dispatch
1728     @static
1729     @since 3.6.0
1730     **/
1731     dispatch: function () {
1732         var i, len, router, req, res;
1734         for (i = 0, len = instances.length; i < len; i += 1) {
1735             router = instances[i];
1737             if (router) {
1738                 req = router._getRequest('dispatch');
1739                 res = router._getResponse(req);
1741                 router._dispatch(req, res);
1742             }
1743         }
1744     }
1748 The `Controller` class was deprecated in YUI 3.5.0 and is now an alias for the
1749 `Router` class. Use that class instead. This alias will be removed in a future
1750 version of YUI.
1752 @class Controller
1753 @constructor
1754 @extends Base
1755 @deprecated Use `Router` instead.
1756 @see Router
1758 Y.Controller = Y.Router;
1761 }, '3.13.0', {"optional": ["querystring-parse"], "requires": ["array-extras", "base-build", "history"]});