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

« back to all changes in this revision

Viewing changes to lib/yuilib/3.9.1/build/router/router.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
 
        });
420
 
 
421
 
        // Using middleware.
422
 
 
423
 
        router.findUser = function (req, res, next) {
424
 
            req.user = this.get('users').findById(req.params.user);
425
 
            next();
426
 
        };
427
 
 
428
 
        router.route('/users/:user', 'findUser', function (req, res, next) {
429
 
            // The `findUser` middleware puts the `user` object on the `req`.
430
 
        });
431
 
 
432
 
    @method route
433
 
    @param {String|RegExp} path Path to match. May be a string or a regular
434
 
      expression.
435
 
    @param {Array|Function|String} callbacks* Callback functions to call
436
 
        whenever this route is triggered. These can be specified as separate
437
 
        arguments, or in arrays, or both. If a callback is specified as a
438
 
        string, the named function will be called on this router instance.
439
 
 
440
 
      @param {Object} callbacks.req Request object containing information about
441
 
          the request. It contains the following properties.
442
 
 
443
 
        @param {Array|Object} callbacks.req.params Captured parameters matched by
444
 
          the route path specification. If a string path was used and contained
445
 
          named parameters, then this will be a key/value hash mapping parameter
446
 
          names to their matched values. If a regex path was used, this will be
447
 
          an array of subpattern matches starting at index 0 for the full match,
448
 
          then 1 for the first subpattern match, and so on.
449
 
        @param {String} callbacks.req.path The current URL path.
450
 
        @param {Number} callbacks.req.pendingCallbacks Number of remaining
451
 
          callbacks the route handler has after this one in the dispatch chain.
452
 
        @param {Number} callbacks.req.pendingRoutes Number of matching routes
453
 
          after this one in the dispatch chain.
454
 
        @param {Object} callbacks.req.query Query hash representing the URL
455
 
          query string, if any. Parameter names are keys, and are mapped to
456
 
          parameter values.
457
 
        @param {String} callbacks.req.url The full URL.
458
 
        @param {String} callbacks.req.src What initiated the dispatch. In an
459
 
          HTML5 browser, when the back/forward buttons are used, this property
460
 
          will have a value of "popstate".
461
 
 
462
 
      @param {Object} callbacks.res Response object containing methods and
463
 
          information that relate to responding to a request. It contains the
464
 
          following properties.
465
 
        @param {Object} callbacks.res.req Reference to the request object.
466
 
 
467
 
      @param {Function} callbacks.next Function to pass control to the next
468
 
          callback or the next matching route if no more callbacks (middleware)
469
 
          exist for the current route handler. If you don't call this function,
470
 
          then no further callbacks or route handlers will be executed, even if
471
 
          there are more that match. If you do call this function, then the next
472
 
          callback (if any) or matching route handler (if any) will be called.
473
 
          All of these functions will receive the same `req` and `res` objects
474
 
          that were passed to this route (so you can use these objects to pass
475
 
          data along to subsequent callbacks and routes).
476
 
        @param {String} [callbacks.next.err] Optional error which will stop the
477
 
          dispatch chaining for this `req`, unless the value is `"route"`, which
478
 
          is special cased to jump skip past any callbacks for the current route
479
 
          and pass control the next route handler.
480
 
    @chainable
481
 
    **/
482
 
    route: function (path, callbacks) {
483
 
        callbacks = YArray.flatten(YArray(arguments, 1, true));
484
 
 
485
 
        var keys = [];
486
 
 
487
 
        this._routes.push({
488
 
            callbacks: callbacks,
489
 
            keys     : keys,
490
 
            path     : path,
491
 
            regex    : this._getRegex(path, keys),
492
 
 
493
 
            // For back-compat.
494
 
            callback: callbacks[0]
495
 
        });
496
 
 
497
 
        return this;
498
 
    },
499
 
 
500
 
    /**
501
 
    Saves a new browser history entry and dispatches to the first matching route
502
 
    handler, if any.
503
 
 
504
 
    Behind the scenes, this method uses HTML5 `pushState()` in browsers that
505
 
    support it (or the location hash in older browsers and IE) to change the
506
 
    URL and create a history entry.
507
 
 
508
 
    The specified URL must share the same origin (i.e., protocol, host, and
509
 
    port) as the current page, or an error will occur.
510
 
 
511
 
    @example
512
 
        // Starting URL: http://example.com/
513
 
 
514
 
        router.save('/path/');
515
 
        // New URL: http://example.com/path/
516
 
 
517
 
        router.save('/path?foo=bar');
518
 
        // New URL: http://example.com/path?foo=bar
519
 
 
520
 
        router.save('/');
521
 
        // New URL: http://example.com/
522
 
 
523
 
    @method save
524
 
    @param {String} [url] URL to set. This URL needs to be of the same origin as
525
 
      the current URL. This can be a URL relative to the router's `root`
526
 
      attribute. If no URL is specified, the page's current URL will be used.
527
 
    @chainable
528
 
    @see replace()
529
 
    **/
