~michael.nelson/ubuntu-webcatalog/1267731-import-sca-apps-error

« back to all changes in this revision

Viewing changes to src/webcatalog/static/yui/3.10.3/build/router/router.js

  • Committer: Tarmac
  • Author(s): Stephen Stewart
  • Date: 2013-06-26 09:19:32 UTC
  • mfrom: (184.1.4 ubuntu-global-nav)
  • Revision ID: tarmac-20130626091932-8urtuli368k8p7ds
[r=beuno,jonas-drange] add ubuntu global nav to apps.ubuntu.com

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
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/
 
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
 
 
22
    win = Y.config.win,
 
23
 
 
24
    // Holds all the active router instances. This supports the static
 
25
    // `dispatch()` method which causes all routers to dispatch.
 
26
    instances = [],
 
27
 
 
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
 
30
    // associated with.
 
31
    saveQueue = [],
 
32
 
 
33
    /**
 
34
    Fired when the router is ready to begin dispatching to route handlers.
 
35
 
 
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.
 
39
 
 
40
    @event ready
 
41
    @param {Boolean} dispatched `true` if routes have already been dispatched
 
42
      (most likely due to a history change).
 
43
    @fireOnce
 
44
    **/
 
45
    EVT_READY = 'ready';
 
46
 
 
47
/**
 
48
Provides URL-based routing using HTML5 `pushState()` or the location hash.
 
49
 
 
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
 
52
URLs.
 
53
 
 
54
@class Router
 
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
 
58
        history.
 
59
    @param {String} [config.root=''] Root path from which all routes should be
 
60
        evaluated.
 
61
    @param {Array} [config.routes=[]] Array of route definition objects.
 
62
@constructor
 
63
@extends Base
 
64
@since 3.4.0
 
65
**/
 
66
function Router() {
 
67
    Router.superclass.constructor.apply(this, arguments);
 
68
}
 
