[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/yuilib/3.17.2/router/ -> router.js (source)

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


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1