530
 
    save: function (url) {
531
 
        return this._queue(url);
532
 
    },
533
 
 
534
 
    /**
535
 
    Upgrades a hash-based URL to an HTML5 URL if necessary. In non-HTML5
536
 
    browsers, this method is a noop.
537
 
 
538
 
    @method upgrade
539
 
    @return {Boolean} `true` if the URL was upgraded, `false` otherwise.
540
 
    **/
541
 
    upgrade: function () {
542
 
        if (!this._html5) {
543
 
            return false;
544
 
        }
545
 
 
546
 
        // Get the resolve hash path.
547
 
        var hashPath = this._getHashPath();
548
 
 
549
 
        if (hashPath) {
550
 
            // This is an HTML5 browser and we have a hash-based path in the
551
 
            // URL, so we need to upgrade the URL to a non-hash URL. This
552
 
            // will trigger a `history:change` event, which will in turn
553
 
            // trigger a dispatch.
554
 
            this.once(EVT_READY, function () {
555
 
                this.replace(hashPath);
556
 
            });
557
 
 
558
 
            return true;
559
 
        }
560
 
 
561
 
        return false;
562
 
    },
563
 
 
564
 
    // -- Protected Methods ----------------------------------------------------
565
 
 
566
 
    /**
567
 
    Wrapper around `decodeURIComponent` that also converts `+` chars into
568
 
    spaces.
569
 
 
570
 
    @method _decode
571
 
    @param {String} string String to decode.
572
 
    @return {String} Decoded string.
573
 
    @protected
574
 
    **/
575
 
    _decode: function (string) {
576
 
        return decodeURIComponent(string.replace(/\+/g, ' '));
577
 
    },
578
 
 
579
 
    /**
580
 
    Shifts the topmost `_save()` call off the queue and executes it. Does
581
 
    nothing if the queue is empty.
582
 
 
583
 
    @method _dequeue
584
 
    @chainable
585
 
    @see _queue
586
 
    @protected
587
 
    **/
588
 
    _dequeue: function () {
589
 
        var self = this,
590
 
            fn;
591
 
 
592
 
        // If window.onload hasn't yet fired, wait until it has before
593
 
        // dequeueing. This will ensure that we don't call pushState() before an
594
 
        // initial popstate event has fired.
595
 
        if (!YUI.Env.windowLoaded) {
596
 
            Y.once('load', function () {
597
 
                self._dequeue();
598
 
            });
599
 
 
600
 
            return this;
601
 
        }
602
 
 
603
 
        fn = saveQueue.shift();
604
 
        return fn ? fn() : this;
605
 
    },
606
 
 
607
 
    /**
608
 
    Dispatches to the first route handler that matches the specified _path_.
609
 
 
610
 
    If called before the `ready` event has fired, the dispatch will be aborted.
611
 
    This ensures normalized behavior between Chrome (which fires a `popstate`
612
 
    event on every pageview) and other browsers (which do not).
613
 
 
614
 
    @method _dispatch
615
 
    @param {String} path URL path.
616
 
    @param {String} url Full URL.
617
 
    @param {String} src What initiated the dispatch.
618
 
    @chainable
619
 
    @protected
620
 
    **/
621
 
    _dispatch: function (path, url, src) {
622
 
        var self      = this,
623
 
            decode    = self._decode,
624
 
            routes    = self.match(path),
625
 
            callbacks = [],
626
 
            matches, req, res;
627
 
 
628
 
        self._dispatching = self._dispatched = true;
629
 
 
630
 
        if (!routes || !routes.length) {
631
 
            self._dispatching = false;
632
 
            return self;
633
 
        }
634
 
 
635
 
        req = self._getRequest(path, url, src);
636
 
        res = self._getResponse(req);
637
 
 
638
 
        req.next = function (err) {
639
 
            var callback, name, route;
640
 
 
641
 
            if (err) {
642
 
                // Special case "route" to skip to the next route handler
643
 
                // avoiding any additional callbacks for the current route.
644
 
                if (err === 'route') {
645
 
                    callbacks = [];
646
 
                    req.next();
647
 
                } else {
648
 
                    Y.error(err);
649
 
                }
650
 
 
651
 
            } else if ((callback = callbacks.shift())) {
652
 
                if (typeof callback === 'string') {
653
 
                    name     = callback;
654
 
                    callback = self[name];
655
 
 
656
 
                    if (!callback) {
657
 
                        Y.error('Router: Callback not found: ' + name, null, 'router');
658
 
                    }
659
 
                }
660
 
 
661
 
                // Allow access to the number of remaining callbacks for the
662
 
                // route.
663
 
                req.pendingCallbacks = callbacks.length;
664
 
 
665
 
                callback.call(self, req, res, req.next);
666
 
 
667
 
            } else if ((route = routes.shift())) {
668
 
                // Make a copy of this route's `callbacks` so the original array
669
 
                // is preserved.
670
 
                callbacks = route.callbacks.concat();
671
 
 
672
 
                // Decode each of the path matches so that the any URL-encoded
673
 
                // path segments are decoded in the `req.params` object.
674
 
                matches = YArray.map(route.regex.exec(path) || [], decode);
675
 
 
676
 
                // Use named keys for parameter names if the route path contains
677
 
                // named keys. Otherwise, use numerical match indices.
678
 
                if (matches.length === route.keys.length + 1) {
679
 
                    req.params = YArray.hash(route.keys, matches.slice(1));
680
 
                } else {
681
 
                    req.params = matches.concat();
682
 
                }
683
 
 
684
 
                // Allow access to the number of remaining routes for this
685
 
                // request.
686
 
                req.pendingRoutes = routes.length;
687
 
 
688
 
                // Execute this route's `callbacks`.
689
 
                req.next();
690
 
            }
691
 
        };
692
 
 
693
 
        req.next();
694
 
 
695
 
        self._dispatching = false;
696
 
        return self._dequeue();
697
 
    },
698
 
 
699
 
    /**
700
 
    Returns the resolved path from the hash fragment, or an empty string if the
701
 
    hash is not path-like.
702
 
 
703
 
    @method _getHashPath
704
 
    @param {String} [hash] Hash fragment to resolve into a path. By default this
705
 
        will be the hash from the current URL.
706
 
    @return {String} Current hash path, or an empty string if the hash is empty.
707
 
    @protected
708
 
    **/
709
 
    _getHashPath: function (hash) {
710
 
        hash || (hash = HistoryHash.getHash());
711
 
 
712
 
        // Make sure the `hash` is path-like.
713
 
        if (hash && hash.charAt(0) === '/') {
714
 
            return this._joinURL(hash);
715
 
        }
716
 
 
717
 
        return '';
718
 
    },
719
 
 
720
 
    /**
721
 
    Gets the location origin (i.e., protocol, host, and port) as a URL.
722
 
 
723
 
    @example
724
 
        http://example.com
725
 
 
726
 
    @method _getOrigin
727
 
    @return {String} Location origin (i.e., protocol, host, and port).
728
 
    @protected
729
 
    **/
730
 
    _getOrigin: function () {
731
 
        var location = Y.getLocation();
732
 
        return location.origin || (location.protocol + '//' + location.host);
733
 
    },
734
 
 
735
 
    /**
736
 
    Gets the current route path, relative to the `root` (if any).
737
 
 
738
 
    @method _getPath
739
 
    @return {String} Current route path.
740
 
    @protected
741
 
    **/
742
 
    _getPath: function () {
743
 
        var path = (!this._html5 && this._getHashPath()) ||
744
 
                Y.getLocation().pathname;
745
 
 
746
 
        return this.removeQuery(this.removeRoot(path));
747
 
    },
748
 
 
749
 
    /**
750
 
    Returns the current path root after popping off the last path segment,
751
 
    making it useful for resolving other URL paths against.
752
 
 
753
 
    The path root will always begin and end with a '/'.
754
 
 
755
 
    @method _getPathRoot
756
 
    @return {String} The URL's path root.
757
 
    @protected
758
 
    @since 3.5.0
759
 
    **/
760
 
    _getPathRoot: function () {
761
 
        var slash = '/',
762
 
            path  = Y.getLocation().pathname,
763
 
            segments;
764
 
 
765
 
        if (path.charAt(path.length - 1) === slash) {
766
 
            return path;
767
 
        }
768
 
 
769
 
        segments = path.split(slash);
770
 
        segments.pop();
771
 
 
772
 
        return segments.join(slash) + slash;
773
 
    },
774
 
 
775
 
    /**
776
 
    Gets the current route query string.
777
 
 
778
 
    @method _getQuery
779
 
    @return {String} Current route query string.
780
 
    @protected
781
 
    **/
782
 
    _getQuery: function () {
783
 
        var location = Y.getLocation(),
784
 
            hash, matches;
785
 
 
786
 
        if (this._html5) {
787
 
            return location.search.substring(1);
788
 
        }
789
 
 
790
 
        hash    = HistoryHash.getHash();
791
 
        matches = hash.match(this._regexUrlQuery);
792
 
 
793
 
        return hash && matches ? matches[1] : location.search.substring(1);
794
 
    },
795
 
 
796
 
    /**
797
 
    Creates a regular expression from the given route specification. If _path_
798
 
    is already a regex, it will be returned unmodified.
799
 
 
800
 
    @method _getRegex
801
 
    @param {String|RegExp} path Route path specification.
802
 
    @param {Array} keys Array reference to which route parameter names will be
803
 
      added.
804
 
    @return {RegExp} Route regex.
805
 
    @protected
806
 
    **/
807
 
    _getRegex: function (path, keys) {
808
 
        if (path instanceof RegExp) {
809
 
            return path;
810
 
        }
811
 
 
812
 
        // Special case for catchall paths.
813
 
        if (path === '*') {
814
 
            return (/.*/);
815
 
        }
816
 
 
817
 
        path = path.replace(this._regexPathParam, function (match, operator, key) {
818
 
            // Only `*` operators are supported for key-less matches to allowing
819
 
            // in-path wildcards like: '/foo/*'.
820
 
            if (!key) {
821
 
                return operator === '*' ? '.*' : match;
822
 
            }
823
 
 
824
 
            keys.push(key);
825
 
            return operator === '*' ? '(.*?)' : '([^/#?]*)';
826
 
        });
827
 
 
828
 
        return new RegExp('^' + path + '$');
829
 
    },
830
 
 
831
 
    /**
832
 
    Gets a request object that can be passed to a route handler.
833
 
 
834
 
    @method _getRequest
835
 
    @param {String} path Current path being dispatched.
836
 
    @param {String} url Current full URL being dispatched.
837
 
    @param {String} src What initiated the dispatch.
838
 
    @return {Object} Request object.
839
 
    @protected
840
 
    **/
841
 
    _getRequest: function (path, url, src) {
842
 
        return {
843
 
            path : path,
844
 
            query: this._parseQuery(this._getQuery()),
845
 
            url  : url,
846
 
            src  : src
847
 
        };
848
 
    },
849
 
 
850
 
    /**
851
 
    Gets a response object that can be passed to a route handler.
852
 
 
853
 
    @method _getResponse
854
 
    @param {Object} req Request object.
855
 
    @return {Object} Response Object.
856
 
    @protected
857
 
    **/
858
 
    _getResponse: function (req) {
859
 
        // For backwards compatibility, the response object is a function that
860
 
        // calls `next()` on the request object and returns the result.
861
 
        var res = function () {
862
 
            return req.next.apply(this, arguments);
863
 
        };
864
 
 
865
 
        res.req = req;
866
 
        return res;
867
 
    },
868
 
 
869
 
    /**
870
 
    Getter for the `routes` attribute.
871
 
 
872
 
    @method _getRoutes
873
 
    @return {Object[]} Array of route objects.
874
 
    @protected
875
 
    **/
876
 
    _getRoutes: function () {
877
 
        return this._routes.concat();
878
 
    },
879
 
 
880
 
    /**
881
 
    Gets the current full URL.
882
 
 
883
 
    @method _getURL
884
 
    @return {String} URL.
885
 
    @protected
886
 
    **/
887
 
    _getURL: function () {
888
 
        var url = Y.getLocation().toString();
889
 
 
890
 
        if (!this._html5) {
891
 
            url = this._upgradeURL(url);
892
 
        }
893
 
 
894
 
        return url;
895
 
    },
896
 
 
897
 
    /**
898
 
    Returns `true` when the specified `url` is from the same origin as the
899
 
    current URL; i.e., the protocol, host, and port of the URLs are the same.
900
 
 
901
 
    All host or path relative URLs are of the same origin. A scheme-relative URL
902
 
    is first prefixed with the current scheme before being evaluated.
903
 
 
904
 
    @method _hasSameOrigin
905
 
    @param {String} url URL to compare origin with the current URL.
906
 
    @return {Boolean} Whether the URL has the same origin of the current URL.
907
 
    @protected
908
 
    **/
909
 
    _hasSameOrigin: function (url) {
910
 
        var origin = ((url && url.match(this._regexUrlOrigin)) || [])[0];
911
 
 
912
 
        // Prepend current scheme to scheme-relative URLs.
913
 
        if (origin && origin.indexOf('//') === 0) {
914
 
            origin = Y.getLocation().protocol + origin;
915
 
        }
916
 
 
917
 
        return !origin || origin === this._getOrigin();
918
 
    },
919
 
 
920
 
    /**
921
 
    Joins the `root` URL to the specified _url_, normalizing leading/trailing
922
 
    `/` characters.
923
 
 
924
 
    @example
925
 
        router.set('root', '/foo');
926
 
        router._joinURL('bar');  // => '/foo/bar'
927
 
        router._joinURL('/bar'); // => '/foo/bar'
928
 
 
929
 
        router.set('root', '/foo/');
930
 
        router._joinURL('bar');  // => '/foo/bar'
931
 
        router._joinURL('/bar'); // => '/foo/bar'
932
 
 
933
 
    @method _joinURL
934
 
    @param {String} url URL to append to the `root` URL.
935
 
    @return {String} Joined URL.
936
 
    @protected
937
 
    **/
938
 
    _joinURL: function (url) {
939
 
        var root = this.get('root');
940
 
 
941
 
        // Causes `url` to _always_ begin with a "/".
942
 
        url = this.removeRoot(url);
943
 
 
944
 
        if (url.charAt(0) === '/') {
945
 
            url = url.substring(1);
946
 
        }
947
 
 
948
 
        return root && root.charAt(root.length - 1) === '/' ?
949
 
                root + url :
950
 
                root + '/' + url;
951
 
    },
952
 
 
953
 
    /**
954
 
    Returns a normalized path, ridding it of any '..' segments and properly
955
 
    handling leading and trailing slashes.
956
 
 
957
 
    @method _normalizePath
958
 
    @param {String} path URL path to normalize.
959
 
    @return {String} Normalized path.
960
 
    @protected
961
 
    @since 3.5.0
962
 
    **/
963
 
    _normalizePath: function (path) {
964
 
        var dots  = '..',
965
 
            slash = '/',
966
 
            i, len, normalized, segments, segment, stack;
967
 
 
968
 
        if (!path || path === slash) {
969
 
            return slash;
970
 
        }
971
 
 
972
 
        segments = path.split(slash);
973
 
        stack    = [];
974
 
 
975
 
        for (i = 0, len = segments.length; i < len; ++i) {
976
 
            segment = segments[i];
977
 
 
978
 
            if (segment === dots) {
979
 
                stack.pop();
980
 
            } else if (segment) {
981
 
                stack.push(segment);
982
 
            }
983
 
        }
984
 
 
985
 
        normalized = slash + stack.join(slash);
986
 
 
987
 
        // Append trailing slash if necessary.
988
 
        if (normalized !== slash && path.charAt(path.length - 1) === slash) {
989
 
            normalized += slash;
990
 
        }
991
 
 
992
 
        return normalized;
993
 
    },
994
 
 
995
 
    /**
996
 
    Parses a URL query string into a key/value hash. If `Y.QueryString.parse` is
997
 
    available, this method will be an alias to that.
998
 
 
999
 
    @method _parseQuery
1000
 
    @param {String} query Query string to parse.
1001
 
    @return {Object} Hash of key/value pairs for query parameters.
1002
 
    @protected
1003
 
    **/
1004
 
    _parseQuery: QS && QS.parse ? QS.parse : function (query) {
1005
 
        var decode = this._decode,
1006
 
            params = query.split('&'),
1007
 
            i      = 0,
1008
 
            len    = params.length,
1009
 
            result = {},
1010
 
            param;
1011
 
 
1012
 
        for (; i < len; ++i) {
1013
 
            param = params[i].split('=');
1014
 
 
1015
 
            if (param[0]) {
1016
 
                result[decode(param[0])] = decode(param[1] || '');
1017
 
            }
1018
 
        }
1019
 
 
1020
 
        return result;
1021
 
    },
1022
 
 
1023
 
    /**
1024
 
    Queues up a `_save()` call to run after all previously-queued calls have
1025
 
    finished.
1026
 
 
1027
 
    This is necessary because if we make multiple `_save()` calls before the
1028
 
    first call gets dispatched, then both calls will dispatch to the last call's
1029
 
    URL.
1030
 
 
1031
 
    All arguments passed to `_queue()` will be passed on to `_save()` when the
1032
 
    queued function is executed.
1033
 
 
1034
 
    @method _queue
1035
 
    @chainable
1036
 
    @see _dequeue
1037
 
    @protected
1038
 
    **/
1039
 
    _queue: function () {
1040
 
        var args = arguments,
1041
 
            self = this;
1042
 
 
1043
 
        saveQueue.push(function () {
1044
 
            if (self._html5) {
1045
 
                if (Y.UA.ios && Y.UA.ios < 5) {
1046
 
                    // iOS <5 has buggy HTML5 history support, and needs to be
1047
 
                    // synchronous.
1048
 
                    self._save.apply(self, args);
1049
 
                } else {
1050
 
                    // Wrapped in a timeout to ensure that _save() calls are
1051
 
                    // always processed asynchronously. This ensures consistency
1052
 
                    // between HTML5- and hash-based history.
1053
 
                    setTimeout(function () {
1054
 
                        self._save.apply(self, args);
1055
 
                    }, 1);
1056
 
                }
1057
 
            } else {
1058
 
                self._dispatching = true; // otherwise we'll dequeue too quickly
1059
 
                self._save.apply(self, args);
1060
 
            }
1061
 
 
1062
 
            return self;
1063
 
        });
1064
 
 
1065
 
        return !this._dispatching ? this._dequeue() : this;
1066
 
    },