69
 
 
70
Y.Router = Y.extend(Router, Y.Base, {
 
71
    // -- Protected Properties -------------------------------------------------
 
72
 
 
73
    /**
 
74
    Whether or not `_dispatch()` has been called since this router was
 
75
    instantiated.
 
76
 
 
77
    @property _dispatched
 
78
    @type Boolean
 
79
    @default undefined
 
80
    @protected
 
81
    **/
 
82
 
 
83
    /**
 
84
    Whether or not we're currently in the process of dispatching to routes.
 
85
 
 
86
    @property _dispatching
 
87
    @type Boolean
 
88
    @default undefined
 
89
    @protected
 
90
    **/
 
91
 
 
92
    /**
 
93
    History event handle for the `history:change` or `hashchange` event
 
94
    subscription.
 
95
 
 
96
    @property _historyEvents
 
97
    @type EventHandle
 
98
    @protected
 
99
    **/
 
100
 
 
101
    /**
 
102
    Cached copy of the `html5` attribute for internal use.
 
103
 
 
104
    @property _html5
 
105
    @type Boolean
 
106
    @protected
 
107
    **/
 
108
 
 
109
    /**
 
110
    Whether or not the `ready` event has fired yet.
 
111
 
 
112
    @property _ready
 
113
    @type Boolean
 
114
    @default undefined
 
115
    @protected
 
116
    **/
 
117
 
 
118
    /**
 
119
    Regex used to match parameter placeholders in route paths.
 
120
 
 
121
    Subpattern captures:
 
122
 
 
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.
 
126
 
 
127
      2. Parameter name, if specified, otherwise it is a wildcard match.
 
128
 
 
129
    @property _regexPathParam
 
130
    @type RegExp
 
131
    @protected
 
132
    **/
 
133
    _regexPathParam: /([:*])([\w\-]+)?/g,
 
134
 
 
135
    /**
 
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.
 
138
 
 
139
    @property _regexUrlQuery
 
140
    @type RegExp
 
141
    @protected
 
142
    **/
 
143
    _regexUrlQuery: /\?([^#]*).*$/,
 
144
 
 
145
    /**
 
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
 
148
    only want the path.
 
149
 
 
150
    @property _regexUrlOrigin
 
151
    @type RegExp
 
152
    @protected
 
153
    **/
 
154
    _regexUrlOrigin: /^(?:[^\/#?:]+:\/\/|\/\/)[^\/]*/,
 
155
 
 
156
    // -- Lifecycle Methods ----------------------------------------------------
 
157
    initializer: function (config) {
 
158
        var self = this;
 
159
 
 
160
        self._html5  = self.get('html5');
 
161
        self._routes = [];
 
162
        self._url    = self._getURL();
 
163
 
 
164
        // Necessary because setters don't run on init.
 
165
        self._setRoutes(config && config.routes ? config.routes :
 
166
                self.get('routes'));
 
167
 
 
168
        // Set up a history instance or hashchange listener.
 
169
        if (self._html5) {
 
170
            self._history       = new Y.HistoryHTML5({force: true});
 
171
            self._historyEvents =
 
172
                    Y.after('history:change', self._afterHistoryChange, self);
 
173
        } else {
 
174
            self._historyEvents =
 
175
                    Y.on('hashchange', self._afterHistoryChange, win, self);
 
176
        }
 
177
 
 
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,
 
184
            fireOnce   : true,
 
185
            preventable: false
 
186
        });
 
187
 
 
188
        self.once('initializedChange', function () {
 
189
            Y.once('load', function () {
 
190
                setTimeout(function () {
 
191
                    self.fire(EVT_READY, {dispatched: !!self._dispatched});
 
192
                }, 20);
 
193
            });
 
194
        });
 
195
 
 
196
        // Store this router in the collection of all active router instances.
 
197
        instances.push(this);
 
198
    },
 
199
 
 
200
    destructor: function () {
 
201
        var instanceIndex = YArray.indexOf(instances, this);
 
202
 
 
203
        // Remove this router from the collection of active router instances.
 
204
        if (instanceIndex > -1) {
 
205
            instances.splice(instanceIndex, 1);
 
206
        }
 
207
 
 
208
        if (this._historyEvents) {
 
209
            this._historyEvents.detach();
 
210
        }
 
211
    },
 
212
 
 
213
    // -- Public Methods -------------------------------------------------------
 
214
 
 
215
    /**
 
216
    Dispatches to the first route handler that matches the current URL, if any.
 
217
 
 
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.
 
221
 
 
222
    @method dispatch
 
223
    @chainable
 
224
    **/
 
225
    dispatch: function () {
 
226
        this.once(EVT_READY, function () {
 
227
            this._ready = true;
 
228
 
 
229
            if (!this.upgrade()) {
 
230
                this._dispatch(this._getPath(), this._getURL());
 
231
            }
 
232
        });
 
233
 
 
234
        return this;
 
235
    },
 
236
 
 
237
    /**
 
238
    Gets the current route path, relative to the `root` (if any).
 
239
 
 
240
    @method getPath
 
241
    @return {String} Current route path.
 
242
    **/
 
243
    getPath: function () {
 
244
        return this._getPath();
 
245
    },
 
246
 
 
247
    /**
 
248
    Returns `true` if this router has at least one route that matches the
 
249
    specified URL, `false` otherwise.
 
250
 
 
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`.
 
254
 
 
255
    @method hasRoute
 
256
    @param {String} url URL to match.
 
257
    @return {Boolean} `true` if there's at least one matching route, `false`
 
258
      otherwise.
 
259
    **/
 
260
    hasRoute: function (url) {
 
261
        var path;
 
262
 
 
263
        if (!this._hasSameOrigin(url)) {
 
264
            return false;
 
265
        }
 
266
 
 
267
        if (!this._html5) {
 
268
            url = this._upgradeURL(url);
 
269
        }
 
270
 
 
271
        path = this.removeQuery(this.removeRoot(url));
 
272
 
 
273
        return !!this.match(path).length;
 
274
    },
 
275
 
 
276
    /**
 
277
    Returns an array of route objects that match the specified URL path.
 
278
 
 
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.
 
282
 
 
283
    Each returned route object has the following properties:
 
284
 
 
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.
 
287
 
 
288
      * `keys`: An array of strings representing the named parameters defined in
 
289
        the route's path specification, if any.
 
290
 
 
291
      * `path`: The route's path specification, which may be either a string or
 
292
        a regex.
 
293
 
 
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.
 
296
 
 
297
    @example
 
298
        router.route('/foo', function () {});
 
299
        router.match('/foo');
 
300
        // => [{callback: ..., keys: [], path: '/foo', regex: ...}]
 
301
 
 
302
    @method match
 
303
    @param {String} path URL path to match.
 
304
    @return {Object[]} Array of route objects that match the specified path.
 
305
    **/
 
306
    match: function (path) {
 
307
        return YArray.filter(this._routes, function (route) {
 
308
            return path.search(route.regex) > -1;
 
309
        });
 
310
    },
 
311
 
 
312
    /**
 
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 `/`.
 
315
 
 
316
    @method removeRoot
 
317
    @param {String} url URL.
 
318
    @return {String} Rootless path.
 
319
    **/
 
320
    removeRoot: function (url) {
 
321
        var root = this.get('root');
 
322
 
 
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, '');
 
326
 
 
327
        if (root && url.indexOf(root) === 0) {
 
328
            url = url.substring(root.length);
 
329
        }
 
330
 
 
331
        return url.charAt(0) === '/' ? url : '/' + url;
 
332
    },
 
333
 
 
334
    /**
 
335
    Removes a query string from the end of the _url_ (if one exists) and returns
 
336
    the result.
 
337
 
 
338
    @method removeQuery
 
339
    @param {String} url URL.
 
340
    @return {String} Queryless path.
 
341
    **/
 
342
    removeQuery: function (url) {
 
343
        return url.replace(/\?.*$/, '');
 
344
    },
 
345
 
 
346
    /**
 
347
    Replaces the current browser history entry with a new one, and dispatches to
 
348
    the first matching route handler, if any.
 
349
 
 
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
 
352
    URL.
 
353
 
 
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.
 
356
 
 
357
    @example
 
358
        // Starting URL: http://example.com/
 
359
 
 
360
        router.replace('/path/');
 
361
        // New URL: http://example.com/path/
 
362
 
 
363
        router.replace('/path?foo=bar');
 
364
        // New URL: http://example.com/path?foo=bar
 
365
 
 
366
        router.replace('/');
 
367
        // New URL: http://example.com/
 
368
 
 
369
    @method replace
 
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.
 
373
    @chainable
 
374
    @see save()
 
375
    **/
 
376
    replace: function (url) {
 
377
        return this._queue(url, true);
 
378
    },
 
379
 
 
380
    /**
 
381
    Adds a route handler for the specified URL _path_.
 
382
 
 
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.
 
389
 
 
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.
 
393
 
 
394
    Here's a set of sample routes along with URL paths that they match:
 
395
 
 
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'}`
 
399
 
 
400
      * Route: `/file/*path`
 
401
        * URL: `/file/foo/bar/baz.txt`, params: `{path: 'foo/bar/baz.txt'}`
 
402
        * URL: `/file/foo`, params: `{path: 'foo'}`
 
403
 
 
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
 
408
    arrays, or both.
 
409
 
 
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.
 
412
 
 
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
 
419
    route.
 
420
 
 
421
    @example
 
422
        router.route('/photos/:tag/:page', function (req, res, next) {
 
423
        });
 
424
 
 
425
        // Using middleware.
 
426
 
 
427
        router.findUser = function (req, res, next) {
 
428
            req.user = this.get('users').findById(req.params.user);
 
429
            next();
 
430
        };
 
431
 
 
432
        router.route('/users/:user', 'findUser', function (req, res, next) {
 
433
            // The `findUser` middleware puts the `user` object on the `req`.
 
434
        });
 
435
 
 
436
    @method route
 
437
    @param {String|RegExp} path Path to match. May be a string or a regular
 
438
      expression.
 
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.
 
443
 
 
444
      @param {Object} callbacks.req Request object containing information about
 
445
          the request. It contains the following properties.
 
446
 
 
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
 
460
          parameter values.
 
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".
 
465
 
 
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.
 
470
 
 
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.
 
484
    @chainable
 
485
    **/
 
486
    route: function (path, callbacks) {
 
487
        callbacks = YArray.flatten(YArray(arguments, 1, true));
 
488
 
 
489
        var keys = [];
 
490
 
 
491
        this._routes.push({
 
492
            callbacks: callbacks,
 
493
            keys     : keys,
 
494
            path     : path,
 
495
            regex    : this._getRegex(path, keys),
 
496
 
 
497
            // For back-compat.
 
498
            callback: callbacks[0]
 
499
        });
 
500
 
 
501
        return this;
 
502
    },
 
503
 
 
504
    /**
 
505
    Saves a new browser history entry and dispatches to the first matching route
 
506
    handler, if any.
 
507
 
 
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.
 
511
 
 
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.
 
514
 
 
515
    @example
 
516
        // Starting URL: http://example.com/
 
517
 
 
518
        router.save('/path/');
 
519
        // New URL: http://example.com/path/
 
520
 
 
521
        router.save('/path?foo=bar');
 
522
        // New URL: http://example.com/path?foo=bar
 
523
 
 
524
        router.save('/');
 
525
        // New URL: http://example.com/
 
526
 
 
527
    @method save
 
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.
 
531
    @chainable
 
532
    @see replace()
 
533
    **/
 
534
    save: function (url) {
 
535
        return this._queue(url);
 
536
    },
 
537
 
 
538
    /**
 
539
    Upgrades a hash-based URL to an HTML5 URL if necessary. In non-HTML5
 
540
    browsers, this method is a noop.
 
541
 
 
542
    @method upgrade
 
543
    @return {Boolean} `true` if the URL was upgraded, `false` otherwise.
 
544
    **/
 
545
    upgrade: function () {
 
546
        if (!this._html5) {
 
547
            return false;
 
548
        }
 
549
 
 
550
        // Get the resolve hash path.
 
551
        var hashPath = this._getHashPath();
 
552
 
 
553
        if (hashPath) {
 
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);
 
560
            });
 
561
 
 
562
            return true;
 
563
        }
 
564
 
 
565
        return false;
 
566
    },
 
