src/cm/media/js/lib/yui/yui_3.10.3/build/router/router-debug.js
changeset 525 89ef5ed3c48b
equal deleted inserted replaced
524:322d0feea350 525:89ef5ed3c48b
       
     1 /*
       
     2 YUI 3.10.3 (build 2fb5187)
       
     3 Copyright 2013 Yahoo! Inc. All rights reserved.
       
     4 Licensed under the BSD License.
       
     5 http://yuilibrary.com/license/
       
     6 */
       
     7 
       
     8 YUI.add('router', function (Y, NAME) {
       
     9 
       
    10 /**
       
    11 Provides URL-based routing using HTML5 `pushState()` or the location hash.
       
    12 
       
    13 @module app
       
    14 @submodule router
       
    15 @since 3.4.0
       
    16 **/
       
    17 
       
    18 var HistoryHash = Y.HistoryHash,
       
    19     QS          = Y.QueryString,
       
    20     YArray      = Y.Array,
       
    21 
       
    22     win = Y.config.win,
       
    23 
       
    24     // Holds all the active router instances. This supports the static
       
    25     // `dispatch()` method which causes all routers to dispatch.
       
    26     instances = [],
       
    27 
       
    28     // We have to queue up pushState calls to avoid race conditions, since the
       
    29     // popstate event doesn't actually provide any info on what URL it's
       
    30     // associated with.
       
    31     saveQueue = [],
       
    32 
       
    33     /**
       
    34     Fired when the router is ready to begin dispatching to route handlers.
       
    35 
       
    36     You shouldn't need to wait for this event unless you plan to implement some
       
    37     kind of custom dispatching logic. It's used internally in order to avoid
       
    38     dispatching to an initial route if a browser history change occurs first.
       
    39 
       
    40     @event ready
       
    41     @param {Boolean} dispatched `true` if routes have already been dispatched
       
    42       (most likely due to a history change).
       
    43     @fireOnce
       
    44     **/
       
    45     EVT_READY = 'ready';
       
    46 
       
    47 /**
       
    48 Provides URL-based routing using HTML5 `pushState()` or the location hash.
       
    49 
       
    50 This makes it easy to wire up route handlers for different application states
       
    51 while providing full back/forward navigation support and bookmarkable, shareable
       
    52 URLs.
       
    53 
       
    54 @class Router
       
    55 @param {Object} [config] Config properties.
       
    56     @param {Boolean} [config.html5] Overrides the default capability detection
       
    57         and forces this router to use (`true`) or not use (`false`) HTML5
       
    58         history.
       
    59     @param {String} [config.root=''] Root path from which all routes should be
       
    60         evaluated.
       
    61     @param {Array} [config.routes=[]] Array of route definition objects.
       
    62 @constructor
       
    63 @extends Base
       
    64 @since 3.4.0
       
    65 **/
       
    66 function Router() {
       
    67     Router.superclass.constructor.apply(this, arguments);
       
    68 }
       
    69 
       
    70 Y.Router = Y.extend(Router, Y.Base, {
       
    71     // -- Protected Properties -------------------------------------------------
       
    72 
       
    73     /**
       
    74     Whether or not `_dispatch()` has been called since this router was
       
    75     instantiated.
       
    76 
       
    77     @property _dispatched
       
    78     @type Boolean
       
    79     @default undefined
       
    80     @protected
       
    81     **/
       
    82 
       
    83     /**
       
    84     Whether or not we're currently in the process of dispatching to routes.
       
    85 
       
    86     @property _dispatching
       
    87     @type Boolean
       
    88     @default undefined
       
    89     @protected
       
    90     **/
       
    91 
       
    92     /**
       
    93     History event handle for the `history:change` or `hashchange` event
       
    94     subscription.
       
    95 
       
    96     @property _historyEvents
       
    97     @type EventHandle
       
    98     @protected
       
    99     **/
       
   100 
       
   101     /**
       
   102     Cached copy of the `html5` attribute for internal use.
       
   103 
       
   104     @property _html5
       
   105     @type Boolean
       
   106     @protected
       
   107     **/
       
   108 
       
   109     /**
       
   110     Whether or not the `ready` event has fired yet.
       
   111 
       
   112     @property _ready
       
   113     @type Boolean
       
   114     @default undefined
       
   115     @protected
       
   116     **/
       
   117 
       
   118     /**
       
   119     Regex used to match parameter placeholders in route paths.
       
   120 
       
   121     Subpattern captures:
       
   122 
       
   123       1. Parameter prefix character. Either a `:` for subpath parameters that
       
   124          should only match a single level of a path, or `*` for splat parameters
       
   125          that should match any number of path levels.
       
   126 
       
   127       2. Parameter name, if specified, otherwise it is a wildcard match.
       
   128 
       
   129     @property _regexPathParam
       
   130     @type RegExp
       
   131     @protected
       
   132     **/
       
   133     _regexPathParam: /([:*])([\w\-]+)?/g,
       
   134 
       
   135     /**
       
   136     Regex that matches and captures the query portion of a URL, minus the
       
   137     preceding `?` character, and discarding the hash portion of the URL if any.
       
   138 
       
   139     @property _regexUrlQuery
       
   140     @type RegExp
       
   141     @protected
       
   142     **/
       
   143     _regexUrlQuery: /\?([^#]*).*$/,
       
   144 
       
   145     /**
       
   146     Regex that matches everything before the path portion of a URL (the origin).
       
   147     This will be used to strip this part of the URL from a string when we
       
   148     only want the path.
       
   149 
       
   150     @property _regexUrlOrigin
       
   151     @type RegExp
       
   152     @protected
       
   153     **/
       
   154     _regexUrlOrigin: /^(?:[^\/#?:]+:\/\/|\/\/)[^\/]*/,
       
   155 
       
   156     // -- Lifecycle Methods ----------------------------------------------------
       
   157     initializer: function (config) {
       
   158         var self = this;
       
   159 
       
   160         self._html5  = self.get('html5');
       
   161         self._routes = [];
       
   162         self._url    = self._getURL();
       
   163 
       
   164         // Necessary because setters don't run on init.
       
   165         self._setRoutes(config && config.routes ? config.routes :
       
   166                 self.get('routes'));
       
   167 
       
   168         // Set up a history instance or hashchange listener.
       
   169         if (self._html5) {
       
   170             self._history       = new Y.HistoryHTML5({force: true});
       
   171             self._historyEvents =
       
   172                     Y.after('history:change', self._afterHistoryChange, self);
       
   173         } else {
       
   174             self._historyEvents =
       
   175                     Y.on('hashchange', self._afterHistoryChange, win, self);
       
   176         }
       
   177 
       
   178         // Fire a `ready` event once we're ready to route. We wait first for all
       
   179         // subclass initializers to finish, then for window.onload, and then an
       
   180         // additional 20ms to allow the browser to fire a useless initial
       
   181         // `popstate` event if it wants to (and Chrome always wants to).
       
   182         self.publish(EVT_READY, {
       
   183             defaultFn  : self._defReadyFn,
       
   184             fireOnce   : true,
       
   185             preventable: false
       
   186         });
       
   187 
       
   188         self.once('initializedChange', function () {
       
   189             Y.once('load', function () {
       
   190                 setTimeout(function () {
       
   191                     self.fire(EVT_READY, {dispatched: !!self._dispatched});
       
   192                 }, 20);
       
   193             });
       
   194         });
       
   195 
       
   196         // Store this router in the collection of all active router instances.
       
   197         instances.push(this);
       
   198     },
       
   199 
       
   200     destructor: function () {
       
   201         var instanceIndex = YArray.indexOf(instances, this);
       
   202 
       
   203         // Remove this router from the collection of active router instances.
       
   204         if (instanceIndex > -1) {
       
   205             instances.splice(instanceIndex, 1);
       
   206         }
       
   207 
       
   208         if (this._historyEvents) {
       
   209             this._historyEvents.detach();
       
   210         }
       
   211     },
       
   212 
       
   213     // -- Public Methods -------------------------------------------------------
       
   214 
       
   215     /**
       
   216     Dispatches to the first route handler that matches the current URL, if any.
       
   217 
       
   218     If `dispatch()` is called before the `ready` event has fired, it will
       
   219     automatically wait for the `ready` event before dispatching. Otherwise it
       
   220     will dispatch immediately.
       
   221 
       
   222     @method dispatch
       
   223     @chainable
       
   224     **/
       
   225     dispatch: function () {
       
   226         this.once(EVT_READY, function () {
       
   227             this._ready = true;
       
   228 
       
   229             if (!this.upgrade()) {
       
   230                 this._dispatch(this._getPath(), this._getURL());
       
   231             }
       
   232         });
       
   233 
       
   234         return this;
       
   235     },
       
   236 
       
   237     /**
       
   238     Gets the current route path, relative to the `root` (if any).
       
   239 
       
   240     @method getPath
       
   241     @return {String} Current route path.
       
   242     **/
       
   243     getPath: function () {
       
   244         return this._getPath();
       
   245     },
       
   246 
       
   247     /**
       
   248     Returns `true` if this router has at least one route that matches the
       
   249     specified URL, `false` otherwise.
       
   250 
       
   251     This method enforces the same-origin security constraint on the specified
       
   252     `url`; any URL which is not from the same origin as the current URL will
       
   253     always return `false`.
       
   254 
       
   255     @method hasRoute
       
   256     @param {String} url URL to match.
       
   257     @return {Boolean} `true` if there's at least one matching route, `false`
       
   258       otherwise.
       
   259     **/
       
   260     hasRoute: function (url) {
       
   261         var path;
       
   262 
       
   263         if (!this._hasSameOrigin(url)) {
       
   264             return false;
       
   265         }
       
   266 
       
   267         if (!this._html5) {
       
   268             url = this._upgradeURL(url);
       
   269         }
       
   270 
       
   271         path = this.removeQuery(this.removeRoot(url));
       
   272 
       
   273         return !!this.match(path).length;
       
   274     },
       
   275 
       
   276     /**
       
   277     Returns an array of route objects that match the specified URL path.
       
   278 
       
   279     This method is called internally to determine which routes match the current
       
   280     path whenever the URL changes. You may override it if you want to customize
       
   281     the route matching logic, although this usually shouldn't be necessary.
       
   282 
       
   283     Each returned route object has the following properties:
       
   284 
       
   285       * `callback`: A function or a string representing the name of a function
       
   286         this router that should be executed when the route is triggered.
       
   287 
       
   288       * `keys`: An array of strings representing the named parameters defined in
       
   289         the route's path specification, if any.
       
   290 
       
   291       * `path`: The route's path specification, which may be either a string or
       
   292         a regex.
       
   293 
       
   294       * `regex`: A regular expression version of the route's path specification.
       
   295         This regex is used to determine whether the route matches a given path.
       
   296 
       
   297     @example
       
   298         router.route('/foo', function () {});
       
   299         router.match('/foo');
       
   300         // => [{callback: ..., keys: [], path: '/foo', regex: ...}]
       
   301 
       
   302     @method match
       
   303     @param {String} path URL path to match.
       
   304     @return {Object[]} Array of route objects that match the specified path.
       
   305     **/
       
   306     match: function (path) {
       
   307         return YArray.filter(this._routes, function (route) {
       
   308             return path.search(route.regex) > -1;
       
   309         });
       
   310     },
       
   311 
       
   312     /**
       
   313     Removes the `root` URL from the front of _url_ (if it's there) and returns
       
   314     the result. The returned path will always have a leading `/`.
       
   315 
       
   316     @method removeRoot
       
   317     @param {String} url URL.
       
   318     @return {String} Rootless path.
       
   319     **/
       
   320     removeRoot: function (url) {
       
   321         var root = this.get('root');
       
   322 
       
   323         // Strip out the non-path part of the URL, if any (e.g.
       
   324         // "http://foo.com"), so that we're left with just the path.
       
   325         url = url.replace(this._regexUrlOrigin, '');
       
   326 
       
   327         if (root && url.indexOf(root) === 0) {
       
   328             url = url.substring(root.length);
       
   329         }
       
   330 
       
   331         return url.charAt(0) === '/' ? url : '/' + url;
       
   332     },
       
   333 
       
   334     /**
       
   335     Removes a query string from the end of the _url_ (if one exists) and returns
       
   336     the result.
       
   337 
       
   338     @method removeQuery
       
   339     @param {String} url URL.
       
   340     @return {String} Queryless path.
       
   341     **/
       
   342     removeQuery: function (url) {
       
   343         return url.replace(/\?.*$/, '');
       
   344     },
       
   345 
       
   346     /**
       
   347     Replaces the current browser history entry with a new one, and dispatches to
       
   348     the first matching route handler, if any.
       
   349 
       
   350     Behind the scenes, this method uses HTML5 `pushState()` in browsers that
       
   351     support it (or the location hash in older browsers and IE) to change the
       
   352     URL.
       
   353 
       
   354     The specified URL must share the same origin (i.e., protocol, host, and
       
   355     port) as the current page, or an error will occur.
       
   356 
       
   357     @example
       
   358         // Starting URL: http://example.com/
       
   359 
       
   360         router.replace('/path/');
       
   361         // New URL: http://example.com/path/
       
   362 
       
   363         router.replace('/path?foo=bar');
       
   364         // New URL: http://example.com/path?foo=bar
       
   365 
       
   366         router.replace('/');
       
   367         // New URL: http://example.com/
       
   368 
       
   369     @method replace
       
   370     @param {String} [url] URL to set. This URL needs to be of the same origin as
       
   371       the current URL. This can be a URL relative to the router's `root`
       
   372       attribute. If no URL is specified, the page's current URL will be used.
       
   373     @chainable
       
   374     @see save()
       
   375     **/
       
   376     replace: function (url) {
       
   377         return this._queue(url, true);
       
   378     },
       
   379 
       
   380     /**
       
   381     Adds a route handler for the specified URL _path_.
       
   382 
       
   383     The _path_ parameter may be either a string or a regular expression. If it's
       
   384     a string, it may contain named parameters: `:param` will match any single
       
   385     part of a URL path (not including `/` characters), and `*param` will match
       
   386     any number of parts of a URL path (including `/` characters). These named
       
   387     parameters will be made available as keys on the `req.params` object that's
       
   388     passed to route handlers.
       
   389 
       
   390     If the _path_ parameter is a regex, all pattern matches will be made
       
   391     available as numbered keys on `req.params`, starting with `0` for the full
       
   392     match, then `1` for the first subpattern match, and so on.
       
   393 
       
   394     Here's a set of sample routes along with URL paths that they match:
       
   395 
       
   396       * Route: `/photos/:tag/:page`
       
   397         * URL: `/photos/kittens/1`, params: `{tag: 'kittens', page: '1'}`
       
   398         * URL: `/photos/puppies/2`, params: `{tag: 'puppies', page: '2'}`
       
   399 
       
   400       * Route: `/file/*path`
       
   401         * URL: `/file/foo/bar/baz.txt`, params: `{path: 'foo/bar/baz.txt'}`
       
   402         * URL: `/file/foo`, params: `{path: 'foo'}`
       
   403 
       
   404     **Middleware**: Routes also support an arbitrary number of callback
       
   405     functions. This allows you to easily reuse parts of your route-handling code
       
   406     with different route. This method is liberal in how it processes the
       
   407     specified `callbacks`, you can specify them as separate arguments, or as
       
   408     arrays, or both.
       
   409 
       
   410     If multiple route match a given URL, they will be executed in the order they
       
   411     were added. The first route that was added will be the first to be executed.
       
   412 
       
   413     **Passing Control**: Invoking the `next()` function within a route callback
       
   414     will pass control to the next callback function (if any) or route handler
       
   415     (if any). If a value is passed to `next()`, it's assumed to be an error,
       
   416     therefore stopping the dispatch chain, unless that value is: `"route"`,
       
   417     which is special case and dispatching will skip to the next route handler.
       
   418     This allows middleware to skip any remaining middleware for a particular
       
   419     route.
       
   420 
       
   421     @example
       
   422         router.route('/photos/:tag/:page', function (req, res, next) {
       
   423           Y.log('Current tag: ' + req.params.tag);
       
   424           Y.log('Current page number: ' + req.params.page);
       
   425         });
       
   426 
       
   427         // Using middleware.
       
   428 
       
   429         router.findUser = function (req, res, next) {
       
   430             req.user = this.get('users').findById(req.params.user);
       
   431             next();
       
   432         };
       
   433 
       
   434         router.route('/users/:user', 'findUser', function (req, res, next) {
       
   435             // The `findUser` middleware puts the `user` object on the `req`.
       
   436             Y.log('Current user:' req.user.get('name'));
       
   437         });
       
   438 
       
   439     @method route
       
   440     @param {String|RegExp} path Path to match. May be a string or a regular
       
   441       expression.
       
   442     @param {Array|Function|String} callbacks* Callback functions to call
       
   443         whenever this route is triggered. These can be specified as separate
       
   444         arguments, or in arrays, or both. If a callback is specified as a
       
   445         string, the named function will be called on this router instance.
       
   446 
       
   447       @param {Object} callbacks.req Request object containing information about
       
   448           the request. It contains the following properties.
       
   449 
       
   450         @param {Array|Object} callbacks.req.params Captured parameters matched by
       
   451           the route path specification. If a string path was used and contained
       
   452           named parameters, then this will be a key/value hash mapping parameter
       
   453           names to their matched values. If a regex path was used, this will be
       
   454           an array of subpattern matches starting at index 0 for the full match,
       
   455           then 1 for the first subpattern match, and so on.
       
   456         @param {String} callbacks.req.path The current URL path.
       
   457         @param {Number} callbacks.req.pendingCallbacks Number of remaining
       
   458           callbacks the route handler has after this one in the dispatch chain.
       
   459         @param {Number} callbacks.req.pendingRoutes Number of matching routes
       
   460           after this one in the dispatch chain.
       
   461         @param {Object} callbacks.req.query Query hash representing the URL
       
   462           query string, if any. Parameter names are keys, and are mapped to
       
   463           parameter values.
       
   464         @param {String} callbacks.req.url The full URL.
       
   465         @param {String} callbacks.req.src What initiated the dispatch. In an
       
   466           HTML5 browser, when the back/forward buttons are used, this property
       
   467           will have a value of "popstate".
       
   468 
       
   469       @param {Object} callbacks.res Response object containing methods and
       
   470           information that relate to responding to a request. It contains the
       
   471           following properties.
       
   472         @param {Object} callbacks.res.req Reference to the request object.
       
   473 
       
   474       @param {Function} callbacks.next Function to pass control to the next
       
   475           callback or the next matching route if no more callbacks (middleware)
       
   476           exist for the current route handler. If you don't call this function,
       
   477           then no further callbacks or route handlers will be executed, even if
       
   478           there are more that match. If you do call this function, then the next
       
   479           callback (if any) or matching route handler (if any) will be called.
       
   480           All of these functions will receive the same `req` and `res` objects
       
   481           that were passed to this route (so you can use these objects to pass
       
   482           data along to subsequent callbacks and routes).
       
   483         @param {String} [callbacks.next.err] Optional error which will stop the
       
   484           dispatch chaining for this `req`, unless the value is `"route"`, which
       
   485           is special cased to jump skip past any callbacks for the current route
       
   486           and pass control the next route handler.
       
   487     @chainable
       
   488     **/
       
   489     route: function (path, callbacks) {
       
   490         callbacks = YArray.flatten(YArray(arguments, 1, true));
       
   491 
       
   492         var keys = [];
       
   493 
       
   494         this._routes.push({
       
   495             callbacks: callbacks,
       
   496             keys     : keys,
       
   497             path     : path,
       
   498             regex    : this._getRegex(path, keys),
       
   499 
       
   500             // For back-compat.
       
   501             callback: callbacks[0]
       
   502         });
       
   503 
       
   504         return this;
       
   505     },
       
   506 
       
   507     /**
       
   508     Saves a new browser history entry and dispatches to the first matching route
       
   509     handler, if any.
       
   510 
       
   511     Behind the scenes, this method uses HTML5 `pushState()` in browsers that
       
   512     support it (or the location hash in older browsers and IE) to change the
       
   513     URL and create a history entry.
       
   514 
       
   515     The specified URL must share the same origin (i.e., protocol, host, and
       
   516     port) as the current page, or an error will occur.
       
   517 
       
   518     @example
       
   519         // Starting URL: http://example.com/
       
   520 
       
   521         router.save('/path/');
       
   522         // New URL: http://example.com/path/
       
   523 
       
   524         router.save('/path?foo=bar');
       
   525         // New URL: http://example.com/path?foo=bar
       
   526 
       
   527         router.save('/');
       
   528         // New URL: http://example.com/
       
   529 
       
   530     @method save
       
   531     @param {String} [url] URL to set. This URL needs to be of the same origin as
       
   532       the current URL. This can be a URL relative to the router's `root`
       
   533       attribute. If no URL is specified, the page's current URL will be used.
       
   534     @chainable
       
   535     @see replace()
       
   536     **/
       
   537     save: function (url) {
       
   538         return this._queue(url);
       
   539     },
       
   540 
       
   541     /**
       
   542     Upgrades a hash-based URL to an HTML5 URL if necessary. In non-HTML5
       
   543     browsers, this method is a noop.
       
   544 
       
   545     @method upgrade
       
   546     @return {Boolean} `true` if the URL was upgraded, `false` otherwise.
       
   547     **/
       
   548     upgrade: function () {
       
   549         if (!this._html5) {
       
   550             return false;
       
   551         }
       
   552 
       
   553         // Get the resolve hash path.
       
   554         var hashPath = this._getHashPath();
       
   555 
       
   556         if (hashPath) {
       
   557             // This is an HTML5 browser and we have a hash-based path in the
       
   558             // URL, so we need to upgrade the URL to a non-hash URL. This
       
   559             // will trigger a `history:change` event, which will in turn
       
   560             // trigger a dispatch.
       
   561             this.once(EVT_READY, function () {
       
   562                 this.replace(hashPath);
       
   563             });
       
   564 
       
   565             return true;
       
   566         }
       
   567 
       
   568         return false;
       
   569     },
       
   570 
       
   571     // -- Protected Methods ----------------------------------------------------
       
   572 
       
   573     /**
       
   574     Wrapper around `decodeURIComponent` that also converts `+` chars into
       
   575     spaces.
       
   576 
       
   577     @method _decode
       
   578     @param {String} string String to decode.
       
   579     @return {String} Decoded string.
       
   580     @protected
       
   581     **/
       
   582     _decode: function (string) {
       
   583         return decodeURIComponent(string.replace(/\+/g, ' '));
       
   584     },
       
   585 
       
   586     /**
       
   587     Shifts the topmost `_save()` call off the queue and executes it. Does
       
   588     nothing if the queue is empty.
       
   589 
       
   590     @method _dequeue
       
   591     @chainable
       
   592     @see _queue
       
   593     @protected
       
   594     **/
       
   595     _dequeue: function () {
       
   596         var self = this,
       
   597             fn;
       
   598 
       
   599         // If window.onload hasn't yet fired, wait until it has before
       
   600         // dequeueing. This will ensure that we don't call pushState() before an
       
   601         // initial popstate event has fired.
       
   602         if (!YUI.Env.windowLoaded) {
       
   603             Y.once('load', function () {
       
   604                 self._dequeue();
       
   605             });
       
   606 
       
   607             return this;
       
   608         }
       
   609 
       
   610         fn = saveQueue.shift();
       
   611         return fn ? fn() : this;
       
   612     },
       
   613 
       
   614     /**
       
   615     Dispatches to the first route handler that matches the specified _path_.
       
   616 
       
   617     If called before the `ready` event has fired, the dispatch will be aborted.
       
   618     This ensures normalized behavior between Chrome (which fires a `popstate`
       
   619     event on every pageview) and other browsers (which do not).
       
   620 
       
   621     @method _dispatch
       
   622     @param {String} path URL path.
       
   623     @param {String} url Full URL.
       
   624     @param {String} src What initiated the dispatch.
       
   625     @chainable
       
   626     @protected
       
   627     **/
       
   628     _dispatch: function (path, url, src) {
       
   629         var self      = this,
       
   630             decode    = self._decode,
       
   631             routes    = self.match(path),
       
   632             callbacks = [],
       
   633             matches, req, res;
       
   634 
       
   635         self._dispatching = self._dispatched = true;
       
   636 
       
   637         if (!routes || !routes.length) {
       
   638             self._dispatching = false;
       
   639             return self;
       
   640         }
       
   641 
       
   642         req = self._getRequest(path, url, src);
       
   643         res = self._getResponse(req);
       
   644 
       
   645         req.next = function (err) {
       
   646             var callback, name, route;
       
   647 
       
   648             if (err) {
       
   649                 // Special case "route" to skip to the next route handler
       
   650                 // avoiding any additional callbacks for the current route.
       
   651                 if (err === 'route') {
       
   652                     callbacks = [];
       
   653                     req.next();
       
   654                 } else {
       
   655                     Y.error(err);
       
   656                 }
       
   657 
       
   658             } else if ((callback = callbacks.shift())) {
       
   659                 if (typeof callback === 'string') {
       
   660                     name     = callback;
       
   661                     callback = self[name];
       
   662 
       
   663                     if (!callback) {
       
   664                         Y.error('Router: Callback not found: ' + name, null, 'router');
       
   665                     }
       
   666                 }
       
   667 
       
   668                 // Allow access to the number of remaining callbacks for the
       
   669                 // route.
       
   670                 req.pendingCallbacks = callbacks.length;
       
   671 
       
   672                 callback.call(self, req, res, req.next);
       
   673 
       
   674             } else if ((route = routes.shift())) {
       
   675                 // Make a copy of this route's `callbacks` so the original array
       
   676                 // is preserved.
       
   677                 callbacks = route.callbacks.concat();
       
   678 
       
   679                 // Decode each of the path matches so that the any URL-encoded
       
   680                 // path segments are decoded in the `req.params` object.
       
   681                 matches = YArray.map(route.regex.exec(path) || [], decode);
       
   682 
       
   683                 // Use named keys for parameter names if the route path contains
       
   684                 // named keys. Otherwise, use numerical match indices.
       
   685                 if (matches.length === route.keys.length + 1) {
       
   686                     req.params = YArray.hash(route.keys, matches.slice(1));
       
   687                 } else {
       
   688                     req.params = matches.concat();
       
   689                 }
       
   690 
       
   691                 // Allow access to the number of remaining routes for this
       
   692                 // request.
       
   693                 req.pendingRoutes = routes.length;
       
   694 
       
   695                 // Execute this route's `callbacks`.
       
   696                 req.next();
       
   697             }
       
   698         };
       
   699 
       
   700         req.next();
       
   701 
       
   702         self._dispatching = false;
       
   703         return self._dequeue();
       
   704     },
       
   705 
       
   706     /**
       
   707     Returns the resolved path from the hash fragment, or an empty string if the
       
   708     hash is not path-like.
       
   709 
       
   710     @method _getHashPath
       
   711     @param {String} [hash] Hash fragment to resolve into a path. By default this
       
   712         will be the hash from the current URL.
       
   713     @return {String} Current hash path, or an empty string if the hash is empty.
       
   714     @protected
       
   715     **/
       
   716     _getHashPath: function (hash) {
       
   717         hash || (hash = HistoryHash.getHash());
       
   718 
       
   719         // Make sure the `hash` is path-like.
       
   720         if (hash && hash.charAt(0) === '/') {
       
   721             return this._joinURL(hash);
       
   722         }
       
   723 
       
   724         return '';
       
   725     },
       
   726 
       
   727     /**
       
   728     Gets the location origin (i.e., protocol, host, and port) as a URL.
       
   729 
       
   730     @example
       
   731         http://example.com
       
   732 
       
   733     @method _getOrigin
       
   734     @return {String} Location origin (i.e., protocol, host, and port).
       
   735     @protected
       
   736     **/
       
   737     _getOrigin: function () {
       
   738         var location = Y.getLocation();
       
   739         return location.origin || (location.protocol + '//' + location.host);
       
   740     },
       
   741 
       
   742     /**
       
   743     Gets the current route path, relative to the `root` (if any).
       
   744 
       
   745     @method _getPath
       
   746     @return {String} Current route path.
       
   747     @protected
       
   748     **/
       
   749     _getPath: function () {
       
   750         var path = (!this._html5 && this._getHashPath()) ||
       
   751                 Y.getLocation().pathname;
       
   752 
       
   753         return this.removeQuery(this.removeRoot(path));
       
   754     },
       
   755 
       
   756     /**
       
   757     Returns the current path root after popping off the last path segment,
       
   758     making it useful for resolving other URL paths against.
       
   759 
       
   760     The path root will always begin and end with a '/'.
       
   761 
       
   762     @method _getPathRoot
       
   763     @return {String} The URL's path root.
       
   764     @protected
       
   765     @since 3.5.0
       
   766     **/
       
   767     _getPathRoot: function () {
       
   768         var slash = '/',
       
   769             path  = Y.getLocation().pathname,
       
   770             segments;
       
   771 
       
   772         if (path.charAt(path.length - 1) === slash) {
       
   773             return path;
       
   774         }
       
   775 
       
   776         segments = path.split(slash);
       
   777         segments.pop();
       
   778 
       
   779         return segments.join(slash) + slash;
       
   780     },
       
   781 
       
   782     /**
       
   783     Gets the current route query string.
       
   784 
       
   785     @method _getQuery
       
   786     @return {String} Current route query string.
       
   787     @protected
       
   788     **/
       
   789     _getQuery: function () {
       
   790         var location = Y.getLocation(),
       
   791             hash, matches;
       
   792 
       
   793         if (this._html5) {
       
   794             return location.search.substring(1);
       
   795         }
       
   796 
       
   797         hash    = HistoryHash.getHash();
       
   798         matches = hash.match(this._regexUrlQuery);
       
   799 
       
   800         return hash && matches ? matches[1] : location.search.substring(1);
       
   801     },
       
   802 
       
   803     /**
       
   804     Creates a regular expression from the given route specification. If _path_
       
   805     is already a regex, it will be returned unmodified.
       
   806 
       
   807     @method _getRegex
       
   808     @param {String|RegExp} path Route path specification.
       
   809     @param {Array} keys Array reference to which route parameter names will be
       
   810       added.
       
   811     @return {RegExp} Route regex.
       
   812     @protected
       
   813     **/
       
   814     _getRegex: function (path, keys) {
       
   815         if (path instanceof RegExp) {
       
   816             return path;
       
   817         }
       
   818 
       
   819         // Special case for catchall paths.
       
   820         if (path === '*') {
       
   821             return (/.*/);
       
   822         }
       
   823 
       
   824         path = path.replace(this._regexPathParam, function (match, operator, key) {
       
   825             // Only `*` operators are supported for key-less matches to allowing
       
   826             // in-path wildcards like: '/foo/*'.
       
   827             if (!key) {
       
   828                 return operator === '*' ? '.*' : match;
       
   829             }
       
   830 
       
   831             keys.push(key);
       
   832             return operator === '*' ? '(.*?)' : '([^/#?]*)';
       
   833         });
       
   834 
       
   835         return new RegExp('^' + path + '$');
       
   836     },
       
   837 
       
   838     /**
       
   839     Gets a request object that can be passed to a route handler.
       
   840 
       
   841     @method _getRequest
       
   842     @param {String} path Current path being dispatched.
       
   843     @param {String} url Current full URL being dispatched.
       
   844     @param {String} src What initiated the dispatch.
       
   845     @return {Object} Request object.
       
   846     @protected
       
   847     **/
       
   848     _getRequest: function (path, url, src) {
       
   849         return {
       
   850             path : path,
       
   851             query: this._parseQuery(this._getQuery()),
       
   852             url  : url,
       
   853             src  : src
       
   854         };
       
   855     },
       
   856 
       
   857     /**
       
   858     Gets a response object that can be passed to a route handler.
       
   859 
       
   860     @method _getResponse
       
   861     @param {Object} req Request object.
       
   862     @return {Object} Response Object.
       
   863     @protected
       
   864     **/
       
   865     _getResponse: function (req) {
       
   866         // For backwards compatibility, the response object is a function that
       
   867         // calls `next()` on the request object and returns the result.
       
   868         var res = function () {
       
   869             return req.next.apply(this, arguments);
       
   870         };
       
   871 
       
   872         res.req = req;
       
   873         return res;
       
   874     },
       
   875 
       
   876     /**
       
   877     Getter for the `routes` attribute.
       
   878 
       
   879     @method _getRoutes
       
   880     @return {Object[]} Array of route objects.
       
   881     @protected
       
   882     **/
       
   883     _getRoutes: function () {
       
   884         return this._routes.concat();
       
   885     },
       
   886 
       
   887     /**
       
   888     Gets the current full URL.
       
   889 
       
   890     @method _getURL
       
   891     @return {String} URL.
       
   892     @protected
       
   893     **/
       
   894     _getURL: function () {
       
   895         var url = Y.getLocation().toString();
       
   896 
       
   897         if (!this._html5) {
       
   898             url = this._upgradeURL(url);
       
   899         }
       
   900 
       
   901         return url;
       
   902     },
       
   903 
       
   904     /**
       
   905     Returns `true` when the specified `url` is from the same origin as the
       
   906     current URL; i.e., the protocol, host, and port of the URLs are the same.
       
   907 
       
   908     All host or path relative URLs are of the same origin. A scheme-relative URL
       
   909     is first prefixed with the current scheme before being evaluated.
       
   910 
       
   911     @method _hasSameOrigin
       
   912     @param {String} url URL to compare origin with the current URL.
       
   913     @return {Boolean} Whether the URL has the same origin of the current URL.
       
   914     @protected
       
   915     **/
       
   916     _hasSameOrigin: function (url) {
       
   917         var origin = ((url && url.match(this._regexUrlOrigin)) || [])[0];
       
   918 
       
   919         // Prepend current scheme to scheme-relative URLs.
       
   920         if (origin && origin.indexOf('//') === 0) {
       
   921             origin = Y.getLocation().protocol + origin;
       
   922         }
       
   923 
       
   924         return !origin || origin === this._getOrigin();
       
   925     },
       
   926 
       
   927     /**
       
   928     Joins the `root` URL to the specified _url_, normalizing leading/trailing
       
   929     `/` characters.
       
   930 
       
   931     @example
       
   932         router.set('root', '/foo');
       
   933         router._joinURL('bar');  // => '/foo/bar'
       
   934         router._joinURL('/bar'); // => '/foo/bar'
       
   935 
       
   936         router.set('root', '/foo/');
       
   937         router._joinURL('bar');  // => '/foo/bar'
       
   938         router._joinURL('/bar'); // => '/foo/bar'
       
   939 
       
   940     @method _joinURL
       
   941     @param {String} url URL to append to the `root` URL.
       
   942     @return {String} Joined URL.
       
   943     @protected
       
   944     **/
       
   945     _joinURL: function (url) {
       
   946         var root = this.get('root');
       
   947 
       
   948         // Causes `url` to _always_ begin with a "/".
       
   949         url = this.removeRoot(url);
       
   950 
       
   951         if (url.charAt(0) === '/') {
       
   952             url = url.substring(1);
       
   953         }
       
   954 
       
   955         return root && root.charAt(root.length - 1) === '/' ?
       
   956                 root + url :
       
   957                 root + '/' + url;
       
   958     },
       
   959 
       
   960     /**
       
   961     Returns a normalized path, ridding it of any '..' segments and properly
       
   962     handling leading and trailing slashes.
       
   963 
       
   964     @method _normalizePath
       
   965     @param {String} path URL path to normalize.
       
   966     @return {String} Normalized path.
       
   967     @protected
       
   968     @since 3.5.0
       
   969     **/
       
   970     _normalizePath: function (path) {
       
   971         var dots  = '..',
       
   972             slash = '/',
       
   973             i, len, normalized, segments, segment, stack;
       
   974 
       
   975         if (!path || path === slash) {
       
   976             return slash;
       
   977         }
       
   978 
       
   979         segments = path.split(slash);
       
   980         stack    = [];
       
   981 
       
   982         for (i = 0, len = segments.length; i < len; ++i) {
       
   983             segment = segments[i];
       
   984 
       
   985             if (segment === dots) {
       
   986                 stack.pop();
       
   987             } else if (segment) {
       
   988                 stack.push(segment);
       
   989             }
       
   990         }
       
   991 
       
   992         normalized = slash + stack.join(slash);
       
   993 
       
   994         // Append trailing slash if necessary.
       
   995         if (normalized !== slash && path.charAt(path.length - 1) === slash) {
       
   996             normalized += slash;
       
   997         }
       
   998 
       
   999         return normalized;
       
  1000     },
       
  1001 
       
  1002     /**
       
  1003     Parses a URL query string into a key/value hash. If `Y.QueryString.parse` is
       
  1004     available, this method will be an alias to that.
       
  1005 
       
  1006     @method _parseQuery
       
  1007     @param {String} query Query string to parse.
       
  1008     @return {Object} Hash of key/value pairs for query parameters.
       
  1009     @protected
       
  1010     **/
       
  1011     _parseQuery: QS && QS.parse ? QS.parse : function (query) {
       
  1012         var decode = this._decode,
       
  1013             params = query.split('&'),
       
  1014             i      = 0,
       
  1015             len    = params.length,
       
  1016             result = {},
       
  1017             param;
       
  1018 
       
  1019         for (; i < len; ++i) {
       
  1020             param = params[i].split('=');
       
  1021 
       
  1022             if (param[0]) {
       
  1023                 result[decode(param[0])] = decode(param[1] || '');
       
  1024             }
       
  1025         }
       
  1026 
       
  1027         return result;
       
  1028     },
       
  1029 
       
  1030     /**
       
  1031     Queues up a `_save()` call to run after all previously-queued calls have
       
  1032     finished.
       
  1033 
       
  1034     This is necessary because if we make multiple `_save()` calls before the
       
  1035     first call gets dispatched, then both calls will dispatch to the last call's
       
  1036     URL.
       
  1037 
       
  1038     All arguments passed to `_queue()` will be passed on to `_save()` when the
       
  1039     queued function is executed.
       
  1040 
       
  1041     @method _queue
       
  1042     @chainable
       
  1043     @see _dequeue
       
  1044     @protected
       
  1045     **/
       
  1046     _queue: function () {
       
  1047         var args = arguments,
       
  1048             self = this;
       
  1049 
       
  1050         saveQueue.push(function () {
       
  1051             if (self._html5) {
       
  1052                 if (Y.UA.ios && Y.UA.ios < 5) {
       
  1053                     // iOS <5 has buggy HTML5 history support, and needs to be
       
  1054                     // synchronous.
       
  1055                     self._save.apply(self, args);
       
  1056                 } else {
       
  1057                     // Wrapped in a timeout to ensure that _save() calls are
       
  1058                     // always processed asynchronously. This ensures consistency
       
  1059                     // between HTML5- and hash-based history.
       
  1060                     setTimeout(function () {
       
  1061                         self._save.apply(self, args);
       
  1062                     }, 1);
       
  1063                 }
       
  1064             } else {
       
  1065                 self._dispatching = true; // otherwise we'll dequeue too quickly
       
  1066                 self._save.apply(self, args);
       
  1067             }
       
  1068 
       
  1069             return self;
       
  1070         });
       
  1071 
       
  1072         return !this._dispatching ? this._dequeue() : this;
       
  1073     },
       
  1074 
       
  1075     /**
       
  1076     Returns the normalized result of resolving the `path` against the current
       
  1077     path. Falsy values for `path` will return just the current path.
       
  1078 
       
  1079     @method _resolvePath
       
  1080     @param {String} path URL path to resolve.
       
  1081     @return {String} Resolved path.
       
  1082     @protected
       
  1083     @since 3.5.0
       
  1084     **/
       
  1085     _resolvePath: function (path) {
       
  1086         if (!path) {
       
  1087             return Y.getLocation().pathname;
       
  1088         }
       
  1089 
       
  1090         if (path.charAt(0) !== '/') {
       
  1091             path = this._getPathRoot() + path;
       
  1092         }
       
  1093 
       
  1094         return this._normalizePath(path);
       
  1095     },
       
  1096 
       
  1097     /**
       
  1098     Resolves the specified URL against the current URL.
       
  1099 
       
  1100     This method resolves URLs like a browser does and will always return an
       
  1101     absolute URL. When the specified URL is already absolute, it is assumed to
       
  1102     be fully resolved and is simply returned as is. Scheme-relative URLs are
       
  1103     prefixed with the current protocol. Relative URLs are giving the current
       
  1104     URL's origin and are resolved and normalized against the current path root.
       
  1105 
       
  1106     @method _resolveURL
       
  1107     @param {String} url URL to resolve.
       
  1108     @return {String} Resolved URL.
       
  1109     @protected
       
  1110     @since 3.5.0
       
  1111     **/
       
  1112     _resolveURL: function (url) {
       
  1113         var parts    = url && url.match(this._regexURL),
       
  1114             origin, path, query, hash, resolved;
       
  1115 
       
  1116         if (!parts) {
       
  1117             return Y.getLocation().toString();
       
  1118         }
       
  1119 
       
  1120         origin = parts[1];
       
  1121         path   = parts[2];
       
  1122         query  = parts[3];
       
  1123         hash   = parts[4];
       
  1124 
       
  1125         // Absolute and scheme-relative URLs are assumed to be fully-resolved.
       
  1126         if (origin) {
       
  1127             // Prepend the current scheme for scheme-relative URLs.
       
  1128             if (origin.indexOf('//') === 0) {
       
  1129                 origin = Y.getLocation().protocol + origin;
       
  1130             }
       
  1131 
       
  1132             return origin + (path || '/') + (query || '') + (hash || '');
       
  1133         }
       
  1134 
       
  1135         // Will default to the current origin and current path.
       
  1136         resolved = this._getOrigin() + this._resolvePath(path);
       
  1137 
       
  1138         // A path or query for the specified URL trumps the current URL's.
       
  1139         if (path || query) {
       
  1140             return resolved + (query || '') + (hash || '');
       
  1141         }
       
  1142 
       
  1143         query = this._getQuery();
       
  1144 
       
  1145         return resolved + (query ? ('?' + query) : '') + (hash || '');
       
  1146     },
       
  1147 
       
  1148     /**
       
  1149     Saves a history entry using either `pushState()` or the location hash.
       
  1150 
       
  1151     This method enforces the same-origin security constraint; attempting to save
       
  1152     a `url` that is not from the same origin as the current URL will result in
       
  1153     an error.
       
  1154 
       
  1155     @method _save
       
  1156     @param {String} [url] URL for the history entry.
       
  1157     @param {Boolean} [replace=false] If `true`, the current history entry will
       
  1158       be replaced instead of a new one being added.
       
  1159     @chainable
       
  1160     @protected
       
  1161     **/
       
  1162     _save: function (url, replace) {
       
  1163         var urlIsString = typeof url === 'string',
       
  1164             currentPath, root, hash;
       
  1165 
       
  1166         // Perform same-origin check on the specified URL.
       
  1167         if (urlIsString && !this._hasSameOrigin(url)) {
       
  1168             Y.error('Security error: The new URL must be of the same origin as the current URL.');
       
  1169             return this;
       
  1170         }
       
  1171 
       
  1172         // Joins the `url` with the `root`.
       
  1173         if (urlIsString) {
       
  1174             url = this._joinURL(url);
       
  1175         }
       
  1176 
       
  1177         // Force _ready to true to ensure that the history change is handled
       
  1178         // even if _save is called before the `ready` event fires.
       
  1179         this._ready = true;
       
  1180 
       
  1181         if (this._html5) {
       
  1182             this._history[replace ? 'replace' : 'add'](null, {url: url});
       
  1183         } else {
       
  1184             currentPath = Y.getLocation().pathname;
       
  1185             root        = this.get('root');
       
  1186             hash        = HistoryHash.getHash();
       
  1187 
       
  1188             if (!urlIsString) {
       
  1189                 url = hash;
       
  1190             }
       
  1191 
       
  1192             // Determine if the `root` already exists in the current location's
       
  1193             // `pathname`, and if it does then we can exclude it from the
       
  1194             // hash-based path. No need to duplicate the info in the URL.
       
  1195             if (root === currentPath || root === this._getPathRoot()) {
       
  1196                 url = this.removeRoot(url);
       
  1197             }
       
  1198 
       
  1199             // The `hashchange` event only fires when the new hash is actually
       
  1200             // different. This makes sure we'll always dequeue and dispatch
       
  1201             // _all_ router instances, mimicking the HTML5 behavior.
       
  1202             if (url === hash) {
       
  1203                 Y.Router.dispatch();
       
  1204             } else {
       
  1205                 HistoryHash[replace ? 'replaceHash' : 'setHash'](url);
       
  1206             }
       
  1207         }
       
  1208 
       
  1209         return this;
       
  1210     },
       
  1211 
       
  1212     /**
       
  1213     Setter for the `routes` attribute.
       
  1214 
       
  1215     @method _setRoutes
       
  1216     @param {Object[]} routes Array of route objects.
       
  1217     @return {Object[]} Array of route objects.
       
  1218     @protected
       
  1219     **/
       
  1220     _setRoutes: function (routes) {
       
  1221         this._routes = [];
       
  1222 
       
  1223         YArray.each(routes, function (route) {
       
  1224             // Makes sure to check `callback` for back-compat.
       
  1225             var callbacks = route.callbacks || route.callback;
       
  1226 
       
  1227             this.route(route.path, callbacks);
       
  1228         }, this);
       
  1229 
       
  1230         return this._routes.concat();
       
  1231     },
       
  1232 
       
  1233     /**
       
  1234     Upgrades a hash-based URL to a full-path URL, if necessary.
       
  1235 
       
  1236     The specified `url` will be upgraded if its of the same origin as the
       
  1237     current URL and has a path-like hash. URLs that don't need upgrading will be
       
  1238     returned as-is.
       
  1239 
       
  1240     @example
       
  1241         app._upgradeURL('http://example.com/#/foo/'); // => 'http://example.com/foo/';
       
  1242 
       
  1243     @method _upgradeURL
       
  1244     @param {String} url The URL to upgrade from hash-based to full-path.
       
  1245     @return {String} The upgraded URL, or the specified URL untouched.
       
  1246     @protected
       
  1247     @since 3.5.0
       
  1248     **/
       
  1249     _upgradeURL: function (url) {
       
  1250         // We should not try to upgrade paths for external URLs.
       
  1251         if (!this._hasSameOrigin(url)) {
       
  1252             return url;
       
  1253         }
       
  1254 
       
  1255         var hash       = (url.match(/#(.*)$/) || [])[1] || '',
       
  1256             hashPrefix = Y.HistoryHash.hashPrefix,
       
  1257             hashPath;
       
  1258 
       
  1259         // Strip any hash prefix, like hash-bangs.
       
  1260         if (hashPrefix && hash.indexOf(hashPrefix) === 0) {
       
  1261             hash = hash.replace(hashPrefix, '');
       
  1262         }
       
  1263 
       
  1264         // If the hash looks like a URL path, assume it is, and upgrade it!
       
  1265         if (hash) {
       
  1266             hashPath = this._getHashPath(hash);
       
  1267 
       
  1268             if (hashPath) {
       
  1269                 return this._resolveURL(hashPath);
       
  1270             }
       
  1271         }
       
  1272 
       
  1273         return url;
       
  1274     },
       
  1275 
       
  1276     // -- Protected Event Handlers ---------------------------------------------
       
  1277 
       
  1278     /**
       
  1279     Handles `history:change` and `hashchange` events.
       
  1280 
       
  1281     @method _afterHistoryChange
       
  1282     @param {EventFacade} e
       
  1283     @protected
       
  1284     **/
       
  1285     _afterHistoryChange: function (e) {
       
  1286         var self       = this,
       
  1287             src        = e.src,
       
  1288             prevURL    = self._url,
       
  1289             currentURL = self._getURL();
       
  1290 
       
  1291         self._url = currentURL;
       
  1292 
       
  1293         // Handles the awkwardness that is the `popstate` event. HTML5 browsers
       
  1294         // fire `popstate` right before they fire `hashchange`, and Chrome fires
       
  1295         // `popstate` on page load. If this router is not ready or the previous
       
  1296         // and current URLs only differ by their hash, then we want to ignore
       
  1297         // this `popstate` event.
       
  1298         if (src === 'popstate' &&
       
  1299                 (!self._ready || prevURL.replace(/#.*$/, '') === currentURL.replace(/#.*$/, ''))) {
       
  1300 
       
  1301             return;
       
  1302         }
       
  1303 
       
  1304         self._dispatch(self._getPath(), currentURL, src);
       
  1305     },
       
  1306 
       
  1307     // -- Default Event Handlers -----------------------------------------------
       
  1308 
       
  1309     /**
       
  1310     Default handler for the `ready` event.
       
  1311 
       
  1312     @method _defReadyFn
       
  1313     @param {EventFacade} e
       
  1314     @protected
       
  1315     **/
       
  1316     _defReadyFn: function (e) {
       
  1317         this._ready = true;
       
  1318     }
       
  1319 }, {
       
  1320     // -- Static Properties ----------------------------------------------------
       
  1321     NAME: 'router',
       
  1322 
       
  1323     ATTRS: {
       
  1324         /**
       
  1325         Whether or not this browser is capable of using HTML5 history.
       
  1326 
       
  1327         Setting this to `false` will force the use of hash-based history even on
       
  1328         HTML5 browsers, but please don't do this unless you understand the
       
  1329         consequences.
       
  1330 
       
  1331         @attribute html5
       
  1332         @type Boolean
       
  1333         @initOnly
       
  1334         **/
       
  1335         html5: {
       
  1336             // Android versions lower than 3.0 are buggy and don't update
       
  1337             // window.location after a pushState() call, so we fall back to
       
  1338             // hash-based history for them.
       
  1339             //
       
  1340             // See http://code.google.com/p/android/issues/detail?id=17471
       
  1341             valueFn: function () { return Y.Router.html5; },
       
  1342             writeOnce: 'initOnly'
       
  1343         },
       
  1344 
       
  1345         /**
       
  1346         Absolute root path from which all routes should be evaluated.
       
  1347 
       
  1348         For example, if your router is running on a page at
       
  1349         `http://example.com/myapp/` and you add a route with the path `/`, your
       
  1350         route will never execute, because the path will always be preceded by
       
  1351         `/myapp`. Setting `root` to `/myapp` would cause all routes to be
       
  1352         evaluated relative to that root URL, so the `/` route would then execute
       
  1353         when the user browses to `http://example.com/myapp/`.
       
  1354 
       
  1355         @attribute root
       
  1356         @type String
       
  1357         @default `''`
       
  1358         **/
       
  1359         root: {
       
  1360             value: ''
       
  1361         },
       
  1362 
       
  1363         /**
       
  1364         Array of route objects.
       
  1365 
       
  1366         Each item in the array must be an object with the following properties:
       
  1367 
       
  1368           * `path`: String or regex representing the path to match. See the docs
       
  1369             for the `route()` method for more details.
       
  1370 
       
  1371           * `callbacks`: Function or a string representing the name of a
       
  1372             function on this router instance that should be called when the
       
  1373             route is triggered. An array of functions and/or strings may also be
       
  1374             provided. See the docs for the `route()` method for more details.
       
  1375 
       
  1376         This attribute is intended to be used to set routes at init time, or to
       
  1377         completely reset all routes after init. To add routes after init without
       
  1378         resetting all existing routes, use the `route()` method.
       
  1379 
       
  1380         @attribute routes
       
  1381         @type Object[]
       
  1382         @default `[]`
       
  1383         @see route
       
  1384         **/
       
  1385         routes: {
       
  1386             value : [],
       
  1387             getter: '_getRoutes',
       
  1388             setter: '_setRoutes'
       
  1389         }
       
  1390     },
       
  1391 
       
  1392     // Used as the default value for the `html5` attribute, and for testing.
       
  1393     html5: Y.HistoryBase.html5 && (!Y.UA.android || Y.UA.android >= 3),
       
  1394 
       
  1395     // To make this testable.
       
  1396     _instances: instances,
       
  1397 
       
  1398     /**
       
  1399     Dispatches to the first route handler that matches the specified `path` for
       
  1400     all active router instances.
       
  1401 
       
  1402     This provides a mechanism to cause all active router instances to dispatch
       
  1403     to their route handlers without needing to change the URL or fire the
       
  1404     `history:change` or `hashchange` event.
       
  1405 
       
  1406     @method dispatch
       
  1407     @static
       
  1408     @since 3.6.0
       
  1409     **/
       
  1410     dispatch: function () {
       
  1411         var i, len, router;
       
  1412 
       
  1413         for (i = 0, len = instances.length; i < len; i += 1) {
       
  1414             router = instances[i];
       
  1415 
       
  1416             if (router) {
       
  1417                 router._dispatch(router._getPath(), router._getURL());
       
  1418             }
       
  1419         }
       
  1420     }
       
  1421 });
       
  1422 
       
  1423 /**
       
  1424 The `Controller` class was deprecated in YUI 3.5.0 and is now an alias for the
       
  1425 `Router` class. Use that class instead. This alias will be removed in a future
       
  1426 version of YUI.
       
  1427 
       
  1428 @class Controller
       
  1429 @constructor
       
  1430 @extends Base
       
  1431 @deprecated Use `Router` instead.
       
  1432 @see Router
       
  1433 **/
       
  1434 Y.Controller = Y.Router;
       
  1435 
       
  1436 
       
  1437 }, '3.10.3', {"optional": ["querystring-parse"], "requires": ["array-extras", "base-build", "history"]});