1067
 
 
1068
 
    /**
1069
 
    Returns the normalized result of resolving the `path` against the current
1070
 
    path. Falsy values for `path` will return just the current path.
1071
 
 
1072
 
    @method _resolvePath
1073
 
    @param {String} path URL path to resolve.
1074
 
    @return {String} Resolved path.
1075
 
    @protected
1076
 
    @since 3.5.0
1077
 
    **/
1078
 
    _resolvePath: function (path) {
1079
 
        if (!path) {
1080
 
            return Y.getLocation().pathname;
1081
 
        }
1082
 
 
1083
 
        if (path.charAt(0) !== '/') {
1084
 
            path = this._getPathRoot() + path;
1085
 
        }
1086
 
 
1087
 
        return this._normalizePath(path);
1088
 
    },
1089
 
 
1090
 
    /**
1091
 
    Resolves the specified URL against the current URL.
1092
 
 
1093
 
    This method resolves URLs like a browser does and will always return an
1094
 
    absolute URL. When the specified URL is already absolute, it is assumed to
1095
 
    be fully resolved and is simply returned as is. Scheme-relative URLs are
1096
 
    prefixed with the current protocol. Relative URLs are giving the current
1097
 
    URL's origin and are resolved and normalized against the current path root.
1098
 
 
1099
 
    @method _resolveURL
1100
 
    @param {String} url URL to resolve.
1101
 
    @return {String} Resolved URL.
1102
 
    @protected
1103
 
    @since 3.5.0
1104
 
    **/
1105
 
    _resolveURL: function (url) {
1106
 
        var parts    = url && url.match(this._regexURL),
1107
 
            origin, path, query, hash, resolved;
1108
 
 
1109
 
        if (!parts) {
1110
 
            return Y.getLocation().toString();
1111
 
        }
1112
 
 
1113
 
        origin = parts[1];
1114
 
        path   = parts[2];
1115
 
        query  = parts[3];
1116
 
        hash   = parts[4];
1117
 
 
1118
 
        // Absolute and scheme-relative URLs are assumed to be fully-resolved.
1119
 
        if (origin) {
1120
 
            // Prepend the current scheme for scheme-relative URLs.
1121
 
            if (origin.indexOf('//') === 0) {
1122
 
                origin = Y.getLocation().protocol + origin;
1123
 
            }
1124
 
 
1125
 
            return origin + (path || '/') + (query || '') + (hash || '');
1126
 
        }
1127
 
 
1128
 
        // Will default to the current origin and current path.
1129
 
        resolved = this._getOrigin() + this._resolvePath(path);
1130
 
 
1131
 
        // A path or query for the specified URL trumps the current URL's.
1132
 
        if (path || query) {
1133
 
            return resolved + (query || '') + (hash || '');
1134
 
        }
1135
 
 
1136
 
        query = this._getQuery();
1137
 
 
1138
 
        return resolved + (query ? ('?' + query) : '') + (hash || '');
1139
 
    },
1140
 
 
1141
 
    /**
1142
 
    Saves a history entry using either `pushState()` or the location hash.
1143
 
 
1144
 
    This method enforces the same-origin security constraint; attempting to save
1145
 
    a `url` that is not from the same origin as the current URL will result in
1146
 
    an error.
1147
 
 
1148
 
    @method _save
1149
 
    @param {String} [url] URL for the history entry.
1150
 
    @param {Boolean} [replace=false] If `true`, the current history entry will
1151
 
      be replaced instead of a new one being added.
1152
 
    @chainable
1153
 
    @protected
1154
 
    **/
1155
 
    _save: function (url, replace) {
1156
 
        var urlIsString = typeof url === 'string',
1157
 
            currentPath, root;
1158
 
 
1159
 
        // Perform same-origin check on the specified URL.
1160
 
        if (urlIsString && !this._hasSameOrigin(url)) {
1161
 
            Y.error('Security error: The new URL must be of the same origin as the current URL.');
1162
 
            return this;
1163
 
        }
1164
 
 
1165
 
        // Joins the `url` with the `root`.
1166
 
        if (urlIsString) {
1167
 
            url = this._joinURL(url);
1168
 
        }
1169
 
 
1170
 
        // Force _ready to true to ensure that the history change is handled
1171
 
        // even if _save is called before the `ready` event fires.
1172
 
        this._ready = true;
1173
 
 
1174
 
        if (this._html5) {
1175
 
            this._history[replace ? 'replace' : 'add'](null, {url: url});
1176
 
        } else {
1177
 
            currentPath = Y.getLocation().pathname;
1178
 
            root        = this.get('root');
1179
 
 
1180
 
            // Determine if the `root` already exists in the current location's
1181
 
            // `pathname`, and if it does then we can exclude it from the
1182
 
            // hash-based path. No need to duplicate the info in the URL.
1183
 
            if (root === currentPath || root === this._getPathRoot()) {
1184
 
                url = this.removeRoot(url);
1185
 
            }
1186
 
 
1187
 
            // The `hashchange` event only fires when the new hash is actually
1188
 
            // different. This makes sure we'll always dequeue and dispatch
1189
 
            // _all_ router instances, mimicking the HTML5 behavior.
1190
 
            if (url === HistoryHash.getHash()) {
1191
 
                Y.Router.dispatch();
1192
 
            } else {
1193
 
                HistoryHash[replace ? 'replaceHash' : 'setHash'](url);
1194
 
            }
1195
 
        }
1196
 
 
1197
 
        return this;
1198
 
    },