567
 
 
568
    // -- Protected Methods ----------------------------------------------------
 
569
 
 
570
    /**
 
571
    Wrapper around `decodeURIComponent` that also converts `+` chars into
 
572
    spaces.
 
573
 
 
574
    @method _decode
 
575
    @param {String} string String to decode.
 
576
    @return {String} Decoded string.
 
577
    @protected
 
578
    **/
 
579
    _decode: function (string) {
 
580
        return decodeURIComponent(string.replace(/\+/g, ' '));
 
581
    },
 
582
 
 
583
    /**
 
584
    Shifts the topmost `_save()` call off the queue and executes it. Does
 
585
    nothing if the queue is empty.
 
586
 
 
587
    @method _dequeue
 
588
    @chainable
 
589
    @see _queue
 
590
    @protected
 
591
    **/
 
592
    _dequeue: function () {
 
593
        var self = this,
 
594
            fn;
 
595
 
 
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 () {
 
601
                self._dequeue();
 
602
            });
 
603
 
 
604
            return this;
 
605
        }
 
606
 
 
607
        fn = saveQueue.shift();
 
608
        return fn ? fn() : this;
 
609
    },
 
610
 
 
611
    /**
 
612
    Dispatches to the first route handler that matches the specified _path_.
 
613
 
 
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).
 
617
 
 
618
    @method _dispatch
 
619
    @param {String} path URL path.
 
620
    @param {String} url Full URL.
 
621
    @param {String} src What initiated the dispatch.
 
622
    @chainable
 
623
    @protected
 
624
    **/
 
625
    _dispatch: function (path, url, src) {
 
626
        var self      = this,
 
627
            decode    = self._decode,
 
628
            routes    = self.match(path),
 
629
            callbacks = [],
 
630
            matches, req, res;
 
631
 
 
632
        self._dispatching = self._dispatched = true;
 
633
 
 
634
        if (!routes || !routes.length) {
 
635
            self._dispatching = false;
 
636
            return self;
 
637
        }
 
638
 
 
639
        req = self._getRequest(path, url, src);
 
640
        res = self._getResponse(req);
 
641
 
 
642
        req.next = function (err) {
 
643
            var callback, name, route;
 
644
 
 
645
            if (err) {
 
646
                // Special case "route" to skip to the next route handler
 
647
                // avoiding any additional callbacks for the current route.
 
648
                if (err === 'route') {
 
649
                    callbacks = [];
 
650
                    req.next();
 
651
                } else {
 
652
                    Y.error(err);
 
653
                }
 
654
 
 
655
            } else if ((callback = callbacks.shift())) {
 
656
                if (typeof callback === 'string') {
 
657
                    name     = callback;
 
658
                    callback = self[name];
 
659
 
 
660
                    if (!callback) {
 
661
                        Y.error('Router: Callback not found: ' + name, null, 'router');
 
662
                    }
 
663
                }
 
664
 
 
665
                // Allow access to the number of remaining callbacks for the
 
666
                // route.
 
667
                req.pendingCallbacks = callbacks.length;
 
668
 
 
669
                callback.call(self, req, res, req.next);
 
670
 
 
671
            } else if ((route = routes.shift())) {
 
672
                // Make a copy of this route's `callbacks` so the original array
 
673
                // is preserved.
 
674
                callbacks = route.callbacks.concat();
 
675
 
 
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);
 
679
 
 
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));
 
684
                } else {
 
685
                    req.params = matches.concat();
 
686
                }
 
687
 
 
688
                // Allow access to the number of remaining routes for this
 
689
                // request.
 
690
                req.pendingRoutes = routes.length;
 
691
 
 
692
                // Execute this route's `callbacks`.
 
693
                req.next();
 
694
            }
 
695
        };
 
696
 
 
697
        req.next();
 
698
 
 
699
        self._dispatching = false;
 
700
        return self._dequeue();
 
701
    },
 
702
 
 
703
    /**
 
704
    Returns the resolved path from the hash fragment, or an empty string if the
 
705
    hash is not path-like.
 
706
 
 
707
    @method _getHashPath
 
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.
 
711
    @protected
 
712
    **/
 
713
    _getHashPath: function (hash) {
 
714
        hash || (hash = HistoryHash.getHash());
 
715
 
 
716
        // Make sure the `hash` is path-like.
 
717
        if (hash && hash.charAt(0) === '/') {
 
718
            return this._joinURL(hash);
 
719
        }
 
720
 
 
721
        return '';
 
722
    },
 
723
 
 
724
    /**
 
725
    Gets the location origin (i.e., protocol, host, and port) as a URL.
 
726
 
 
727
    @example
 
728
        http://example.com
 
729
 
 
730
    @method _getOrigin
 
731
    @return {String} Location origin (i.e., protocol, host, and port).
 
732
    @protected
 
733
    **/
 
734
    _getOrigin: function () {
 
735
        var location = Y.getLocation();
 
736
        return location.origin || (location.protocol + '//' + location.host);
 
737
    },
 
738
 
 
739
    /**
 
740
    Gets the current route path, relative to the `root` (if any).
 
741
 
 
742
    @method _getPath
 
743
    @return {String} Current route path.
 
744
    @protected
 
745
    **/
 
746
    _getPath: function () {
 
747
        var path = (!this._html5 && this._getHashPath()) ||
 
748
                Y.getLocation().pathname;
 
749
 
 
750
        return this.removeQuery(this.removeRoot(path));
 
751
    },
 
752
 
 
753
    /**
 
754
    Returns the current path root after popping off the last path segment,
 
755
    making it useful for resolving other URL paths against.
 
756
 
 
757
    The path root will always begin and end with a '/'.
 
758
 
 
759
    @method _getPathRoot
 
760
    @return {String} The URL's path root.
 
761
    @protected
 
762
    @since 3.5.0
 
763
    **/
 
