~ubuntu-branches/ubuntu/utopic/moodle/utopic

« back to all changes in this revision

Viewing changes to lib/yuilib/3.9.1/build/router/router-debug.js

  • Committer: Package Import Robot
  • Author(s): Thijs Kinkhorst
  • Date: 2014-05-12 16:10:38 UTC
  • mfrom: (36.1.3 sid)
  • Revision ID: package-import@ubuntu.com-20140512161038-puyqf65k4e0s8ytz
Tags: 2.6.3-1
New upstream release.

Show diffs side-by-side

added added

removed removed

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