1199
 
 
1200
 
    /**
1201
 
    Setter for the `routes` attribute.
1202
 
 
1203
 
    @method _setRoutes
1204
 
    @param {Object[]} routes Array of route objects.
1205
 
    @return {Object[]} Array of route objects.
1206
 
    @protected
1207
 
    **/
1208
 
    _setRoutes: function (routes) {
1209
 
        this._routes = [];
1210
 
 
1211
 
        YArray.each(routes, function (route) {
1212
 
            // Makes sure to check `callback` for back-compat.
1213
 
            var callbacks = route.callbacks || route.callback;
1214
 
 
1215
 
            this.route(route.path, callbacks);
1216
 
        }, this);
1217
 
 
1218
 
        return this._routes.concat();
1219
 
    },
1220
 
 
1221
 
    /**
1222
 
    Upgrades a hash-based URL to a full-path URL, if necessary.
1223
 
 
1224
 
    The specified `url` will be upgraded if its of the same origin as the
1225
 
    current URL and has a path-like hash. URLs that don't need upgrading will be
1226
 
    returned as-is.
1227
 
 
1228
 
    @example
1229
 
        app._upgradeURL('http://example.com/#/foo/'); // => 'http://example.com/foo/';
1230
 
 
1231
 
    @method _upgradeURL
1232
 
    @param {String} url The URL to upgrade from hash-based to full-path.
1233
 
    @return {String} The upgraded URL, or the specified URL untouched.
1234
 
    @protected
1235
 
    @since 3.5.0
1236
 
    **/