764
    _getPathRoot: function () {
 
765
        var slash = '/',
 
766
            path  = Y.getLocation().pathname,
 
767
            segments;
 
768
 
 
769
        if (path.charAt(path.length - 1) === slash) {
 
770
            return path;
 
771
        }
 
772
 
 
773
        segments = path.split(slash);
 
774
        segments.pop();
 
775
 
 
776
        return segments.join(slash) + slash;
 
777
    },
 
778
 
 
779
    /**
 
780
    Gets the current route query string.
 
781
 
 
782
    @method _getQuery
 
783
    @return {String} Current route query string.
 
784
    @protected
 
785
    **/
 
786
    _getQuery: function () {
 
787
        var location = Y.getLocation(),
 
788
            hash, matches;
 
789
 
 
790
        if (this._html5) {
 
791
            return location.search.substring(1);
 
792
        }
 
793
 
 
794
        hash    = HistoryHash.getHash();
 
795
        matches = hash.match(this._regexUrlQuery);
 
796
 
 
797
        return hash && matches ? matches[1] : location.search.substring(1);
 
798
    },
 
799
 
 
800
    /**
 
801
    Creates a regular expression from the given route specification. If _path_
 
802
    is already a regex, it will be returned unmodified.
 
803
 
 
804
    @method _getRegex
 
805
    @param {String|RegExp} path Route path specification.
 
806
    @param {Array} keys Array reference to which route parameter names will be
 
807
      added.
 
808
    @return {RegExp} Route regex.
 
809
    @protected
 
810
    **/
 
811
    _getRegex: function (path, keys) {
 
812
        if (path instanceof RegExp) {
 
813
            return path;
 
814
        }
 
815
 
 
816
        // Special case for catchall paths.
 
817
        if (path === '*') {
 
818
            return (/.*/);
 
819
        }
 
820
 
 
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/*'.
 
824
            if (!key) {
 
825
                return operator === '*' ? '.*' : match;
 
826
            }
 
827
 
 
828
            keys.push(key);
 
829
            return operator === '*' ? '(.*?)' : '([^/#?]*)';
 
830
        });
 
831
 
 
832
        return new RegExp('^' + path + '$');
 
833
    },
 
834
 
 
835
    /**
 
836
    Gets a request object that can be passed to a route handler.
 
837
 
 
838
    @method _getRequest
 
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.
 
843
    @protected
 
844
    **/
 
845
    _getRequest: function (path, url, src) {
 
846
        return {
 
847
            path : path,
 
848
            query: this._parseQuery(this._getQuery()),
 
849
            url  : url,
 
850
            src  : src
 
851
        };
 
852
    },
 
853
 
 
854
    /**
 
855
    Gets a response object that can be passed to a route handler.
 
856
 
 
857
    @method _getResponse
 
858
    @param {Object} req Request object.
 
859
    @return {Object} Response Object.
 
860
    @protected
 
861
    **/
 
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);
 
867
        };
 
868
 
 
869
        res.req = req;
 
870
        return res;
 
871
    },
 
872
 
 
873
    /**
 
874
    Getter for the `routes` attribute.
 
875
 
 
876
    @method _getRoutes
 
877
    @return {Object[]} Array of route objects.
 
878
    @protected
 
879
    **/
 
880
    _getRoutes: function () {
 
881
        return this._routes.concat();
 
882
    },
 
883
 
 
884
    /**
 
885
    Gets the current full URL.
 
886
 
 
887
    @method _getURL
 
888
    @return {String} URL.
 
889
    @protected
 
890
    **/
 
891
    _getURL: function () {
 
892
        var url = Y.getLocation().toString();
 
893
 
 
894
        if (!this._html5) {
 
895
            url = this._upgradeURL(url);
 
896
        }
 
897
 
 
898
        return url;
 
899
    },
 
900
 
 
901
    /**
 
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.
 
904
 
 
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.
 
907
 
 
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.
 
911
    @protected
 
912
    **/
 
913
    _hasSameOrigin: function (url) {
 
914
        var origin = ((url && url.match(this._regexUrlOrigin)) || [])[0];
 
915
 
 
916
        // Prepend current scheme to scheme-relative URLs.
 
917
        if (origin && origin.indexOf('//') === 0) {
 
918
            origin = Y.getLocation().protocol + origin;
 
919
        }
 
920
 
 
921
        return !origin || origin === this._getOrigin();
 
922
    },
 
923
 
 
924
    /**
 
925
    Joins the `root` URL to the specified _url_, normalizing leading/trailing
 
926
    `/` characters.
 
927
 
 
928
    @example
 
929
        router.set('root', '/foo');
 
930
        router._joinURL('bar');  // => '/foo/bar'
 
931
        router._joinURL('/bar'); // => '/foo/bar'
 
932
 
 
933
        router.set('root', '/foo/');
 
934
        router._joinURL('bar');  // => '/foo/bar'
 
935
        router._joinURL('/bar'); // => '/foo/bar'
 
936
 
 
937
    @method _joinURL
 
938
    @param {String} url URL to append to the `root` URL.
 
939
    @return {String} Joined URL.
 
940
    @protected
 
941
    **/
 
