[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
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"]});
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |