2
YUI 3.10.3 (build 2fb5187)
3
Copyright 2013 Yahoo! Inc. All rights reserved.
4
Licensed under the BSD License.
5
http://yuilibrary.com/license/
8
YUI.add('router', function (Y, NAME) {
11
Provides URL-based routing using HTML5 `pushState()` or the location hash.
18
var HistoryHash = Y.HistoryHash,
24
// Holds all the active router instances. This supports the static
25
// `dispatch()` method which causes all routers to dispatch.
28
// We have to queue up pushState calls to avoid race conditions, since the
29
// popstate event doesn't actually provide any info on what URL it's
34
Fired when the router is ready to begin dispatching to route handlers.
36
You shouldn't need to wait for this event unless you plan to implement some
37
kind of custom dispatching logic. It's used internally in order to avoid
38
dispatching to an initial route if a browser history change occurs first.
41
@param {Boolean} dispatched `true` if routes have already been dispatched
42
(most likely due to a history change).
48
Provides URL-based routing using HTML5 `pushState()` or the location hash.
50
This makes it easy to wire up route handlers for different application states
51
while providing full back/forward navigation support and bookmarkable, shareable
55
@param {Object} [config] Config properties.
56
@param {Boolean} [config.html5] Overrides the default capability detection
57
and forces this router to use (`true`) or not use (`false`) HTML5
59
@param {String} [config.root=''] Root path from which all routes should be
61
@param {Array} [config.routes=[]] Array of route definition objects.
67
Router.superclass.constructor.apply(this, arguments);
70
Y.Router = Y.extend(Router, Y.Base, {
71
// -- Protected Properties -------------------------------------------------
74
Whether or not `_dispatch()` has been called since this router was
84
Whether or not we're currently in the process of dispatching to routes.
86
@property _dispatching
93
History event handle for the `history:change` or `hashchange` event
96
@property _historyEvents
102
Cached copy of the `html5` attribute for internal use.
110
Whether or not the `ready` event has fired yet.
119
Regex used to match parameter placeholders in route paths.
123
1. Parameter prefix character. Either a `:` for subpath parameters that
124
should only match a single level of a path, or `*` for splat parameters
125
that should match any number of path levels.
127
2. Parameter name, if specified, otherwise it is a wildcard match.
129
@property _regexPathParam
133
_regexPathParam: /([:*])([\w\-]+)?/g,
136
Regex that matches and captures the query portion of a URL, minus the
137
preceding `?` character, and discarding the hash portion of the URL if any.
139
@property _regexUrlQuery
143
_regexUrlQuery: /\?([^#]*).*$/,
146
Regex that matches everything before the path portion of a URL (the origin).
147
This will be used to strip this part of the URL from a string when we
150
@property _regexUrlOrigin
154
_regexUrlOrigin: /^(?:[^\/#?:]+:\/\/|\/\/)[^\/]*/,
156
// -- Lifecycle Methods ----------------------------------------------------
157
initializer: function (config) {
160
self._html5 = self.get('html5');
162
self._url = self._getURL();
164
// Necessary because setters don't run on init.
165
self._setRoutes(config && config.routes ? config.routes :
168
// Set up a history instance or hashchange listener.
170
self._history = new Y.HistoryHTML5({force: true});
171
self._historyEvents =
172
Y.after('history:change', self._afterHistoryChange, self);
174
self._historyEvents =
175
Y.on('hashchange', self._afterHistoryChange, win, self);
178
// Fire a `ready` event once we're ready to route. We wait first for all
179
// subclass initializers to finish, then for window.onload, and then an
180
// additional 20ms to allow the browser to fire a useless initial
181
// `popstate` event if it wants to (and Chrome always wants to).
182
self.publish(EVT_READY, {
183
defaultFn : self._defReadyFn,
188
self.once('initializedChange', function () {
189
Y.once('load', function () {
190
setTimeout(function () {
191
self.fire(EVT_READY, {dispatched: !!self._dispatched});
196
// Store this router in the collection of all active router instances.
197
instances.push(this);
200
destructor: function () {
201
var instanceIndex = YArray.indexOf(instances, this);
203
// Remove this router from the collection of active router instances.
204
if (instanceIndex > -1) {
205
instances.splice(instanceIndex, 1);
208
if (this._historyEvents) {
209
this._historyEvents.detach();
213
// -- Public Methods -------------------------------------------------------
216
Dispatches to the first route handler that matches the current URL, if any.
218
If `dispatch()` is called before the `ready` event has fired, it will
219
automatically wait for the `ready` event before dispatching. Otherwise it
220
will dispatch immediately.
225
dispatch: function () {
226
this.once(EVT_READY, function () {
229
if (!this.upgrade()) {
230
this._dispatch(this._getPath(), this._getURL());
238
Gets the current route path, relative to the `root` (if any).
241
@return {String} Current route path.
243
getPath: function () {
244
return this._getPath();
248
Returns `true` if this router has at least one route that matches the
249
specified URL, `false` otherwise.
251
This method enforces the same-origin security constraint on the specified
252
`url`; any URL which is not from the same origin as the current URL will
253
always return `false`.
256
@param {String} url URL to match.
257
@return {Boolean} `true` if there's at least one matching route, `false`
260
hasRoute: function (url) {
263
if (!this._hasSameOrigin(url)) {
268
url = this._upgradeURL(url);
271
path = this.removeQuery(this.removeRoot(url));
273
return !!this.match(path).length;
277
Returns an array of route objects that match the specified URL path.
279
This method is called internally to determine which routes match the current
280
path whenever the URL changes. You may override it if you want to customize
281
the route matching logic, although this usually shouldn't be necessary.
283
Each returned route object has the following properties:
285
* `callback`: A function or a string representing the name of a function
286
this router that should be executed when the route is triggered.
288
* `keys`: An array of strings representing the named parameters defined in
289
the route's path specification, if any.
291
* `path`: The route's path specification, which may be either a string or
294
* `regex`: A regular expression version of the route's path specification.
295
This regex is used to determine whether the route matches a given path.
298
router.route('/foo', function () {});
299
router.match('/foo');
300
// => [{callback: ..., keys: [], path: '/foo', regex: ...}]
303
@param {String} path URL path to match.
304
@return {Object[]} Array of route objects that match the specified path.
306
match: function (path) {
307
return YArray.filter(this._routes, function (route) {
308
return path.search(route.regex) > -1;
313
Removes the `root` URL from the front of _url_ (if it's there) and returns
314
the result. The returned path will always have a leading `/`.
317
@param {String} url URL.
318
@return {String} Rootless path.
320
removeRoot: function (url) {
321
var root = this.get('root');
323
// Strip out the non-path part of the URL, if any (e.g.
324
// "http://foo.com"), so that we're left with just the path.
325
url = url.replace(this._regexUrlOrigin, '');
327
if (root && url.indexOf(root) === 0) {
328
url = url.substring(root.length);
331
return url.charAt(0) === '/' ? url : '/' + url;
335
Removes a query string from the end of the _url_ (if one exists) and returns
339
@param {String} url URL.
340
@return {String} Queryless path.
342
removeQuery: function (url) {
343
return url.replace(/\?.*$/, '');
347
Replaces the current browser history entry with a new one, and dispatches to
348
the first matching route handler, if any.
350
Behind the scenes, this method uses HTML5 `pushState()` in browsers that
351
support it (or the location hash in older browsers and IE) to change the
354
The specified URL must share the same origin (i.e., protocol, host, and
355
port) as the current page, or an error will occur.
358
// Starting URL: http://example.com/
360
router.replace('/path/');
361
// New URL: http://example.com/path/
363
router.replace('/path?foo=bar');
364
// New URL: http://example.com/path?foo=bar
367
// New URL: http://example.com/
370
@param {String} [url] URL to set. This URL needs to be of the same origin as
371
the current URL. This can be a URL relative to the router's `root`
372
attribute. If no URL is specified, the page's current URL will be used.
376
replace: function (url) {
377
return this._queue(url, true);
381
Adds a route handler for the specified URL _path_.
383
The _path_ parameter may be either a string or a regular expression. If it's
384
a string, it may contain named parameters: `:param` will match any single
385
part of a URL path (not including `/` characters), and `*param` will match
386
any number of parts of a URL path (including `/` characters). These named
387
parameters will be made available as keys on the `req.params` object that's
388
passed to route handlers.
390
If the _path_ parameter is a regex, all pattern matches will be made
391
available as numbered keys on `req.params`, starting with `0` for the full
392
match, then `1` for the first subpattern match, and so on.
394
Here's a set of sample routes along with URL paths that they match:
396
* Route: `/photos/:tag/:page`
397
* URL: `/photos/kittens/1`, params: `{tag: 'kittens', page: '1'}`
398
* URL: `/photos/puppies/2`, params: `{tag: 'puppies', page: '2'}`
400
* Route: `/file/*path`
401
* URL: `/file/foo/bar/baz.txt`, params: `{path: 'foo/bar/baz.txt'}`
402
* URL: `/file/foo`, params: `{path: 'foo'}`
404
**Middleware**: Routes also support an arbitrary number of callback
405
functions. This allows you to easily reuse parts of your route-handling code
406
with different route. This method is liberal in how it processes the
407
specified `callbacks`, you can specify them as separate arguments, or as
410
If multiple route match a given URL, they will be executed in the order they
411
were added. The first route that was added will be the first to be executed.
413
**Passing Control**: Invoking the `next()` function within a route callback
414
will pass control to the next callback function (if any) or route handler
415
(if any). If a value is passed to `next()`, it's assumed to be an error,
416
therefore stopping the dispatch chain, unless that value is: `"route"`,
417
which is special case and dispatching will skip to the next route handler.
418
This allows middleware to skip any remaining middleware for a particular
422
router.route('/photos/:tag/:page', function (req, res, next) {
427
router.findUser = function (req, res, next) {
428
req.user = this.get('users').findById(req.params.user);
432
router.route('/users/:user', 'findUser', function (req, res, next) {
433
// The `findUser` middleware puts the `user` object on the `req`.
437
@param {String|RegExp} path Path to match. May be a string or a regular
439
@param {Array|Function|String} callbacks* Callback functions to call
440
whenever this route is triggered. These can be specified as separate
441
arguments, or in arrays, or both. If a callback is specified as a
442
string, the named function will be called on this router instance.
444
@param {Object} callbacks.req Request object containing information about
445
the request. It contains the following properties.
447
@param {Array|Object} callbacks.req.params Captured parameters matched by
448
the route path specification. If a string path was used and contained
449
named parameters, then this will be a key/value hash mapping parameter
450
names to their matched values. If a regex path was used, this will be
451
an array of subpattern matches starting at index 0 for the full match,
452
then 1 for the first subpattern match, and so on.
453
@param {String} callbacks.req.path The current URL path.
454
@param {Number} callbacks.req.pendingCallbacks Number of remaining
455
callbacks the route handler has after this one in the dispatch chain.
456
@param {Number} callbacks.req.pendingRoutes Number of matching routes
457
after this one in the dispatch chain.
458
@param {Object} callbacks.req.query Query hash representing the URL
459
query string, if any. Parameter names are keys, and are mapped to
461
@param {String} callbacks.req.url The full URL.
462
@param {String} callbacks.req.src What initiated the dispatch. In an
463
HTML5 browser, when the back/forward buttons are used, this property
464
will have a value of "popstate".
466
@param {Object} callbacks.res Response object containing methods and
467
information that relate to responding to a request. It contains the
468
following properties.
469
@param {Object} callbacks.res.req Reference to the request object.
471
@param {Function} callbacks.next Function to pass control to the next
472
callback or the next matching route if no more callbacks (middleware)
473
exist for the current route handler. If you don't call this function,
474
then no further callbacks or route handlers will be executed, even if
475
there are more that match. If you do call this function, then the next
476
callback (if any) or matching route handler (if any) will be called.
477
All of these functions will receive the same `req` and `res` objects
478
that were passed to this route (so you can use these objects to pass
479
data along to subsequent callbacks and routes).
480
@param {String} [callbacks.next.err] Optional error which will stop the
481
dispatch chaining for this `req`, unless the value is `"route"`, which
482
is special cased to jump skip past any callbacks for the current route
483
and pass control the next route handler.
486
route: function (path, callbacks) {
487
callbacks = YArray.flatten(YArray(arguments, 1, true));
492
callbacks: callbacks,
495
regex : this._getRegex(path, keys),
498
callback: callbacks[0]
505
Saves a new browser history entry and dispatches to the first matching route
508
Behind the scenes, this method uses HTML5 `pushState()` in browsers that
509
support it (or the location hash in older browsers and IE) to change the
510
URL and create a history entry.
512
The specified URL must share the same origin (i.e., protocol, host, and
513
port) as the current page, or an error will occur.
516
// Starting URL: http://example.com/
518
router.save('/path/');
519
// New URL: http://example.com/path/
521
router.save('/path?foo=bar');
522
// New URL: http://example.com/path?foo=bar
525
// New URL: http://example.com/
528
@param {String} [url] URL to set. This URL needs to be of the same origin as
529
the current URL. This can be a URL relative to the router's `root`
530
attribute. If no URL is specified, the page's current URL will be used.
534
save: function (url) {
535
return this._queue(url);
539
Upgrades a hash-based URL to an HTML5 URL if necessary. In non-HTML5
540
browsers, this method is a noop.
543
@return {Boolean} `true` if the URL was upgraded, `false` otherwise.
545
upgrade: function () {
550
// Get the resolve hash path.
551
var hashPath = this._getHashPath();
554
// This is an HTML5 browser and we have a hash-based path in the
555
// URL, so we need to upgrade the URL to a non-hash URL. This
556
// will trigger a `history:change` event, which will in turn
557
// trigger a dispatch.
558
this.once(EVT_READY, function () {
559
this.replace(hashPath);
568
// -- Protected Methods ----------------------------------------------------
571
Wrapper around `decodeURIComponent` that also converts `+` chars into
575
@param {String} string String to decode.
576
@return {String} Decoded string.
579
_decode: function (string) {
580
return decodeURIComponent(string.replace(/\+/g, ' '));
584
Shifts the topmost `_save()` call off the queue and executes it. Does
585
nothing if the queue is empty.
592
_dequeue: function () {
596
// If window.onload hasn't yet fired, wait until it has before
597
// dequeueing. This will ensure that we don't call pushState() before an
598
// initial popstate event has fired.
599
if (!YUI.Env.windowLoaded) {
600
Y.once('load', function () {
607
fn = saveQueue.shift();
608
return fn ? fn() : this;
612
Dispatches to the first route handler that matches the specified _path_.
614
If called before the `ready` event has fired, the dispatch will be aborted.
615
This ensures normalized behavior between Chrome (which fires a `popstate`
616
event on every pageview) and other browsers (which do not).
619
@param {String} path URL path.
620
@param {String} url Full URL.
621
@param {String} src What initiated the dispatch.
625
_dispatch: function (path, url, src) {
627
decode = self._decode,
628
routes = self.match(path),
632
self._dispatching = self._dispatched = true;
634
if (!routes || !routes.length) {
635
self._dispatching = false;
639
req = self._getRequest(path, url, src);
640
res = self._getResponse(req);
642
req.next = function (err) {
643
var callback, name, route;
646
// Special case "route" to skip to the next route handler
647
// avoiding any additional callbacks for the current route.
648
if (err === 'route') {
655
} else if ((callback = callbacks.shift())) {
656
if (typeof callback === 'string') {
658
callback = self[name];
661
Y.error('Router: Callback not found: ' + name, null, 'router');
665
// Allow access to the number of remaining callbacks for the
667
req.pendingCallbacks = callbacks.length;
669
callback.call(self, req, res, req.next);
671
} else if ((route = routes.shift())) {
672
// Make a copy of this route's `callbacks` so the original array
674
callbacks = route.callbacks.concat();
676
// Decode each of the path matches so that the any URL-encoded
677
// path segments are decoded in the `req.params` object.
678
matches = YArray.map(route.regex.exec(path) || [], decode);
680
// Use named keys for parameter names if the route path contains
681
// named keys. Otherwise, use numerical match indices.
682
if (matches.length === route.keys.length + 1) {
683
req.params = YArray.hash(route.keys, matches.slice(1));
685
req.params = matches.concat();
688
// Allow access to the number of remaining routes for this
690
req.pendingRoutes = routes.length;
692
// Execute this route's `callbacks`.
699
self._dispatching = false;
700
return self._dequeue();
704
Returns the resolved path from the hash fragment, or an empty string if the
705
hash is not path-like.
708
@param {String} [hash] Hash fragment to resolve into a path. By default this
709
will be the hash from the current URL.
710
@return {String} Current hash path, or an empty string if the hash is empty.
713
_getHashPath: function (hash) {
714
hash || (hash = HistoryHash.getHash());
716
// Make sure the `hash` is path-like.
717
if (hash && hash.charAt(0) === '/') {
718
return this._joinURL(hash);
725
Gets the location origin (i.e., protocol, host, and port) as a URL.
731
@return {String} Location origin (i.e., protocol, host, and port).
734
_getOrigin: function () {
735
var location = Y.getLocation();
736
return location.origin || (location.protocol + '//' + location.host);
740
Gets the current route path, relative to the `root` (if any).
743
@return {String} Current route path.
746
_getPath: function () {
747
var path = (!this._html5 && this._getHashPath()) ||
748
Y.getLocation().pathname;
750
return this.removeQuery(this.removeRoot(path));
754
Returns the current path root after popping off the last path segment,
755
making it useful for resolving other URL paths against.
757
The path root will always begin and end with a '/'.
760
@return {String} The URL's path root.
764
_getPathRoot: function () {
766
path = Y.getLocation().pathname,
769
if (path.charAt(path.length - 1) === slash) {
773
segments = path.split(slash);
776
return segments.join(slash) + slash;
780
Gets the current route query string.
783
@return {String} Current route query string.
786
_getQuery: function () {
787
var location = Y.getLocation(),
791
return location.search.substring(1);
794
hash = HistoryHash.getHash();
795
matches = hash.match(this._regexUrlQuery);
797
return hash && matches ? matches[1] : location.search.substring(1);
801
Creates a regular expression from the given route specification. If _path_
802
is already a regex, it will be returned unmodified.
805
@param {String|RegExp} path Route path specification.
806
@param {Array} keys Array reference to which route parameter names will be
808
@return {RegExp} Route regex.
811
_getRegex: function (path, keys) {
812
if (path instanceof RegExp) {
816
// Special case for catchall paths.
821
path = path.replace(this._regexPathParam, function (match, operator, key) {
822
// Only `*` operators are supported for key-less matches to allowing
823
// in-path wildcards like: '/foo/*'.
825
return operator === '*' ? '.*' : match;
829
return operator === '*' ? '(.*?)' : '([^/#?]*)';
832
return new RegExp('^' + path + '$');
836
Gets a request object that can be passed to a route handler.
839
@param {String} path Current path being dispatched.
840
@param {String} url Current full URL being dispatched.
841
@param {String} src What initiated the dispatch.
842
@return {Object} Request object.
845
_getRequest: function (path, url, src) {
848
query: this._parseQuery(this._getQuery()),
855
Gets a response object that can be passed to a route handler.
858
@param {Object} req Request object.
859
@return {Object} Response Object.
862
_getResponse: function (req) {
863
// For backwards compatibility, the response object is a function that
864
// calls `next()` on the request object and returns the result.
865
var res = function () {
866
return req.next.apply(this, arguments);
874
Getter for the `routes` attribute.
877
@return {Object[]} Array of route objects.
880
_getRoutes: function () {
881
return this._routes.concat();
885
Gets the current full URL.
888
@return {String} URL.
891
_getURL: function () {
892
var url = Y.getLocation().toString();
895
url = this._upgradeURL(url);
902
Returns `true` when the specified `url` is from the same origin as the
903
current URL; i.e., the protocol, host, and port of the URLs are the same.
905
All host or path relative URLs are of the same origin. A scheme-relative URL
906
is first prefixed with the current scheme before being evaluated.
908
@method _hasSameOrigin
909
@param {String} url URL to compare origin with the current URL.
910
@return {Boolean} Whether the URL has the same origin of the current URL.
913
_hasSameOrigin: function (url) {
914
var origin = ((url && url.match(this._regexUrlOrigin)) || [])[0];
916
// Prepend current scheme to scheme-relative URLs.
917
if (origin && origin.indexOf('//') === 0) {
918
origin = Y.getLocation().protocol + origin;
921
return !origin || origin === this._getOrigin();
925
Joins the `root` URL to the specified _url_, normalizing leading/trailing
929
router.set('root', '/foo');
930
router._joinURL('bar'); // => '/foo/bar'
931
router._joinURL('/bar'); // => '/foo/bar'
933
router.set('root', '/foo/');
934
router._joinURL('bar'); // => '/foo/bar'
935
router._joinURL('/bar'); // => '/foo/bar'
938
@param {String} url URL to append to the `root` URL.
939
@return {String} Joined URL.
942
_joinURL: function (url) {
943
var root = this.get('root');
945
// Causes `url` to _always_ begin with a "/".
946
url = this.removeRoot(url);
948
if (url.charAt(0) === '/') {
949
url = url.substring(1);
952
return root && root.charAt(root.length - 1) === '/' ?
958
Returns a normalized path, ridding it of any '..' segments and properly
959
handling leading and trailing slashes.
961
@method _normalizePath
962
@param {String} path URL path to normalize.
963
@return {String} Normalized path.
967
_normalizePath: function (path) {
970
i, len, normalized, segments, segment, stack;
972
if (!path || path === slash) {
976
segments = path.split(slash);
979
for (i = 0, len = segments.length; i < len; ++i) {
980
segment = segments[i];
982
if (segment === dots) {
984
} else if (segment) {
989
normalized = slash + stack.join(slash);
991
// Append trailing slash if necessary.
992
if (normalized !== slash && path.charAt(path.length - 1) === slash) {
1000
Parses a URL query string into a key/value hash. If `Y.QueryString.parse` is
1001
available, this method will be an alias to that.
1004
@param {String} query Query string to parse.
1005
@return {Object} Hash of key/value pairs for query parameters.
1008
_parseQuery: QS && QS.parse ? QS.parse : function (query) {
1009
var decode = this._decode,
1010
params = query.split('&'),
1012
len = params.length,
1016
for (; i < len; ++i) {
1017
param = params[i].split('=');
1020
result[decode(param[0])] = decode(param[1] || '');
1028
Queues up a `_save()` call to run after all previously-queued calls have
1031
This is necessary because if we make multiple `_save()` calls before the
1032
first call gets dispatched, then both calls will dispatch to the last call's
1035
All arguments passed to `_queue()` will be passed on to `_save()` when the
1036
queued function is executed.
1043
_queue: function () {
1044
var args = arguments,
1047
saveQueue.push(function () {
1049
if (Y.UA.ios && Y.UA.ios < 5) {
1050
// iOS <5 has buggy HTML5 history support, and needs to be
1052
self._save.apply(self, args);
1054
// Wrapped in a timeout to ensure that _save() calls are
1055
// always processed asynchronously. This ensures consistency
1056
// between HTML5- and hash-based history.
1057
setTimeout(function () {
1058
self._save.apply(self, args);
1062
self._dispatching = true; // otherwise we'll dequeue too quickly
1063
self._save.apply(self, args);
1069
return !this._dispatching ? this._dequeue() : this;
1073
Returns the normalized result of resolving the `path` against the current
1074
path. Falsy values for `path` will return just the current path.
1076
@method _resolvePath
1077
@param {String} path URL path to resolve.
1078
@return {String} Resolved path.
1082
_resolvePath: function (path) {
1084
return Y.getLocation().pathname;
1087
if (path.charAt(0) !== '/') {
1088
path = this._getPathRoot() + path;
1091
return this._normalizePath(path);
1095
Resolves the specified URL against the current URL.
1097
This method resolves URLs like a browser does and will always return an
1098
absolute URL. When the specified URL is already absolute, it is assumed to
1099
be fully resolved and is simply returned as is. Scheme-relative URLs are
1100
prefixed with the current protocol. Relative URLs are giving the current
1101
URL's origin and are resolved and normalized against the current path root.
1104
@param {String} url URL to resolve.
1105
@return {String} Resolved URL.
1109
_resolveURL: function (url) {
1110
var parts = url && url.match(this._regexURL),
1111
origin, path, query, hash, resolved;
1114
return Y.getLocation().toString();
1122
// Absolute and scheme-relative URLs are assumed to be fully-resolved.
1124
// Prepend the current scheme for scheme-relative URLs.
1125
if (origin.indexOf('//') === 0) {
1126
origin = Y.getLocation().protocol + origin;
1129
return origin + (path || '/') + (query || '') + (hash || '');
1132
// Will default to the current origin and current path.
1133
resolved = this._getOrigin() + this._resolvePath(path);
1135
// A path or query for the specified URL trumps the current URL's.
1136
if (path || query) {
1137
return resolved + (query || '') + (hash || '');
1140
query = this._getQuery();
1142
return resolved + (query ? ('?' + query) : '') + (hash || '');
1146
Saves a history entry using either `pushState()` or the location hash.
1148
This method enforces the same-origin security constraint; attempting to save
1149
a `url` that is not from the same origin as the current URL will result in
1153
@param {String} [url] URL for the history entry.
1154
@param {Boolean} [replace=false] If `true`, the current history entry will
1155
be replaced instead of a new one being added.
1159
_save: function (url, replace) {
1160
var urlIsString = typeof url === 'string',
1161
currentPath, root, hash;
1163
// Perform same-origin check on the specified URL.
1164
if (urlIsString && !this._hasSameOrigin(url)) {
1165
Y.error('Security error: The new URL must be of the same origin as the current URL.');
1169
// Joins the `url` with the `root`.
1171
url = this._joinURL(url);
1174
// Force _ready to true to ensure that the history change is handled
1175
// even if _save is called before the `ready` event fires.
1179
this._history[replace ? 'replace' : 'add'](null, {url: url});
1181
currentPath = Y.getLocation().pathname;
1182
root = this.get('root');
1183
hash = HistoryHash.getHash();
1189
// Determine if the `root` already exists in the current location's
1190
// `pathname`, and if it does then we can exclude it from the
1191
// hash-based path. No need to duplicate the info in the URL.
1192
if (root === currentPath || root === this._getPathRoot()) {
1193
url = this.removeRoot(url);
1196
// The `hashchange` event only fires when the new hash is actually
1197
// different. This makes sure we'll always dequeue and dispatch
1198
// _all_ router instances, mimicking the HTML5 behavior.
1200
Y.Router.dispatch();
1202
HistoryHash[replace ? 'replaceHash' : 'setHash'](url);
1210
Setter for the `routes` attribute.
1213
@param {Object[]} routes Array of route objects.
1214
@return {Object[]} Array of route objects.
1217
_setRoutes: function (routes) {
1220
YArray.each(routes, function (route) {
1221
// Makes sure to check `callback` for back-compat.
1222
var callbacks = route.callbacks || route.callback;
1224
this.route(route.path, callbacks);
1227
return this._routes.concat();
1231
Upgrades a hash-based URL to a full-path URL, if necessary.
1233
The specified `url` will be upgraded if its of the same origin as the
1234
current URL and has a path-like hash. URLs that don't need upgrading will be
1238
app._upgradeURL('http://example.com/#/foo/'); // => 'http://example.com/foo/';
1241
@param {String} url The URL to upgrade from hash-based to full-path.
1242
@return {String} The upgraded URL, or the specified URL untouched.
1246
_upgradeURL: function (url) {
1247
// We should not try to upgrade paths for external URLs.
1248
if (!this._hasSameOrigin(url)) {
1252
var hash = (url.match(/#(.*)$/) || [])[1] || '',
1253
hashPrefix = Y.HistoryHash.hashPrefix,
1256
// Strip any hash prefix, like hash-bangs.
1257
if (hashPrefix && hash.indexOf(hashPrefix) === 0) {
1258
hash = hash.replace(hashPrefix, '');
1261
// If the hash looks like a URL path, assume it is, and upgrade it!
1263
hashPath = this._getHashPath(hash);
1266
return this._resolveURL(hashPath);
1273
// -- Protected Event Handlers ---------------------------------------------
1276
Handles `history:change` and `hashchange` events.
1278
@method _afterHistoryChange
1279
@param {EventFacade} e
1282
_afterHistoryChange: function (e) {
1285
prevURL = self._url,
1286
currentURL = self._getURL();
1288
self._url = currentURL;
1290
// Handles the awkwardness that is the `popstate` event. HTML5 browsers
1291
// fire `popstate` right before they fire `hashchange`, and Chrome fires
1292
// `popstate` on page load. If this router is not ready or the previous
1293
// and current URLs only differ by their hash, then we want to ignore
1294
// this `popstate` event.
1295
if (src === 'popstate' &&
1296
(!self._ready || prevURL.replace(/#.*$/, '') === currentURL.replace(/#.*$/, ''))) {
1301
self._dispatch(self._getPath(), currentURL, src);
1304
// -- Default Event Handlers -----------------------------------------------
1307
Default handler for the `ready` event.
1310
@param {EventFacade} e
1313
_defReadyFn: function (e) {
1317
// -- Static Properties ----------------------------------------------------
1322
Whether or not this browser is capable of using HTML5 history.
1324
Setting this to `false` will force the use of hash-based history even on
1325
HTML5 browsers, but please don't do this unless you understand the
1333
// Android versions lower than 3.0 are buggy and don't update
1334
// window.location after a pushState() call, so we fall back to
1335
// hash-based history for them.
1337
// See http://code.google.com/p/android/issues/detail?id=17471
1338
valueFn: function () { return Y.Router.html5; },
1339
writeOnce: 'initOnly'
1343
Absolute root path from which all routes should be evaluated.
1345
For example, if your router is running on a page at
1346
`http://example.com/myapp/` and you add a route with the path `/`, your
1347
route will never execute, because the path will always be preceded by
1348
`/myapp`. Setting `root` to `/myapp` would cause all routes to be
1349
evaluated relative to that root URL, so the `/` route would then execute
1350
when the user browses to `http://example.com/myapp/`.
1361
Array of route objects.
1363
Each item in the array must be an object with the following properties:
1365
* `path`: String or regex representing the path to match. See the docs
1366
for the `route()` method for more details.
1368
* `callbacks`: Function or a string representing the name of a
1369
function on this router instance that should be called when the
1370
route is triggered. An array of functions and/or strings may also be
1371
provided. See the docs for the `route()` method for more details.
1373
This attribute is intended to be used to set routes at init time, or to
1374
completely reset all routes after init. To add routes after init without
1375
resetting all existing routes, use the `route()` method.
1384
getter: '_getRoutes',
1385
setter: '_setRoutes'
1389
// Used as the default value for the `html5` attribute, and for testing.
1390
html5: Y.HistoryBase.html5 && (!Y.UA.android || Y.UA.android >= 3),
1392
// To make this testable.
1393
_instances: instances,
1396
Dispatches to the first route handler that matches the specified `path` for
1397
all active router instances.
1399
This provides a mechanism to cause all active router instances to dispatch
1400
to their route handlers without needing to change the URL or fire the
1401
`history:change` or `hashchange` event.
1407
dispatch: function () {
1410
for (i = 0, len = instances.length; i < len; i += 1) {
1411
router = instances[i];
1414
router._dispatch(router._getPath(), router._getURL());
1421
The `Controller` class was deprecated in YUI 3.5.0 and is now an alias for the
1422
`Router` class. Use that class instead. This alias will be removed in a future
1428
@deprecated Use `Router` instead.
1431
Y.Controller = Y.Router;
1434
}, '3.10.3', {"optional": ["querystring-parse"], "requires": ["array-extras", "base-build", "history"]});