~lutostag/ubuntu/utopic/maas/1.5.2

« back to all changes in this revision

Viewing changes to src/maasserver/static/js/yui/3.4.1/controller/controller.js

  • Committer: Package Import Robot
  • Author(s): Andres Rodriguez
  • Date: 2012-03-15 18:14:08 UTC
  • mfrom: (1.1.3)
  • Revision ID: package-import@ubuntu.com-20120315181408-zgl94hzo0x4n99an
Tags: 0.1+bzr295+dfsg-0ubuntu2
* debian/patches:
  - 01-fix-database-settings.patch: Update to set PSERV_URL.
  - 02-pserv-config.patch: Set port to 8001.
* debian/maas.postinst: Run maas-import-isos on install.
* debian/control: Depends on rabbitmq-server.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*
2
 
YUI 3.4.1 (build 4118)
3
 
Copyright 2011 Yahoo! Inc. All rights reserved.
4
 
Licensed under the BSD License.
5
 
http://yuilibrary.com/license/
6
 
*/
7
 
YUI.add('controller', function(Y) {
8
 
 
9
 
/**
10
 
The app framework provides simple MVC-like building blocks (models, model lists,
11
 
views, and controllers) for writing single-page JavaScript applications.
12
 
 
13
 
@main app
14
 
@module app
15
 
@since 3.4.0
16
 
**/
17
 
 
18
 
/**
19
 
Provides URL-based routing using HTML5 `pushState()` or the location hash.
20
 
 
21
 
@submodule controller
22
 
@since 3.4.0
23
 
**/
24
 
 
25
 
/**
26
 
Provides URL-based routing using HTML5 `pushState()` or the location hash.
27
 
 
28
 
This makes it easy to wire up route handlers for different application states
29
 
while providing full back/forward navigation support and bookmarkable, shareable
30
 
URLs.
31
 
 
32
 
@class Controller
33
 
@constructor
34
 
@extends Base
35
 
@since 3.4.0
36
 
**/
37
 
 
38
 
var HistoryHash = Y.HistoryHash,
39
 
    Lang        = Y.Lang,
40
 
    QS          = Y.QueryString,
41
 
    YArray      = Y.Array,
42
 
 
43
 
    // Android versions lower than 3.0 are buggy and don't update
44
 
    // window.location after a pushState() call, so we fall back to hash-based
45
 
    // history for them.
46
 
    //
47
 
    // See http://code.google.com/p/android/issues/detail?id=17471
48
 
    html5    = Y.HistoryBase.html5 && (!Y.UA.android || Y.UA.android >= 3),
49
 
    win      = Y.config.win,
50
 
    location = win.location,
51
 
 
52
 
    // We have to queue up pushState calls to avoid race conditions, since the
53
 
    // popstate event doesn't actually provide any info on what URL it's
54
 
    // associated with.
55
 
    saveQueue = [],
56
 
 
57
 
    /**
58
 
    Fired when the controller is ready to begin dispatching to route handlers.
59
 
 
60
 
    You shouldn't need to wait for this event unless you plan to implement some
61
 
    kind of custom dispatching logic. It's used internally in order to avoid
62
 
    dispatching to an initial route if a browser history change occurs first.
63
 
 
64
 
    @event ready
65
 
    @param {Boolean} dispatched `true` if routes have already been dispatched
66
 
      (most likely due to a history change).
67
 
    @fireOnce
68
 
    **/
69
 
    EVT_READY = 'ready';
70
 
 
71
 
function Controller() {
72
 
    Controller.superclass.constructor.apply(this, arguments);
73
 
}
74
 
 
75
 
Y.Controller = Y.extend(Controller, Y.Base, {
76
 
    // -- Public Properties ----------------------------------------------------
77
 
 
78
 
    /**
79
 
    Whether or not this browser is capable of using HTML5 history.
80
 
 
81
 
    This property is for informational purposes only. It's not configurable, and
82
 
    changing it will have no effect.
83
 
 
84
 
    @property html5
85
 
    @type Boolean
86
 
    **/
87
 
    html5: html5,
88
 
 
89
 
    /**
90
 
    Absolute root path from which all routes should be evaluated.
91
 
 
92
 
    For example, if your controller is running on a page at
93
 
    `http://example.com/myapp/` and you add a route with the path `/`, your
94
 
    route will never execute, because the path will always be preceded by
95
 
    `/myapp`. Setting `root` to `/myapp` would cause all routes to be evaluated
96
 
    relative to that root URL, so the `/` route would then execute when the
97
 
    user browses to `http://example.com/myapp/`.
98
 
 
99
 
    This property may be overridden in a subclass, set after instantiation, or
100
 
    passed as a config attribute when instantiating a `Y.Controller`-based
101
 
    class.
102
 
 
103
 
    @property root
104
 
    @type String
105
 
    @default `''`
106
 
    **/
107
 
    root: '',
108
 
 
109
 
    /**
110
 
    Array of route objects specifying routes to be created at instantiation
111
 
    time.
112
 
 
113
 
    Each item in the array must be an object with the following properties:
114
 
 
115
 
      * `path`: String or regex representing the path to match. See the docs for
116
 
        the `route()` method for more details.
117
 
      * `callback`: Function or a string representing the name of a function on
118
 
        this controller instance that should be called when the route is
119
 
        triggered. See the docs for the `route()` method for more details.
120
 
 
121
 
    This property may be overridden in a subclass or passed as a config
122
 
    attribute when instantiating a `Y.Controller`-based class, but setting it
123
 
    after instantiation will have no effect (use the `route()` method instead).
124
 
 
125
 
    If routes are passed at instantiation time, they will override any routes
126
 
    set on the prototype.
127
 
 
128
 
    @property routes
129
 
    @type Object[]
130
 
    @default `[]`
131
 
    **/
132
 
    routes: [],
133
 
 
134
 
    // -- Protected Properties -------------------------------------------------
135
 
 
136
 
    /**
137
 
    Whether or not `_dispatch()` has been called since this controller was
138
 
    instantiated.
139
 
 
140
 
    @property _dispatched
141
 
    @type Boolean
142
 
    @default undefined
143
 
    @protected
144
 
    **/
145
 
 
146
 
    /**
147
 
    Whether or not we're currently in the process of dispatching to routes.
148
 
 
149
 
    @property _dispatching
150
 
    @type Boolean
151
 
    @default undefined
152
 
    @protected
153
 
    **/
154
 
 
155
 
    /**
156
 
    Whether or not the `ready` event has fired yet.
157
 
 
158
 
    @property _ready
159
 
    @type Boolean
160
 
    @default undefined
161
 
    @protected
162
 
    **/
163
 
 
164
 
    /**
165
 
    Regex used to match parameter placeholders in route paths.
166
 
 
167
 
    Subpattern captures:
168
 
 
169
 
      1. Parameter prefix character. Either a `:` for subpath parameters that
170
 
         should only match a single level of a path, or `*` for splat parameters
171
 
         that should match any number of path levels.
172
 
      2. Parameter name.
173
 
 
174
 
    @property _regexPathParam
175
 
    @type RegExp
176
 
    @protected
177
 
    **/
178
 
    _regexPathParam: /([:*])([\w-]+)/g,
179
 
 
180
 
    /**
181
 
    Regex that matches and captures the query portion of a URL, minus the
182
 
    preceding `?` character, and discarding the hash portion of the URL if any.
183
 
 
184
 
    @property _regexUrlQuery
185
 
    @type RegExp
186
 
    @protected
187
 
    **/
188
 
    _regexUrlQuery: /\?([^#]*).*$/,
189
 
 
190
 
    /**
191
 
    Regex that matches everything before the path portion of an HTTP or HTTPS
192
 
    URL. This will be used to strip this part of the URL from a string when we
193
 
    only want the path.
194
 
 
195
 
    @property _regexUrlStrip
196
 
    @type RegExp
197
 
    @protected
198
 
    **/
199
 
    _regexUrlStrip: /^https?:\/\/[^\/]*/i,
200
 
 
201
 
    // -- Lifecycle Methods ----------------------------------------------------
202
 
    initializer: function (config) {
203
 
        var self = this;
204
 
 
205
 
        // Set config properties.
206
 
        config || (config = {});
207
 
 
208
 
        config.routes && (self.routes = config.routes);
209
 
        Lang.isValue(config.root) && (self.root = config.root);
210
 
 
211
 
        // Create routes.
212
 
        self._routes = [];
213
 
 
214
 
        YArray.each(self.routes, function (route) {
215
 
            self.route(route.path, route.callback);
216
 
        });
217
 
 
218
 
        // Set up a history instance or hashchange listener.
219
 
        if (html5) {
220
 
            self._history = new Y.HistoryHTML5({force: true});
221
 
            self._history.after('change', self._afterHistoryChange, self);
222
 
        } else {
223
 
            Y.on('hashchange', self._afterHistoryChange, win, self);
224
 
        }
225
 
 
226
 
        // Fire a 'ready' event once we're ready to route. We wait first for all
227
 
        // subclass initializers to finish, then for window.onload, and then an
228
 
        // additional 20ms to allow the browser to fire a useless initial
229
 
        // `popstate` event if it wants to (and Chrome always wants to).
230
 
        self.publish(EVT_READY, {
231
 
            defaultFn  : self._defReadyFn,
232
 
            fireOnce   : true,
233
 
            preventable: false
234
 
        });
235
 
 
236
 
        self.once('initializedChange', function () {
237
 
            Y.once('load', function () {
238
 
                setTimeout(function () {
239
 
                    self.fire(EVT_READY, {dispatched: !!self._dispatched});
240
 
                }, 20);
241
 
            });
242
 
        });
243
 
    },
244
 
 
245
 
    destructor: function () {
246
 
        if (html5) {
247
 
            this._history.detachAll();
248
 
        } else {
249
 
            Y.detach('hashchange', this._afterHistoryChange, win);
250
 
        }
251
 
    },
252
 
 
253
 
    // -- Public Methods -------------------------------------------------------
254
 
 
255
 
    /**
256
 
    Dispatches to the first route handler that matches the current URL, if any.
257
 
 
258
 
    If `dispatch()` is called before the `ready` event has fired, it will
259
 
    automatically wait for the `ready` event before dispatching. Otherwise it
260
 
    will dispatch immediately.
261
 
 
262
 
    @method dispatch
263
 
    @chainable
264
 
    **/
265
 
    dispatch: function () {
266
 
        this.once(EVT_READY, function () {
267
 
            this._ready = true;
268
 
 
269
 
            if (html5 && this.upgrade()) {
270
 
                return;
271
 
            } else {
272
 
                this._dispatch(this._getPath());
273
 
            }
274
 
        });
275
 
 
276
 
        return this;
277
 
    },
278
 
 
279
 
    /**
280
 
    Gets the current route path, relative to the `root` (if any).
281
 
 
282
 
    @method getPath
283
 
    @return {String} Current route path.
284
 
    **/
285
 
    getPath: function () {
286
 
        return this._getPath();
287
 
    },
288
 
 
289
 
    /**
290
 
    Returns `true` if this controller has at least one route that matches the
291
 
    specified URL path, `false` otherwise.
292
 
 
293
 
    @method hasRoute
294
 
    @param {String} path URL path to match.
295
 
    @return {Boolean} `true` if there's at least one matching route, `false`
296
 
      otherwise.
297
 
    **/
298
 
    hasRoute: function (path) {
299
 
        return !!this.match(path).length;
300
 
    },
301
 
 
302
 
    /**
303
 
    Returns an array of route objects that match the specified URL path.
304
 
 
305
 
    This method is called internally to determine which routes match the current
306
 
    path whenever the URL changes. You may override it if you want to customize
307
 
    the route matching logic, although this usually shouldn't be necessary.
308
 
 
309
 
    Each returned route object has the following properties:
310
 
 
311
 
      * `callback`: A function or a string representing the name of a function
312
 
        this controller that should be executed when the route is triggered.
313
 
      * `keys`: An array of strings representing the named parameters defined in
314
 
        the route's path specification, if any.
315
 
      * `path`: The route's path specification, which may be either a string or
316
 
        a regex.
317
 
      * `regex`: A regular expression version of the route's path specification.
318
 
        This regex is used to determine whether the route matches a given path.
319
 
 
320
 
    @example
321
 
        controller.route('/foo', function () {});
322
 
        controller.match('/foo');
323
 
        // => [{callback: ..., keys: [], path: '/foo', regex: ...}]
324
 
 
325
 
    @method match
326
 
    @param {String} path URL path to match.
327
 
    @return {Object[]} Array of route objects that match the specified path.
328
 
    **/
329
 
    match: function (path) {
330
 
        return YArray.filter(this._routes, function (route) {
331
 
            return path.search(route.regex) > -1;
332
 
        });
333
 
    },
334
 
 
335
 
    /**
336
 
    Removes the `root` URL from the from of _path_ (if it's there) and returns
337
 
    the result. The returned path will always have a leading `/`.
338
 
 
339
 
    @method removeRoot
340
 
    @param {String} path URL path.
341
 
    @return {String} Rootless path.
342
 
    **/
343
 
    removeRoot: function (path) {
344
 
        var root = this.root;
345
 
 
346
 
        // Strip out the non-path part of the URL, if any (e.g.
347
 
        // "http://foo.com"), so that we're left with just the path.
348
 
        path = path.replace(this._regexUrlStrip, '');
349
 
 
350
 
        if (root && path.indexOf(root) === 0) {
351
 
            path = path.substring(root.length);
352
 
        }
353
 
 
354
 
        return path.charAt(0) === '/' ? path : '/' + path;
355
 
    },
356
 
 
357
 
    /**
358
 
    Replaces the current browser history entry with a new one, and dispatches to
359
 
    the first matching route handler, if any.
360
 
 
361
 
    Behind the scenes, this method uses HTML5 `pushState()` in browsers that
362
 
    support it (or the location hash in older browsers and IE) to change the
363
 
    URL.
364
 
 
365
 
    The specified URL must share the same origin (i.e., protocol, host, and
366
 
    port) as the current page, or an error will occur.
367
 
 
368
 
    @example
369
 
        // Starting URL: http://example.com/
370
 
 
371
 
        controller.replace('/path/');
372
 
        // New URL: http://example.com/path/
373
 
 
374
 
        controller.replace('/path?foo=bar');
375
 
        // New URL: http://example.com/path?foo=bar
376
 
 
377
 
        controller.replace('/');
378
 
        // New URL: http://example.com/
379
 
 
380
 
    @method replace
381
 
    @param {String} [url] URL to set. Should be a relative URL. If this
382
 
      controller's `root` property is set, this URL must be relative to the
383
 
      root URL. If no URL is specified, the page's current URL will be used.
384
 
    @chainable
385
 
    @see save()
386
 
    **/
387
 
    replace: function (url) {
388
 
        return this._queue(url, true);
389
 
    },
390
 
 
391
 
    /**
392
 
    Adds a route handler for the specified URL _path_.
393
 
 
394
 
    The _path_ parameter may be either a string or a regular expression. If it's
395
 
    a string, it may contain named parameters: `:param` will match any single
396
 
    part of a URL path (not including `/` characters), and `*param` will match
397
 
    any number of parts of a URL path (including `/` characters). These named
398
 
    parameters will be made available as keys on the `req.params` object that's
399
 
    passed to route handlers.
400
 
 
401
 
    If the _path_ parameter is a regex, all pattern matches will be made
402
 
    available as numbered keys on `req.params`, starting with `0` for the full
403
 
    match, then `1` for the first subpattern match, and so on.
404
 
 
405
 
    Here's a set of sample routes along with URL paths that they match:
406
 
 
407
 
      * Route: `/photos/:tag/:page`
408
 
        * URL: `/photos/kittens/1`, params: `{tag: 'kittens', page: '1'}`
409
 
        * URL: `/photos/puppies/2`, params: `{tag: 'puppies', page: '2'}`
410
 
 
411
 
      * Route: `/file/*path`
412
 
        * URL: `/file/foo/bar/baz.txt`, params: `{path: 'foo/bar/baz.txt'}`
413
 
        * URL: `/file/foo`, params: `{path: 'foo'}`
414
 
 
415
 
    If multiple route handlers match a given URL, they will be executed in the
416
 
    order they were added. The first route that was added will be the first to
417
 
    be executed.
418
 
 
419
 
    @example
420
 
        controller.route('/photos/:tag/:page', function (req, next) {
421
 
        });
422
 
 
423
 
    @method route
424
 
    @param {String|RegExp} path Path to match. May be a string or a regular
425
 
      expression.
426
 
    @param {Function|String} callback Callback function to call whenever this
427
 
        route is triggered. If specified as a string, the named function will be
428
 
        called on this controller instance.
429
 
      @param {Object} callback.req Request object containing information about
430
 
          the request. It contains the following properties.
431
 
        @param {Array|Object} callback.req.params Captured parameters matched by
432
 
          the route path specification. If a string path was used and contained
433
 
          named parameters, then this will be a key/value hash mapping parameter
434
 
          names to their matched values. If a regex path was used, this will be
435
 
          an array of subpattern matches starting at index 0 for the full match,
436
 
          then 1 for the first subpattern match, and so on.
437
 
        @param {String} callback.req.path The current URL path.
438
 
        @param {Object} callback.req.query Query hash representing the URL query
439
 
          string, if any. Parameter names are keys, and are mapped to parameter
440
 
          values.
441
 
      @param {Function} callback.next Callback to pass control to the next
442
 
        matching route. If you don't call this function, then no further route
443
 
        handlers will be executed, even if there are more that match. If you do
444
 
        call this function, then the next matching route handler (if any) will
445
 
        be called, and will receive the same `req` object that was passed to
446
 
        this route (so you can use the request object to pass data along to
447
 
        subsequent routes).
448
 
    @chainable
449
 
    **/
450
 
    route: function (path, callback) {
451
 
        var keys = [];
452
 
 
453
 
        this._routes.push({
454
 
            callback: callback,
455
 
            keys    : keys,
456
 
            path    : path,
457
 
            regex   : this._getRegex(path, keys)
458
 
        });
459
 
 
460
 
        return this;
461
 
    },
462
 
 
463
 
    /**
464
 
    Saves a new browser history entry and dispatches to the first matching route
465
 
    handler, if any.
466
 
 
467
 
    Behind the scenes, this method uses HTML5 `pushState()` in browsers that
468
 
    support it (or the location hash in older browsers and IE) to change the
469
 
    URL and create a history entry.
470
 
 
471
 
    The specified URL must share the same origin (i.e., protocol, host, and
472
 
    port) as the current page, or an error will occur.
473
 
 
474
 
    @example
475
 
        // Starting URL: http://example.com/
476
 
 
477
 
        controller.save('/path/');
478
 
        // New URL: http://example.com/path/
479
 
 
480
 
        controller.save('/path?foo=bar');
481
 
        // New URL: http://example.com/path?foo=bar
482
 
 
483
 
        controller.save('/');
484
 
        // New URL: http://example.com/
485
 
 
486
 
    @method save
487
 
    @param {String} [url] URL to set. Should be a relative URL. If this
488
 
      controller's `root` property is set, this URL must be relative to the
489
 
      root URL. If no URL is specified, the page's current URL will be used.
490
 
    @chainable
491
 
    @see replace()
492
 
    **/
493
 
    save: function (url) {
494
 
        return this._queue(url);
495
 
    },
496
 
 
497
 
    /**
498
 
    Upgrades a hash-based URL to an HTML5 URL if necessary. In non-HTML5
499
 
    browsers, this method is a noop.
500
 
 
501
 
    @method upgrade
502
 
    @return {Boolean} `true` if the URL was upgraded, `false` otherwise.
503
 
    **/
504
 
    upgrade: html5 ? function () {
505
 
        var hash = this._getHashPath();
506
 
 
507
 
        if (hash && hash.charAt(0) === '/') {
508
 
            // This is an HTML5 browser and we have a hash-based path in the
509
 
            // URL, so we need to upgrade the URL to a non-hash URL. This
510
 
            // will trigger a `history:change` event, which will in turn
511
 
            // trigger a dispatch.
512
 
            this.once(EVT_READY, function () {
513
 
                this.replace(hash);
514
 
            });
515
 
 
516
 
            return true;
517
 
        }
518
 
 
519
 
        return false;
520
 
    } : function () { return false; },
521
 
 
522
 
    // -- Protected Methods ----------------------------------------------------
523
 
 
524
 
    /**
525
 
    Wrapper around `decodeURIComponent` that also converts `+` chars into
526
 
    spaces.
527
 
 
528
 
    @method _decode
529
 
    @param {String} string String to decode.
530
 
    @return {String} Decoded string.
531
 
    @protected
532
 
    **/
533
 
    _decode: function (string) {
534
 
        return decodeURIComponent(string.replace(/\+/g, ' '));
535
 
    },
536
 
 
537
 
    /**
538
 
    Shifts the topmost `_save()` call off the queue and executes it. Does
539
 
    nothing if the queue is empty.
540
 
 
541
 
    @method _dequeue
542
 
    @chainable
543
 
    @see _queue
544
 
    @protected
545
 
    **/
546
 
    _dequeue: function () {
547
 
        var self = this,
548
 
            fn;
549
 
 
550
 
        // If window.onload hasn't yet fired, wait until it has before
551
 
        // dequeueing. This will ensure that we don't call pushState() before an
552
 
        // initial popstate event has fired.
553
 
        if (!YUI.Env.windowLoaded) {
554
 
            Y.once('load', function () {
555
 
                self._dequeue();
556
 
            });
557
 
 
558
 
            return this;
559
 
        }
560
 
 
561
 
        fn = saveQueue.shift();
562
 
        return fn ? fn() : this;
563
 
    },
564
 
 
565
 
    /**
566
 
    Dispatches to the first route handler that matches the specified _path_.
567
 
 
568
 
    If called before the `ready` event has fired, the dispatch will be aborted.
569
 
    This ensures normalized behavior between Chrome (which fires a `popstate`
570
 
    event on every pageview) and other browsers (which do not).
571
 
 
572
 
    @method _dispatch
573
 
    @param {String} path URL path.
574
 
    @chainable
575
 
    @protected
576
 
    **/
577
 
    _dispatch: function (path) {
578
 
        var self   = this,
579
 
            routes = self.match(path),
580
 
            req;
581
 
 
582
 
        self._dispatching = self._dispatched = true;
583
 
 
584
 
        if (!routes || !routes.length) {
585
 
            self._dispatching = false;
586
 
            return self;
587
 
        }
588
 
 
589
 
        req = self._getRequest(path);
590
 
 
591
 
        req.next = function (err) {
592
 
            var callback, matches, route;
593
 
 
594
 
            if (err) {
595
 
                Y.error(err);
596
 
            } else if ((route = routes.shift())) {
597
 
                matches  = route.regex.exec(path);
598
 
                callback = typeof route.callback === 'string' ?
599
 
                        self[route.callback] : route.callback;
600
 
 
601
 
                // Use named keys for parameter names if the route path contains
602
 
                // named keys. Otherwise, use numerical match indices.
603
 
                if (matches.length === route.keys.length + 1) {
604
 
                    req.params = YArray.hash(route.keys, matches.slice(1));
605
 
                } else {
606
 
                    req.params = matches.concat();
607
 
                }
608
 
 
609
 
                callback.call(self, req, req.next);
610
 
            }
611
 
        };
612
 
 
613
 
        req.next();
614
 
 
615
 
        self._dispatching = false;
616
 
        return self._dequeue();
617
 
    },
618
 
 
619
 
    /**
620
 
    Gets the current path from the location hash, or an empty string if the
621
 
    hash is empty.
622
 
 
623
 
    @method _getHashPath
624
 
    @return {String} Current hash path, or an empty string if the hash is empty.
625
 
    @protected
626
 
    **/
627
 
    _getHashPath: function () {
628
 
        return HistoryHash.getHash().replace(this._regexUrlQuery, '');
629
 
    },
630
 
 
631
 
    /**
632
 
    Gets the current route path.
633
 
 
634
 
    @method _getPath
635
 
    @return {String} Current route path.
636
 
    @protected
637
 
    **/
638
 
    _getPath: html5 ? function () {
639
 
        return this.removeRoot(location.pathname);
640
 
    } : function () {
641
 
        return this._getHashPath() || this.removeRoot(location.pathname);
642
 
    },
643
 
 
644
 
    /**
645
 
    Gets the current route query string.
646
 
 
647
 
    @method _getQuery
648
 
    @return {String} Current route query string.
649
 
    @protected
650
 
    **/
651
 
    _getQuery: html5 ? function () {
652
 
        return location.search.substring(1);
653
 
    } : function () {
654
 
        var hash    = HistoryHash.getHash(),
655
 
            matches = hash.match(this._regexUrlQuery);
656
 
 
657
 
        return hash && matches ? matches[1] : location.search.substring(1);
658
 
    },
659
 
 
660
 
    /**
661
 
    Creates a regular expression from the given route specification. If _path_
662
 
    is already a regex, it will be returned unmodified.
663
 
 
664
 
    @method _getRegex
665
 
    @param {String|RegExp} path Route path specification.
666
 
    @param {Array} keys Array reference to which route parameter names will be
667
 
      added.
668
 
    @return {RegExp} Route regex.
669
 
    @protected
670
 
    **/
671
 
    _getRegex: function (path, keys) {
672
 
        if (path instanceof RegExp) {
673
 
            return path;
674
 
        }
675
 
 
676
 
        path = path.replace(this._regexPathParam, function (match, operator, key) {
677
 
            keys.push(key);
678
 
            return operator === '*' ? '(.*?)' : '([^/]*)';
679
 
        });
680
 
 
681
 
        return new RegExp('^' + path + '$');
682
 
    },
683
 
 
684
 
    /**
685
 
    Gets a request object that can be passed to a route handler.
686
 
 
687
 
    @method _getRequest
688
 
    @param {String} path Current path being dispatched.
689
 
    @return {Object} Request object.
690
 
    @protected
691
 
    **/
692
 
    _getRequest: function (path) {
693
 
        return {
694
 
            path : path,
695
 
            query: this._parseQuery(this._getQuery())
696
 
        };
697
 
    },
698
 
 
699
 
    /**
700
 
    Joins the `root` URL to the specified _url_, normalizing leading/trailing
701
 
    `/` characters.
702
 
 
703
 
    @example
704
 
        controller.root = '/foo'
705
 
        controller._joinURL('bar');  // => '/foo/bar'
706
 
        controller._joinURL('/bar'); // => '/foo/bar'
707
 
 
708
 
        controller.root = '/foo/'
709
 
        controller._joinURL('bar');  // => '/foo/bar'
710
 
        controller._joinURL('/bar'); // => '/foo/bar'
711
 
 
712
 
    @method _joinURL
713
 
    @param {String} url URL to append to the `root` URL.
714
 
    @return {String} Joined URL.
715
 
    @protected
716
 
    **/
717
 
    _joinURL: function (url) {
718
 
        var root = this.root;
719
 
 
720
 
        if (url.charAt(0) === '/') {
721
 
            url = url.substring(1);
722
 
        }
723
 
 
724
 
        return root && root.charAt(root.length - 1) === '/' ?
725
 
                root + url :
726
 
                root + '/' + url;
727
 
    },
728
 
 
729
 
    /**
730
 
    Parses a URL query string into a key/value hash. If `Y.QueryString.parse` is
731
 
    available, this method will be an alias to that.
732
 
 
733
 
    @method _parseQuery
734
 
    @param {String} query Query string to parse.
735
 
    @return {Object} Hash of key/value pairs for query parameters.
736
 
    @protected
737
 
    **/
738
 
    _parseQuery: QS && QS.parse ? QS.parse : function (query) {
739
 
        var decode = this._decode,
740
 
            params = query.split('&'),
741
 
            i      = 0,
742
 
            len    = params.length,
743
 
            result = {},
744
 
            param;
745
 
 
746
 
        for (; i < len; ++i) {
747
 
            param = params[i].split('=');
748
 
 
749
 
            if (param[0]) {
750
 
                result[decode(param[0])] = decode(param[1] || '');
751
 
            }
752
 
        }
753
 
 
754
 
        return result;
755
 
    },
756
 
 
757
 
    /**
758
 
    Queues up a `_save()` call to run after all previously-queued calls have
759
 
    finished.
760
 
 
761
 
    This is necessary because if we make multiple `_save()` calls before the
762
 
    first call gets dispatched, then both calls will dispatch to the last call's
763
 
    URL.
764
 
 
765
 
    All arguments passed to `_queue()` will be passed on to `_save()` when the
766
 
    queued function is executed.
767
 
 
768
 
    @method _queue
769
 
    @chainable
770
 
    @see _dequeue
771
 
    @protected
772
 
    **/
773
 
    _queue: function () {
774
 
        var args = arguments,
775
 
            self = this;
776
 
 
777
 
        saveQueue.push(function () {
778
 
            if (html5) {
779
 
                if (Y.UA.ios && Y.UA.ios < 5) {
780
 
                    // iOS <5 has buggy HTML5 history support, and needs to be
781
 
                    // synchronous.
782
 
                    self._save.apply(self, args);
783
 
                } else {
784
 
                    // Wrapped in a timeout to ensure that _save() calls are
785
 
                    // always processed asynchronously. This ensures consistency
786
 
                    // between HTML5- and hash-based history.
787
 
                    setTimeout(function () {
788
 
                        self._save.apply(self, args);
789
 
                    }, 1);
790
 
                }
791
 
            } else {
792
 
                self._dispatching = true; // otherwise we'll dequeue too quickly
793
 
                self._save.apply(self, args);
794
 
            }
795
 
 
796
 
            return self;
797
 
        });
798
 
 
799
 
        return !this._dispatching ? this._dequeue() : this;
800
 
    },
801
 
 
802
 
    /**
803
 
    Saves a history entry using either `pushState()` or the location hash.
804
 
 
805
 
    @method _save
806
 
    @param {String} [url] URL for the history entry.
807
 
    @param {Boolean} [replace=false] If `true`, the current history entry will
808
 
      be replaced instead of a new one being added.
809
 
    @chainable
810
 
    @protected
811
 
    **/
812
 
    _save: html5 ? function (url, replace) {
813
 
        // Force _ready to true to ensure that the history change is handled
814
 
        // even if _save is called before the `ready` event fires.
815
 
        this._ready = true;
816
 
 
817
 
        this._history[replace ? 'replace' : 'add'](null, {
818
 
            url: typeof url === 'string' ? this._joinURL(url) : url
819
 
        });
820
 
 
821
 
        return this;
822
 
    } : function (url, replace) {
823
 
        this._ready = true;
824
 
 
825
 
        if (typeof url === 'string' && url.charAt(0) !== '/') {
826
 
            url = '/' + url;
827
 
        }
828
 
 
829
 
        HistoryHash[replace ? 'replaceHash' : 'setHash'](url);
830
 
        return this;
831
 
    },
832
 
 
833
 
    // -- Protected Event Handlers ---------------------------------------------
834
 
 
835
 
    /**
836
 
    Handles `history:change` and `hashchange` events.
837
 
 
838
 
    @method _afterHistoryChange
839
 
    @param {EventFacade} e
840
 
    @protected
841
 
    **/
842
 
    _afterHistoryChange: function (e) {
843
 
        var self = this;
844
 
 
845
 
        if (self._ready) {
846
 
            self._dispatch(self._getPath());
847
 
        }
848
 
    },
849
 
 
850
 
    // -- Default Event Handlers -----------------------------------------------
851
 
 
852
 
    /**
853
 
    Default handler for the `ready` event.
854
 
 
855
 
    @method _defReadyFn
856
 
    @param {EventFacade} e
857
 
    @protected
858
 
    **/
859
 
    _defReadyFn: function (e) {
860
 
        this._ready = true;
861
 
    }
862
 
}, {
863
 
    NAME: 'controller'
864
 
});
865
 
 
866
 
 
867
 
}, '3.4.1' ,{optional:['querystring-parse'], requires:['array-extras', 'base-build', 'history']});