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