942
    _joinURL: function (url) {
 
943
        var root = this.get('root');
 
944
 
 
945
        // Causes `url` to _always_ begin with a "/".
 
946
        url = this.removeRoot(url);
 
947
 
 
948
        if (url.charAt(0) === '/') {
 
949
            url = url.substring(1);
 
950
        }
 
951
 
 
952
        return root && root.charAt(root.length - 1) === '/' ?
 
953
                root + url :
 
954
                root + '/' + url;
 
955
    },
 
956
 
 
957
    /**
 
958
    Returns a normalized path, ridding it of any '..' segments and properly
 
959
    handling leading and trailing slashes.
 
960
 
 
961
    @method _normalizePath
 
962
    @param {String} path URL path to normalize.
 
963
    @return {String} Normalized path.
 
964
    @protected
 
965
    @since 3.5.0
 
966
    **/
 
967
    _normalizePath: function (path) {
 
968
        var dots  = '..',
 
969
            slash = '/',
 
970
            i, len, normalized, segments, segment, stack;
 
971
 
 
972
        if (!path || path === slash) {
 
973
            return slash;
 
974
        }
 
975
 
 
976
        segments = path.split(slash);
 
977
        stack    = [];
 
978
 
 
979
        for (i = 0, len = segments.length; i < len; ++i) {
 
980
            segment = segments[i];
 
981
 
 
982
            if (segment === dots) {
 
983
                stack.pop();
 
984
            } else if (segment) {
 
985
                stack.push(segment);
 
986
            }
 
987
        }
 
988
 
 
989
        normalized = slash + stack.join(slash);
 
990
 
 
991
        // Append trailing slash if necessary.
 
992
        if (normalized !== slash && path.charAt(path.length - 1) === slash) {
 
993
            normalized += slash;
 
994
        }
 
995
 
 
996
        return normalized;
 
997
    },
 
998
 
 
999
    /**
 
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.
 
1002
 
 
1003
    @method _parseQuery
 
1004
    @param {String} query Query string to parse.
 
1005
    @return {Object} Hash of key/value pairs for query parameters.
 
1006
    @protected
 
1007
    **/
 
1008
    _parseQuery: QS && QS.parse ? QS.parse : function (query) {
 
1009
        var decode = this._decode,
 
1010
            params = query.split('&'),
 
1011
            i      = 0,
 
1012
            len    = params.length,
 
1013
            result = {},
 
1014
            param;
 
1015
 
 
1016
        for (; i < len; ++i) {
 
1017
            param = params[i].split('=');
 
1018
 
 
1019
            if (param[0]) {
 
1020
                result[decode(param[0])] = decode(param[1] || '');
 
1021
            }
 
1022
        }
 
1023
 
 
1024
        return result;
 
1025
    },
 
1026
 
 
1027
    /**
 
1028
    Queues up a `_save()` call to run after all previously-queued calls have
 
1029
    finished.
 
1030
 
 
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
 
1033
    URL.
 
1034
 
 
1035
    All arguments passed to `_queue()` will be passed on to `_save()` when the
 
1036
    queued function is executed.
 
1037
 
 
1038
    @method _queue
 
1039
    @chainable
 
1040
    @see _dequeue
 
1041
    @protected
 
1042
    **/
 
1043
    _queue: function () {
 
1044
        var args = arguments,
 
1045
            self = this;
 
1046
 
 
1047
        saveQueue.push(function () {
 
1048
            if (self._html5) {
 
1049
                if (Y.UA.ios && Y.UA.ios < 5) {
 
1050
                    // iOS <5 has buggy HTML5 history support, and needs to be
 
1051
                    // synchronous.
 
1052
                    self._save.apply(self, args);
 
1053
                } else {
 
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);
 
1059
                    }, 1);
 
1060
                }
 
1061
            } else {
 
1062
                self._dispatching = true; // otherwise we'll dequeue too quickly
 
1063
                self._save.apply(self, args);
 
1064
            }
 
1065
 
 
1066
            return self;
 
1067
        });
 
1068
 
 
1069
        return !this._dispatching ? this._dequeue() : this;
 
1070
    },
 
1071
 
 
1072
    /**
 
1073
    Returns the normalized result of resolving the `path` against the current
 
1074
    path. Falsy values for `path` will return just the current path.
 
1075
 
 
1076
    @method _resolvePath
 
1077
    @param {String} path URL path to resolve.
 
1078
    @return {String} Resolved path.
 
1079
    @protected
 
1080
    @since 3.5.0
 
1081
    **/
 
1082
    _resolvePath: function (path) {
 
1083
        if (!path) {
 
1084
            return Y.getLocation().pathname;
 
1085
        }
 
1086
 
 
1087
        if (path.charAt(0) !== '/') {
 
1088
            path = this._getPathRoot() + path;
 
1089
        }
 
1090
 
 
1091
        return this._normalizePath(path);
 
1092
    },
 
