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