1237
 
    _upgradeURL: function (url) {
1238
 
        // We should not try to upgrade paths for external URLs.
1239
 
        if (!this._hasSameOrigin(url)) {
1240
 
            return url;
1241
 
        }
1242
 
 
1243
 
        var hash       = (url.match(/#(.*)$/) || [])[1] || '',
1244
 
            hashPrefix = Y.HistoryHash.hashPrefix,
1245
 
            hashPath;
1246
 
 
1247
 
        // Strip any hash prefix, like hash-bangs.
1248
 
        if (hashPrefix && hash.indexOf(hashPrefix) === 0) {
1249
 
            hash = hash.replace(hashPrefix, '');
1250
 
        }
1251
 
 
1252
 
        // If the hash looks like a URL path, assume it is, and upgrade it!
1253
 
        if (hash) {
1254
 
            hashPath = this._getHashPath(hash);
1255
 
 
1256
 
            if (hashPath) {
1257
 
                return this._resolveURL(hashPath);
1258
 
            }
1259
 
        }
1260
 
 
1261
 
        return url;
1262
 
    },
1263
 
 
1264
 
    // -- Protected Event Handlers ---------------------------------------------
1265
 
 
1266
 
    /**
1267
 
    Handles `history:change` and `hashchange` events.
1268
 
 
1269
 
    @method _afterHistoryChange
1270
 
    @param {EventFacade} e
1271
 
    @protected
1272
 
    **/
1273
 
    _afterHistoryChange: function (e) {
1274
 
        var self       = this,
1275
 
            src        = e.src,
1276
 
            prevURL    = self._url,
1277
 
            currentURL = self._getURL();
1278
 
 
1279
 
        self._url = currentURL;
1280
 
 
1281
 
        // Handles the awkwardness that is the `popstate` event. HTML5 browsers
1282
 
        // fire `popstate` right before they fire `hashchange`, and Chrome fires
1283
 
        // `popstate` on page load. If this router is not ready or the previous
1284
 
        // and current URLs only differ by their hash, then we want to ignore
1285
 
        // this `popstate` event.
1286
 
        if (src === 'popstate' &&
1287
 
                (!self._ready || prevURL.replace(/#.*$/, '') === currentURL.replace(/#.*$/, ''))) {
1288
 
 
1289
 
            return;
1290
 
        }
1291
 
 
1292
 
        self._dispatch(self._getPath(), currentURL, src);
1293
 
    },
1294
 
 
1295
 
    // -- Default Event Handlers -----------------------------------------------
1296
 
 
1297
 
    /**
1298
 
    Default handler for the `ready` event.
1299
 
 
1300
 
    @method _defReadyFn
1301
 
    @param {EventFacade} e
1302
 
    @protected
1303
 
    **/
1304
 
    _defReadyFn: function (e) {
1305
 
        this._ready = true;
1306
 
    }
1307
 
}, {
1308
 
    // -- Static Properties ----------------------------------------------------
1309
 
    NAME: 'router',
1310
 
 
1311
 
    ATTRS: {
1312
 
        /**
1313
 
        Whether or not this browser is capable of using HTML5 history.
1314
 
 
1315
 
        Setting this to `false` will force the use of hash-based history even on
1316
 
        HTML5 browsers, but please don't do this unless you understand the
1317
 
        consequences.
1318
 
 
1319
 
        @attribute html5
1320
 
        @type Boolean
1321
 
        @initOnly
1322
 
        **/
1323
 
        html5: {
1324
 
            // Android versions lower than 3.0 are buggy and don't update
1325
 
            // window.location after a pushState() call, so we fall back to
1326
 
            // hash-based history for them.
1327
 
            //
1328
 
            // See http://code.google.com/p/android/issues/detail?id=17471
1329
 
            valueFn: function () { return Y.Router.html5; },
1330
 
            writeOnce: 'initOnly'
1331
 
        },
1332
 
 
1333
 
        /**
1334
 
        Absolute root path from which all routes should be evaluated.
1335
 
 
1336
 
        For example, if your router is running on a page at
1337
 
        `http://example.com/myapp/` and you add a route with the path `/`, your
1338
 
        route will never execute, because the path will always be preceded by
1339
 
        `/myapp`. Setting `root` to `/myapp` would cause all routes to be
1340
 
        evaluated relative to that root URL, so the `/` route would then execute
1341
 
        when the user browses to `http://example.com/myapp/`.
1342
 
 
1343
 
        @attribute root
1344
 
        @type String
1345
 
        @default `''`
1346
 
        **/
1347
 
        root: {
1348
 
            value: ''
1349
 
        },
1350
 
 
1351
 
        /**
1352
 
        Array of route objects.
1353
 
 
1354
 
        Each item in the array must be an object with the following properties:
1355
 
 
1356
 
          * `path`: String or regex representing the path to match. See the docs
1357
 
            for the `route()` method for more details.
1358
 
 
1359
 
          * `callbacks`: Function or a string representing the name of a
1360
 
            function on this router instance that should be called when the
1361
 
            route is triggered. An array of functions and/or strings may also be
1362
 
            provided. See the docs for the `route()` method for more details.
1363
 
 
1364
 
        This attribute is intended to be used to set routes at init time, or to
1365
 
        completely reset all routes after init. To add routes after init without
1366
 
        resetting all existing routes, use the `route()` method.
1367
 
 
1368
 
        @attribute routes
1369
 
        @type Object[]
1370
 
        @default `[]`
1371
 
        @see route
1372
 
        **/
1373
 
        routes: {
1374
 
            value : [],
1375
 
            getter: '_getRoutes',
1376
 
            setter: '_setRoutes'
1377
 
        }
1378
 
    },
1379
 
 
1380
 
    // Used as the default value for the `html5` attribute, and for testing.
1381
 
    html5: Y.HistoryBase.html5 && (!Y.UA.android || Y.UA.android >= 3),
1382
 
 
1383
 
    // To make this testable.
1384
 
    _instances: instances,
1385
 
 
1386
 
    /**
1387
 
    Dispatches to the first route handler that matches the specified `path` for
1388
 
    all active router instances.
1389
 
 
1390
 
    This provides a mechanism to cause all active router instances to dispatch
1391
 
    to their route handlers without needing to change the URL or fire the
1392
 
    `history:change` or `hashchange` event.
1393
 
 
1394
 
    @method dispatch
1395
 
    @static
1396
 
    @since 3.6.0
1397
 
    **/
1398
 
    dispatch: function () {
1399
 
        var i, len, router;
1400
 
 
1401
 
        for (i = 0, len = instances.length; i < len; i += 1) {
1402
 
            router = instances[i];
1403
 
 
1404
 
            if (router) {
1405
 
                router._dispatch(router._getPath(), router._getURL());
1406
 
            }
1407
 
        }
1408
 
    }
1409
 
});
1410
 
 
1411
 
/**
1412
 
The `Controller` class was deprecated in YUI 3.5.0 and is now an alias for the
1413
 
`Router` class. Use that class instead. This alias will be removed in a future
1414
 
version of YUI.
1415
 
 
1416
 
@class Controller
1417
 
@constructor
1418
 
@extends Base
1419
 
@deprecated Use `Router` instead.
1420
 
@see Router
1421
 
**/
1422
 
Y.Controller = Y.Router;
1423
 
 
1424
 
 
1425
 
}, '3.9.1', {"optional": ["querystring-parse"], "requires": ["array-extras", "base-build", "history"]});