1093
 
 
1094
    /**
 
1095
    Resolves the specified URL against the current URL.
 
1096
 
 
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.
 
1102
 
 
1103
    @method _resolveURL
 
1104
    @param {String} url URL to resolve.
 
1105
    @return {String} Resolved URL.
 
1106
    @protected
 
1107
    @since 3.5.0
 
1108
    **/
 
1109
    _resolveURL: function (url) {
 
1110
        var parts    = url && url.match(this._regexURL),
 
1111
            origin, path, query, hash, resolved;
 
1112
 
 
1113
        if (!parts) {
 
1114
            return Y.getLocation().toString();
 
1115
        }
 
1116
 
 
1117
        origin = parts[1];
 
1118
        path   = parts[2];
 
1119
        query  = parts[3];
 
1120
        hash   = parts[4];
 
1121
 
 
1122
        // Absolute and scheme-relative URLs are assumed to be fully-resolved.
 
1123
        if (origin) {
 
1124
            // Prepend the current scheme for scheme-relative URLs.
 
1125
            if (origin.indexOf('//') === 0) {
 
1126
                origin = Y.getLocation().protocol + origin;
 
1127
            }
 
1128
 
 
1129
            return origin + (path || '/') + (query || '') + (hash || '');
 
1130
        }
 
1131
 
 
1132
        // Will default to the current origin and current path.
 
1133
        resolved = this._getOrigin() + this._resolvePath(path);
 
1134
 
 
1135
        // A path or query for the specified URL trumps the current URL's.
 
1136
        if (path || query) {
 
1137
            return resolved + (query || '') + (hash || '');
 
1138
        }
 
1139
 
 
1140
        query = this._getQuery();
 
1141
 
 
1142
        return resolved + (query ? ('?' + query) : '') + (hash || '');
 
1143
    },
 
1144
 
 
1145
    /**
 
1146
    Saves a history entry using either `pushState()` or the location hash.
 
1147
 
 
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
 
1150
    an error.
 
1151
 
 
1152
    @method _save
 
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.
 
1156
    @chainable
 
1157
    @protected
 
1158
    **/
 
1159
    _save: function (url, replace) {
 
1160
        var urlIsString = typeof url === 'string',
 
1161
            currentPath, root, hash;
 
1162
 
 
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.');
 
1166
            return this;
 
1167
        }
 
1168
 
 
1169
        // Joins the `url` with the `root`.
 
1170
        if (urlIsString) {
 
1171
            url = this._joinURL(url);
 
1172
        }
 
1173
 
 
1174
        // Force _ready to true to ensure that the history change is handled
 
1175
        // even if _save is called before the `ready` event fires.
 
1176
        this._ready = true;
 
1177
 
 
1178
        if (this._html5) {
 
1179
            this._history[replace ? 'replace' : 'add'](null, {url: url});
 
1180
        } else {
 
1181
            currentPath = Y.getLocation().pathname;
 
1182
            root        = this.get('root');
 
1183
            hash        = HistoryHash.getHash();
 
1184
 
 
1185
            if (!urlIsString) {
 
1186
                url = hash;
 
1187
            }
 
1188
 
 
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);
 
1194
            }
 
1195
 
 
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.
 
1199
            if (url === hash) {
 
1200
                Y.Router.dispatch();
 
1201
            } else {
 
1202
                HistoryHash[replace ? 'replaceHash' : 'setHash'](url);
 
1203
            }
 
1204
        }
 
1205
 
 
1206
        return this;
 
1207
    },
 
1208
 
 
1209
    /**
 
1210
    Setter for the `routes` attribute.
 
1211
 
 
1212
    @method _setRoutes
 
1213
    @param {Object[]} routes Array of route objects.
 
1214
    @return {Object[]} Array of route objects.
 
1215
    @protected
 
1216
    **/
 
1217
    _setRoutes: function (routes) {
 
1218
        this._routes = [];
 
1219
 
 
1220
        YArray.each(routes, function (route) {
 
1221
            // Makes sure to check `callback` for back-compat.
 
1222
            var callbacks = route.callbacks || route.callback;
 
1223
 
 
1224
            this.route(route.path, callbacks);
 
1225
        }, this);
 
1226
 
 
1227
        return this._routes.concat();
 
1228
    },
 
1229
 
 
1230
    /**
 
1231
    Upgrades a hash-based URL to a full-path URL, if necessary.
 
1232
 
 
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
 
1235
    returned as-is.
 
1236
 
 
1237
    @example
 
1238
        app._upgradeURL('http://example.com/#/foo/'); // => 'http://example.com/foo/';
 
1239
 
 
1240
    @method _upgradeURL
 
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.
 
1243
    @protected
 
1244
    @since 3.5.0
 
1245
    **/
 
1246
    _upgradeURL: function (url) {
 
1247
        // We should not try to upgrade paths for external URLs.
 
1248
        if (!this._hasSameOrigin(url)) {
 
1249
            return url;
 
1250
        }
 
1251
 
 
1252
        var hash       = (url.match(/#(.*)$/) || [])[1] || '',
 
1253
            hashPrefix = Y.HistoryHash.hashPrefix,
 
1254
            hashPath;
 
1255
 
 
1256
        // Strip any hash prefix, like hash-bangs.
 
1257
        if (hashPrefix && hash.indexOf(hashPrefix) === 0) {
 
1258
            hash = hash.replace(hashPrefix, '');
 
1259
        }
 
1260
 
 
1261
        // If the hash looks like a URL path, assume it is, and upgrade it!
 
1262
        if (hash) {
 
1263
            hashPath = this._getHashPath(hash);
 
1264
 
 
1265
            if (hashPath) {
 
1266
                return this._resolveURL(hashPath);
 
1267
            }
 
1268
        }
 
1269
 
 
1270
        return url;
 
1271
    },
 
1272
 
 
1273
    // -- Protected Event Handlers ---------------------------------------------
 
1274
 
 
1275
    /**
 
1276
    Handles `history:change` and `hashchange` events.
 
1277
 
 
1278
    @method _afterHistoryChange
 
1279
    @param {EventFacade} e
 
1280
    @protected
 
1281
    **/
 
1282
    _afterHistoryChange: function (e) {
 
1283
        var self       = this,
 
1284
            src        = e.src,
 
1285
            prevURL    = self._url,
 
1286
            currentURL = self._getURL();
 
1287
 
 
1288
        self._url = currentURL;
 
1289
 
 
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(/#.*$/, ''))) {
 
1297
 
 
1298
            return;
 
1299
        }
 
1300
 
 
1301
        self._dispatch(self._getPath(), currentURL, src);
 
1302
    },
 
1303
 
 
1304
    // -- Default Event Handlers -----------------------------------------------
 
1305
 
 
1306
    /**
 
1307
    Default handler for the `ready` event.
 
1308
 
 
1309
    @method _defReadyFn
 
1310
    @param {EventFacade} e
 
1311
    @protected
 
1312
    **/
 
1313
    _defReadyFn: function (e) {
 
1314
        this._ready = true;
 
1315
    }
 
1316
}, {
 
1317
    // -- Static Properties ----------------------------------------------------
 
1318
    NAME: 'router',
 
1319
 
 
1320
    ATTRS: {
 
1321
        /**
 
1322
        Whether or not this browser is capable of using HTML5 history.
 
1323
 
 
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
 
1326
        consequences.
 
1327
 
 
1328
        @attribute html5
 
1329
        @type Boolean
 
1330
        @initOnly
 
1331
        **/
 
1332
        html5: {
 
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.
 
1336
            //
 
1337
            // See http://code.google.com/p/android/issues/detail?id=17471
 
1338
            valueFn: function () { return Y.Router.html5; },
 
1339
            writeOnce: 'initOnly'
 
1340
        },
 
1341
 
 
1342
        /**
 
1343
        Absolute root path from which all routes should be evaluated.
 
1344
 
 
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/`.
 
1351
 
 
1352
        @attribute root
 
1353
        @type String
 
1354
        @default `''`
 
1355
        **/
 
1356
        root: {
 
1357
            value: ''
 
1358
        },
 
1359
 
 
1360
        /**
 
1361
        Array of route objects.
 
1362
 
 
1363
        Each item in the array must be an object with the following properties:
 
1364
 
 
1365
          * `path`: String or regex representing the path to match. See the docs
 
1366
            for the `route()` method for more details.
 
1367
 
 
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.
 
1372
 
 
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.
 
1376
 
 
1377
        @attribute routes
 
1378
        @type Object[]
 
1379
        @default `[]`
 
1380
        @see route
 
1381
        **/
 
1382
        routes: {
 
1383
            value : [],
 
1384
            getter: '_getRoutes',
 
1385
            setter: '_setRoutes'
 
1386
        }
 
1387
    },
 
1388
 
 
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),
 
1391
 
 
1392
    // To make this testable.
 
1393
    _instances: instances,
 
1394
 
 
1395
    /**
 
1396
    Dispatches to the first route handler that matches the specified `path` for
 
1397
    all active router instances.
 
1398
 
 
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.
 
1402
 
 
1403
    @method dispatch
 
1404
    @static
 
1405
    @since 3.6.0
 
1406
    **/
 
1407
    dispatch: function () {
 
1408
        var i, len, router;
 
1409
 
 
1410
        for (i = 0, len = instances.length; i < len; i += 1) {
 
1411
            router = instances[i];
 
1412
 
 
1413
            if (router) {
 
1414
                router._dispatch(router._getPath(), router._getURL());
 
1415
            }
 
1416
        }
 
1417
    }
 
1418
});
 
1419
 
 
1420
/**
 
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
 
1423
version of YUI.
 
1424
 
 
1425
@class Controller
 
1426
@constructor
 
1427
@extends Base
 
1428
@deprecated Use `Router` instead.
 
1429
@see Router
 
1430
**/
 
1431
Y.Controller = Y.Router;
 
1432
 
 
1433
 
 
1434
}, '3.10.3', {"optional": ["querystring-parse"], "requires": ["array-extras", "base-build", "history"]});