~ubuntu-branches/ubuntu/precise/maas/precise-updates

« back to all changes in this revision

Viewing changes to debian/extras/jslibs/yui/app-base/app-base-debug.js

Tags: 1.2+bzr1373+dfsg-0ubuntu1~12.04.4
* SECURITY UPDATE: failure to authenticate downloaded content (LP: #1039513)
  - debian/patches/CVE-2013-1058.patch: Authenticate downloaded files with
    GnuPG and MD5SUM files. Thanks to Julian Edwards.
  - CVE-2013-1058
* SECURITY UPDATE: configuration options may be loaded from current working
  directory (LP: #1158425)
  - debian/patches/CVE-2013-1057-1-2.patch: Do not load configuration
    options from the current working directory. Thanks to Julian Edwards.
  - CVE-2013-1057

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
YUI 3.5.1 (build 22)
 
3
Copyright 2012 Yahoo! Inc. All rights reserved.
 
4
Licensed under the BSD License.
 
5
http://yuilibrary.com/license/
 
6
*/
 
7
YUI.add('app-base', function(Y) {
 
8
 
 
9
/**
 
10
The App Framework provides simple MVC-like building blocks (models, model lists,
 
11
views, and URL-based routing) for writing single-page JavaScript applications.
 
12
 
 
13
@main app
 
14
@module app
 
15
@since 3.4.0
 
16
**/
 
17
 
 
18
/**
 
19
Provides a top-level application component which manages navigation and views.
 
20
 
 
21
@module app
 
22
@submodule app-base
 
23
@since 3.5.0
 
24
**/
 
25
 
 
26
// TODO: Better handling of lifecycle for registered views:
 
27
//
 
28
//   * [!] Just redo basically everything with view management so there are no
 
29
//     pre-`activeViewChange` side effects and handle the rest of these things:
 
30
//
 
31
//   * Seems like any view created via `createView` should listen for the view's
 
32
//     `destroy` event and use that to remove it from the `_viewsInfoMap`. I
 
33
//     should look at what ModelList does for Models as a reference.
 
34
//
 
35
//   * Should we have a companion `destroyView()` method? Maybe this wouldn't be
 
36
//     needed if we have a `getView(name, create)` method, and already doing the
 
37
//     above? We could do `app.getView('foo').destroy()` and it would be removed
 
38
//     from the `_viewsInfoMap` as well.
 
39
//
 
40
//   * Should we wait to call a view's `render()` method inside of the
 
41
//     `_attachView()` method?
 
42
//
 
43
//   * Should named views support a collection of instances instead of just one?
 
44
//
 
45
 
 
46
var Lang    = Y.Lang,
 
47
    YObject = Y.Object,
 
48
 
 
49
    PjaxBase = Y.PjaxBase,
 
50
    Router   = Y.Router,
 
51
    View     = Y.View,
 
52
 
 
53
    getClassName = Y.ClassNameManager.getClassName,
 
54
 
 
55
    win = Y.config.win,
 
56
 
 
57
    App;
 
58
 
 
59
/**
 
60
Provides a top-level application component which manages navigation and views.
 
61
 
 
62
This gives you a foundation and structure on which to build your application; it
 
63
combines robust URL navigation with powerful routing and flexible view
 
64
management.
 
65
 
 
66
@class App.Base
 
67
@param {Object} [config] The following are configuration properties that can be
 
68
    specified _in addition_ to default attribute values and the non-attribute
 
69
    properties provided by `Y.Base`:
 
70
  @param {Object} [config.views] Hash of view-name to metadata used to
 
71
    declaratively describe an application's views and their relationship with
 
72
    the app and other views. The views specified here will override any defaults
 
73
    provided by the `views` object on the `prototype`.
 
74
@constructor
 
75
@extends Base
 
76
@uses View
 
77
@uses Router
 
78
@uses PjaxBase
 
79
@since 3.5.0
 
80
**/
 
81
App = Y.Base.create('app', Y.Base, [View, Router, PjaxBase], {
 
82
    // -- Public Properties ----------------------------------------------------
 
83
 
 
84
    /**
 
85
    Hash of view-name to metadata used to declaratively describe an
 
86
    application's views and their relationship with the app and its other views.
 
87
 
 
88
    The view metadata is composed of Objects keyed to a view-name that can have
 
89
    any or all of the following properties:
 
90
 
 
91
      * `type`: Function or a string representing the view constructor to use to
 
92
        create view instances. If a string is used, the constructor function is
 
93
        assumed to be on the `Y` object; e.g. `"SomeView"` -> `Y.SomeView`.
 
94
 
 
95
      * `preserve`: Boolean for whether the view instance should be retained. By
 
96
        default, the view instance will be destroyed when it is no longer the
 
97
        `activeView`. If `true` the view instance will simply be `removed()`
 
98
        from the DOM when it is no longer active. This is useful when the view
 
99
        is frequently used and may be expensive to re-create.
 
100
 
 
101
      * `parent`: String to another named view in this hash that represents the
 
102
        parent view within the application's view hierarchy; e.g. a `"photo"`
 
103
        view could have `"album"` has its `parent` view. This parent/child
 
104
        relationship is a useful cue for things like transitions.
 
105
 
 
106
      * `instance`: Used internally to manage the current instance of this named
 
107
        view. This can be used if your view instance is created up-front, or if
 
108
        you would rather manage the View lifecycle, but you probably should just
 
109
        let this be handled for you.
 
110
 
 
111
    If `views` are specified at instantiation time, the metadata in the `views`
 
112
    Object here will be used as defaults when creating the instance's `views`.
 
113
 
 
114
    Every `Y.App` instance gets its own copy of a `views` object so this Object
 
115
    on the prototype will not be polluted.
 
116
 
 
117
    @example
 
118
        // Imagine that `Y.UsersView` and `Y.UserView` have been defined.
 
119
        var app = new Y.App({
 
120
            views: {
 
121
                users: {
 
122
                    type    : Y.UsersView,
 
123
                    preserve: true
 
124
                },
 
125
 
 
126
                user: {
 
127
                    type  : Y.UserView,
 
128
                    parent: 'users'
 
129
                }
 
130
            }
 
131
        });
 
132
 
 
133
    @property views
 
134
    @type Object
 
135
    @default {}
 
136
    @since 3.5.0
 
137
    **/
 
138
    views: {},
 
139
 
 
140
    // -- Protected Properties -------------------------------------------------
 
141
 
 
142
    /**
 
143
    Map of view instance id (via `Y.stamp()`) to view-info object in `views`.
 
144
 
 
145
    This mapping is used to tie a specific view instance back to its metadata by
 
146
    adding a reference to the the related view info on the `views` object.
 
147
 
 
148
    @property _viewInfoMap
 
149
    @type Object
 
150
    @default {}
 
151
    @protected
 
152
    @since 3.5.0
 
153
    **/
 
154
 
 
155
    // -- Lifecycle Methods ----------------------------------------------------
 
156
    initializer: function (config) {
 
157
        config || (config = {});
 
158
 
 
159
        var views = {};
 
160
 
 
161
        // Merges-in specified view metadata into local `views` object.
 
162
        function mergeViewConfig(view, name) {
 
163
            views[name] = Y.merge(views[name], view);
 
164
        }
 
165
 
 
166
        // First, each view in the `views` prototype object gets its metadata
 
167
        // merged-in, providing the defaults.
 
168
        YObject.each(this.views, mergeViewConfig);
 
169
 
 
170
        // Then, each view in the specified `config.views` object gets its
 
171
        // metadata merged-in.
 
172
        YObject.each(config.views, mergeViewConfig);
 
173
 
 
174
        // The resulting hodgepodge of metadata is then stored as the instance's
 
175
        // `views` object, and no one's objects were harmed in the making.
 
176
        this.views        = views;
 
177
        this._viewInfoMap = {};
 
178
 
 
179
        // Using `bind()` to aid extensibility.
 
180
        this.after('activeViewChange', Y.bind('_afterActiveViewChange', this));
 
181
 
 
182
        // PjaxBase will bind click events when `html5` is `true`, so this just
 
183
        // forces the binding when `serverRouting` and `html5` are both falsy.
 
184
        if (!this.get('serverRouting')) {
 
185
            this._pjaxBindUI();
 
186
        }
 
187
    },
 
188
 
 
189
    // TODO: `destructor` to destroy the `activeView`?
 
190
 
 
191
    // -- Public Methods -------------------------------------------------------
 
192
 
 
193
    /**
 
194
    Creates and returns a new view instance using the provided `name` to look up
 
195
    the view info metadata defined in the `views` object. The passed-in `config`
 
196
    object is passed to the view constructor function.
 
197
 
 
198
    This function also maps a view instance back to its view info metadata.
 
199
 
 
200
    @method createView
 
201
    @param {String} name The name of a view defined on the `views` object.
 
202
    @param {Object} [config] The configuration object passed to the view
 
203
      constructor function when creating the new view instance.
 
204
    @return {View} The new view instance.
 
205
    @since 3.5.0
 
206
    **/
 
207
    createView: function (name, config) {
 
208
        var viewInfo = this.getViewInfo(name),
 
209
            type     = (viewInfo && viewInfo.type) || View,
 
210
            ViewConstructor, view;
 
211
 
 
212
        // Looks for a namespaced constructor function on `Y`.
 
213
        ViewConstructor = Lang.isString(type) ?
 
214
                YObject.getValue(Y, type.split('.')) : type;
 
215
 
 
216
        // Create the view instance and map it with its metadata.
 
217
        view = new ViewConstructor(config);
 
218
        this._viewInfoMap[Y.stamp(view, true)] = viewInfo;
 
219
 
 
220
        return view;
 
221
    },
 
222
 
 
223
    /**
 
224
    Returns the metadata associated with a view instance or view name defined on
 
225
    the `views` object.
 
226
 
 
227
    @method getViewInfo
 
228
    @param {View|String} view View instance, or name of a view defined on the
 
229
      `views` object.
 
230
    @return {Object} The metadata for the view, or `undefined` if the view is
 
231
      not registered.
 
232
    @since 3.5.0
 
233
    **/
 
234
    getViewInfo: function (view) {
 
235
        if (Lang.isString(view)) {
 
236
            return this.views[view];
 
237
        }
 
238
 
 
239
        return view && this._viewInfoMap[Y.stamp(view, true)];
 
240
    },
 
241
 
 
242
    /**
 
243
    Navigates to the specified URL if there is a route handler that matches. In
 
244
    browsers capable of using HTML5 history or when `serverRouting` is falsy,
 
245
    the navigation will be enhanced by firing the `navigate` event and having
 
246
    the app handle the "request". When `serverRouting` is `true`, non-HTML5
 
247
    browsers will navigate to the new URL via a full page reload.
 
248
 
 
249
    When there is a route handler for the specified URL and it is being
 
250
    navigated to, this method will return `true`, otherwise it will return
 
251
    `false`.
 
252
 
 
253
    **Note:** The specified URL _must_ be of the same origin as the current URL,
 
254
    otherwise an error will be logged and navigation will not occur. This is
 
255
    intended as both a security constraint and a purposely imposed limitation as
 
256
    it does not make sense to tell the app to navigate to a URL on a
 
257
    different scheme, host, or port.
 
258
 
 
259
    @method navigate
 
260
    @param {String} url The URL to navigate to. This must be of the same origin
 
261
      as the current URL.
 
262
    @param {Object} [options] Additional options to configure the navigation.
 
263
      These are mixed into the `navigate` event facade.
 
264
        @param {Boolean} [options.replace] Whether or not the current history
 
265
          entry will be replaced, or a new entry will be created. Will default
 
266
          to `true` if the specified `url` is the same as the current URL.
 
267
        @param {Boolean} [options.force] Whether the enhanced navigation
 
268
          should occur even in browsers without HTML5 history. Will default to
 
269
          `true` when `serverRouting` is falsy.
 
270
    @see PjaxBase.navigate()
 
271
    **/
 
272
    // Does not override `navigate()` but does use extra `options`.
 
273
 
 
274
    /**
 
275
    Renders this application by appending the `viewContainer` node to the
 
276
    `container` node if it isn't already a child of the container, and the
 
277
    `activeView` will be appended the view container, if it isn't already.
 
278
 
 
279
    You should call this method at least once, usually after the initialization
 
280
    of your app instance so the proper DOM structure is setup and optionally
 
281
    append the container to the DOM if it's not there already.
 
282
 
 
283
    You may override this method to customize the app's rendering, but you
 
284
    should expect that the `viewContainer`'s contents will be modified by the
 
285
    app for the purpose of rendering the `activeView` when it changes.
 
286
 
 
287
    @method render
 
288
    @chainable
 
289
    @see View.render()
 
290
    **/
 
291
    render: function () {
 
292
        var container           = this.get('container'),
 
293
            viewContainer       = this.get('viewContainer'),
 
294
            activeView          = this.get('activeView'),
 
295
            activeViewContainer = activeView && activeView.get('container'),
 
296
            areSame             = container.compareTo(viewContainer);
 
297
 
 
298
        container.addClass(App.CSS_CLASS);
 
299
        viewContainer.addClass(App.VIEWS_CSS_CLASS);
 
300
 
 
301
        // Prevents needless shuffling around of nodes and maintains DOM order.
 
302
        if (activeView && !viewContainer.contains(activeViewContainer)) {
 
303
            viewContainer.appendChild(activeViewContainer);
 
304
        }
 
305
 
 
306
        // Prevents needless shuffling around of nodes and maintains DOM order.
 
307
        if (!container.contains(viewContainer) && !areSame) {
 
308
            container.appendChild(viewContainer);
 
309
        }
 
310
 
 
311
        return this;
 
312
    },
 
313
 
 
314
    /**
 
315
    Sets which view is active/visible for the application. This will set the
 
316
    app's `activeView` attribute to the specified `view`.
 
317
 
 
318
    The `view` will be "attached" to this app, meaning it will be both rendered
 
319
    into this app's `viewContainer` node and all of its events will bubble to
 
320
    the app. The previous `activeView` will be "detached" from this app.
 
321
 
 
322
    When a string-name is provided for a view which has been registered on this
 
323
    app's `views` object, the referenced metadata will be used and the
 
324
    `activeView` will be set to either a preserved view instance, or a new
 
325
    instance of the registered view will be created using the specified `config`
 
326
    object passed-into this method.
 
327
 
 
328
    A callback function can be specified as either the third or fourth argument,
 
329
    and this function will be called after the new `view` becomes the
 
330
    `activeView`, is rendered to the `viewContainer`, and is ready to use.
 
331
 
 
332
    @example
 
333
        var app = new Y.App({
 
334
            views: {
 
335
                usersView: {
 
336
                    // Imagine that `Y.UsersView` has been defined.
 
337
                    type: Y.UsersView
 
338
                }
 
339
            },
 
340
 
 
341
            users: new Y.ModelList()
 
342
        });
 
343
 
 
344
        app.route('/users/', function () {
 
345
            this.showView('usersView', {users: this.get('users')});
 
346
        });
 
347
 
 
348
        app.render();
 
349
        app.navigate('/uses/'); // => Creates a new `Y.UsersView` and shows it.
 
350
 
 
351
    @method showView
 
352
    @param {String|View} view The name of a view defined in the `views` object,
 
353
        or a view instance which should become this app's `activeView`.
 
354
    @param {Object} [config] Optional configuration to use when creating a new
 
355
        view instance. This config object can also be used to update an existing
 
356
        or preserved view's attributes when `options.update` is `true`.
 
357
    @param {Object} [options] Optional object containing any of the following
 
358
        properties:
 
359
      @param {Function} [options.callback] Optional callback function to call
 
360
        after new `activeView` is ready to use, the function will be passed:
 
361
          @param {View} options.callback.view A reference to the new
 
362
            `activeView`.
 
363
      @param {Boolean} [options.prepend=false] Whether the `view` should be
 
364
        prepended instead of appended to the `viewContainer`.
 
365
      @param {Boolean} [options.render] Whether the `view` should be rendered.
 
366
        **Note:** If no value is specified, a view instance will only be
 
367
        rendered if it's newly created by this method.
 
368
      @param {Boolean} [options.update=false] Whether an existing view should
 
369
        have its attributes updated by passing the `config` object to its
 
370
        `setAttrs()` method. **Note:** This option does not have an effect if
 
371
        the `view` instance is created as a result of calling this method.
 
372
    @param {Function} [callback] Optional callback Function to call after the
 
373
        new `activeView` is ready to use. **Note:** this will override
 
374
        `options.callback` and it can be specified as either the third or fourth
 
375
        argument. The function will be passed the following:
 
376
      @param {View} callback.view A reference to the new `activeView`.
 
377
    @chainable
 
378
    @since 3.5.0
 
379
    **/
 
380
    showView: function (view, config, options, callback) {
 
381
        var viewInfo, created;
 
382
 
 
383
        options || (options = {});
 
384
 
 
385
        // Support the callback function being either the third or fourth arg.
 
386
        if (callback) {
 
387
            options.callback = callback;
 
388
        } else if (Lang.isFunction(options)) {
 
389
            options = {callback: options};
 
390
        }
 
391
 
 
392
        if (Lang.isString(view)) {
 
393
            viewInfo = this.getViewInfo(view);
 
394
 
 
395
            // Use the preserved view instance, or create a new view.
 
396
            // TODO: Maybe we can remove the strict check for `preserve` and
 
397
            // assume we'll use a View instance if it is there, and just check
 
398
            // `preserve` when detaching?
 
399
            if (viewInfo && viewInfo.preserve && viewInfo.instance) {
 
400
                view = viewInfo.instance;
 
401
 
 
402
                // Make sure there's a mapping back to the view metadata.
 
403
                this._viewInfoMap[Y.stamp(view, true)] = viewInfo;
 
404
            } else {
 
405
                // TODO: Add the app as a bubble target during construction, but
 
406
                // make sure to check that it isn't already in `bubbleTargets`!
 
407
                // This will allow the app to be notified for about _all_ of the
 
408
                // view's events. **Note:** This should _only_ happen if the
 
409
                // view is created _after_ `activeViewChange`.
 
410
 
 
411
                view    = this.createView(view, config);
 
412
                created = true;
 
413
            }
 
414
        }
 
415
 
 
416
        // Update the specified or preserved `view` when signaled to do so.
 
417
        // There's no need to updated a view if it was _just_ created.
 
418
        if (options.update && !created) {
 
419
            view.setAttrs(config);
 
420
        }
 
421
 
 
422
        // TODO: Hold off on rendering the view until after it has been
 
423
        // "attached", and move the call to render into `_attachView()`.
 
424
 
 
425
        // When a value is specified for `options.render`, prefer it because it
 
426
        // represents the developer's intent. When no value is specified, the
 
427
        // `view` will only be rendered if it was just created.
 
428
        if ('render' in options) {
 
429
            options.render && view.render();
 
430
        } else if (created) {
 
431
            view.render();
 
432
        }
 
433
 
 
434
        return this._set('activeView', view, {options: options});
 
435
    },
 
436
 
 
437
    // -- Protected Methods ----------------------------------------------------
 
438
 
 
439
    /**
 
440
    Helper method to attach the view instance to the application by making the
 
441
    app a bubble target of the view, append the view to the `viewContainer`, and
 
442
    assign it to the `instance` property of the associated view info metadata.
 
443
 
 
444
    @method _attachView
 
445
    @param {View} view View to attach.
 
446
    @param {Boolean} prepend=false Whether the view should be prepended instead
 
447
      of appended to the `viewContainer`.
 
448
    @protected
 
449
    @since 3.5.0
 
450
    **/
 
451
    _attachView: function (view, prepend) {
 
452
        if (!view) {
 
453
            return;
 
454
        }
 
455
 
 
456
        var viewInfo      = this.getViewInfo(view),
 
457
            viewContainer = this.get('viewContainer');
 
458
 
 
459
        view.addTarget(this);
 
460
        viewInfo && (viewInfo.instance = view);
 
461
 
 
462
        // TODO: Attach events here for persevered Views?
 
463
        // See related TODO in `_detachView`.
 
464
 
 
465
        // TODO: Actually render the view here so that it gets "attached" before
 
466
        // it gets rendered?
 
467
 
 
468
        // Insert view into the DOM.
 
469
        viewContainer[prepend ? 'prepend' : 'append'](view.get('container'));
 
470
    },
 
471
 
 
472
    /**
 
473
    Overrides View's container destruction to deal with the `viewContainer` and
 
474
    checks to make sure not to remove and purge the `<body>`.
 
475
 
 
476
    @method _destroyContainer
 
477
    @protected
 
478
    @see View._destroyContainer()
 
479
    **/
 
480
    _destroyContainer: function () {
 
481
        var container     = this.get('container'),
 
482
            viewContainer = this.get('viewContainer'),
 
483
            areSame       = container.compareTo(viewContainer);
 
484
 
 
485
        // We do not want to remove or destroy the `<body>`.
 
486
        if (Y.one('body').compareTo(container)) {
 
487
            // Just clean-up our events listeners.
 
488
            this.detachEvents();
 
489
 
 
490
            // Clean-up `yui3-app` CSS class on the `container`.
 
491
            container && container.removeClass(App.CSS_CLASS);
 
492
 
 
493
            if (areSame) {
 
494
                // Clean-up `yui3-app-views` CSS class on the `container`.
 
495
                container && container.removeClass(App.VIEWS_CSS_CLASS);
 
496
            } else {
 
497
                // Destroy and purge the `viewContainer`.
 
498
                viewContainer && viewContainer.remove(true);
 
499
            }
 
500
 
 
501
            return;
 
502
        }
 
503
 
 
504
        // Remove and purge events from both containers.
 
505
        viewContainer && viewContainer.remove(true);
 
506
        !areSame && container && container.remove(true);
 
507
    },
 
508
 
 
509
    /**
 
510
    Helper method to detach the view instance from the application by removing
 
511
    the application as a bubble target of the view, and either just removing the
 
512
    view if it is intended to be preserved, or destroying the instance
 
513
    completely.
 
514
 
 
515
    @method _detachView
 
516
    @param {View} view View to detach.
 
517
    @protected
 
518
    @since 3.5.0
 
519
    **/
 
520
    _detachView: function (view) {
 
521
        if (!view) {
 
522
            return;
 
523
        }
 
524
 
 
525
        var viewInfo = this.getViewInfo(view) || {};
 
526
 
 
527
        if (viewInfo.preserve) {
 
528
            view.remove();
 
529
            // TODO: Detach events here for preserved Views? It is possible that
 
530
            // some event subscriptions are made on elements other than the
 
531
            // View's `container`.
 
532
        } else {
 
533
            view.destroy({remove: true});
 
534
 
 
535
            // TODO: The following should probably happen automagically from
 
536
            // `destroy()` being called! Possibly `removeTarget()` as well.
 
537
 
 
538
            // Remove from view to view-info map.
 
539
            delete this._viewInfoMap[Y.stamp(view, true)];
 
540
 
 
541
            // Remove from view-info instance property.
 
542
            if (view === viewInfo.instance) {
 
543
                delete viewInfo.instance;
 
544
            }
 
545
        }
 
546
 
 
547
        view.removeTarget(this);
 
548
    },
 
549
 
 
550
    /**
 
551
    Getter for the `viewContainer` attribute.
 
552
 
 
553
    @method _getViewContainer
 
554
    @param {Node|null} value Current attribute value.
 
555
    @return {Node} View container node.
 
556
    @protected
 
557
    @since 3.5.0
 
558
    **/
 
559
    _getViewContainer: function (value) {
 
560
        // This wackiness is necessary to enable fully lazy creation of the
 
561
        // container node both when no container is specified and when one is
 
562
        // specified via a valueFn.
 
563
 
 
564
        if (!value && !this._viewContainer) {
 
565
            // Create a default container and set that as the new attribute
 
566
            // value. The `this._viewContainer` property prevents infinite
 
567
            // recursion.
 
568
            value = this._viewContainer = this.create();
 
569
            this._set('viewContainer', value);
 
570
        }
 
571
 
 
572
        return value;
 
573
    },
 
574
 
 
575
    /**
 
576
    Gets the current full URL. When `html5` is false, the URL will first be
 
577
    upgraded before it's returned.
 
578
 
 
579
    @method _getURL
 
580
    @return {String} URL.
 
581
    @protected
 
582
    @see Router._getURL()
 
583
    **/
 
584
    _getURL: function () {
 
585
        var url = Y.getLocation().toString();
 
586
        return this._html5 ? url : this._upgradeURL(url);
 
587
    },
 
588
 
 
589
    /**
 
590
    Provides the default value for the `html5` attribute.
 
591
 
 
592
    The value returned is dependent on the value of the `serverRouting`
 
593
    attribute. When `serverRouting` is explicit set to `false` (not just falsy),
 
594
    the default value for `html5` will be set to `false` for *all* browsers.
 
595
 
 
596
    When `serverRouting` is `true` or `undefined` the returned value will be
 
597
    dependent on the browser's capability of using HTML5 history.
 
598
 
 
599
    @method _initHtml5
 
600
    @return {Boolean} Whether or not HTML5 history should be used.
 
601
    @protected
 
602
    @since 3.5.0
 
603
    **/
 
604
    _initHtml5: function () {
 
605
        // When `serverRouting` is explicitly set to `false` (not just falsy),
 
606
        // forcing hash-based URLs in all browsers.
 
607
        if (this.get('serverRouting') === false) {
 
608
            return false;
 
609
        } else {
 
610
            return Router.html5;
 
611
        }
 
612
    },
 
613
 
 
614
    /**
 
615
    Determines if the specified `view` is configured as a child of the specified
 
616
    `parent` view. This requires both views to be either named-views, or view
 
617
    instances created using configuration data that exists in the `views`
 
618
    object, e.g. created by the `createView()` or `showView()` method.
 
619
 
 
620
    @method _isChildView
 
621
    @param {View|String} view The name of a view defined in the `views` object,
 
622
      or a view instance.
 
623
    @param {View|String} parent The name of a view defined in the `views`
 
624
      object, or a view instance.
 
625
    @return {Boolean} Whether the view is configured as a child of the parent.
 
626
    @protected
 
627
    @since 3.5.0
 
628
    **/
 
629
    _isChildView: function (view, parent) {
 
630
        var viewInfo   = this.getViewInfo(view),
 
631
            parentInfo = this.getViewInfo(parent);
 
632
 
 
633
        if (viewInfo && parentInfo) {
 
634
            return this.getViewInfo(viewInfo.parent) === parentInfo;
 
635
        }
 
636
 
 
637
        return false;
 
638
    },
 
639
 
 
640
    /**
 
641
    Determines if the specified `view` is configured as the parent of the
 
642
    specified `child` view. This requires both views to be either named-views,
 
643
    or view instances created using configuration data that exists in the
 
644
    `views` object, e.g. created by the `createView()` or `showView()` method.
 
645
 
 
646
    @method _isParentView
 
647
    @param {View|String} view The name of a view defined in the `views` object,
 
648
      or a view instance.
 
649
    @param {View|String} parent The name of a view defined in the `views`
 
650
      object, or a view instance.
 
651
    @return {Boolean} Whether the view is configured as the parent of the child.
 
652
    @protected
 
653
    @since 3.5.0
 
654
    **/
 
655
    _isParentView: function (view, child) {
 
656
        var viewInfo  = this.getViewInfo(view),
 
657
            childInfo = this.getViewInfo(child);
 
658
 
 
659
        if (viewInfo && childInfo) {
 
660
            return this.getViewInfo(childInfo.parent) === viewInfo;
 
661
        }
 
662
 
 
663
        return false;
 
664
    },
 
665
 
 
666
    /**
 
667
    Underlying implementation for `navigate()`.
 
668
 
 
669
    @method _navigate
 
670
    @param {String} url The fully-resolved URL that the app should dispatch to
 
671
      its route handlers to fulfill the enhanced navigation "request", or use to
 
672
      update `window.location` in non-HTML5 history capable browsers when
 
673
      `serverRouting` is `true`.
 
674
    @param {Object} [options] Additional options to configure the navigation.
 
675
      These are mixed into the `navigate` event facade.
 
676
        @param {Boolean} [options.replace] Whether or not the current history
 
677
          entry will be replaced, or a new entry will be created. Will default
 
678
          to `true` if the specified `url` is the same as the current URL.
 
679
        @param {Boolean} [options.force] Whether the enhanced navigation
 
680
          should occur even in browsers without HTML5 history. Will default to
 
681
          `true` when `serverRouting` is falsy.
 
682
    @protected
 
683
    @see PjaxBase._navigate()
 
684
    **/
 
685
    _navigate: function (url, options) {
 
686
        url = this._upgradeURL(url);
 
687
 
 
688
        options || (options = {});
 
689
 
 
690
        if (!this.get('serverRouting')) {
 
691
            // Force navigation to be enhanced and handled by the app when
 
692
            // `serverRouting` is falsy because the server might not be able to
 
693
            // properly handle the request.
 
694
            'force' in options || (options.force = true);
 
695
        }
 
696
 
 
697
        return PjaxBase.prototype._navigate.call(this, url, options);
 
698
    },
 
699
 
 
700
    /**
 
701
    Will either save a history entry using `pushState()` or the location hash,
 
702
    or gracefully-degrade to sending a request to the server causing a full-page
 
703
    reload.
 
704
 
 
705
    Overrides Router's `_save()` method to preform graceful-degradation when the
 
706
    app's `serverRouting` is `true` and `html5` is `false` by updating the full
 
707
    URL via standard assignment to `window.location` or by calling
 
708
    `window.location.replace()`; both of which will cause a request to the
 
709
    server resulting in a full-page reload.
 
710
 
 
711
    Otherwise this will just delegate off to Router's `_save()` method allowing
 
712
    the client-side enhanced routing to occur.
 
713
 
 
714
    @method _save
 
715
    @param {String} [url] URL for the history entry.
 
716
    @param {Boolean} [replace=false] If `true`, the current history entry will
 
717
      be replaced instead of a new one being added.
 
718
    @chainable
 
719
    @protected
 
720
    @see Router._save()
 
721
    **/
 
722
    _save: function (url, replace) {
 
723
        // Forces full-path URLs to always be used by modifying
 
724
        // `window.location` in non-HTML5 history capable browsers.
 
725
        if (this.get('serverRouting') && !this.get('html5')) {
 
726
            // Perform same-origin check on the specified URL.
 
727
            if (!this._hasSameOrigin(url)) {
 
728
                Y.error('Security error: The new URL must be of the same origin as the current URL.');
 
729
                return this;
 
730
            }
 
731
 
 
732
            // Results in the URL's full path starting with '/'.
 
733
            url = this._joinURL(url || '');
 
734
 
 
735
            // Either replace the current history entry or create a new one
 
736
            // while navigating to the `url`.
 
737
            if (replace) {
 
738
                win && win.location.replace(url);
 
739
            } else {
 
740
                win && (win.location = url);
 
741
            }
 
742
 
 
743
            return this;
 
744
        }
 
745
 
 
746
        return Router.prototype._save.apply(this, arguments);
 
747
    },
 
748
 
 
749
    /**
 
750
    Performs the actual change of this app's `activeView` by attaching the
 
751
    `newView` to this app, and detaching the `oldView` from this app using any
 
752
    specified `options`.
 
753
 
 
754
    The `newView` is attached to the app by rendering it to the `viewContainer`,
 
755
    and making this app a bubble target of its events.
 
756
 
 
757
    The `oldView` is detached from the app by removing it from the
 
758
    `viewContainer`, and removing this app as a bubble target for its events.
 
759
    The `oldView` will either be preserved or properly destroyed.
 
760
 
 
761
    **Note:** The `activeView` attribute is read-only and can be changed by
 
762
    calling the `showView()` method.
 
763
 
 
764
    @method _uiSetActiveView
 
765
    @param {View} newView The View which is now this app's `activeView`.
 
766
    @param {View} [oldView] The View which was this app's `activeView`.
 
767
    @param {Object} [options] Optional object containing any of the following
 
768
        properties:
 
769
      @param {Function} [options.callback] Optional callback function to call
 
770
        after new `activeView` is ready to use, the function will be passed:
 
771
          @param {View} options.callback.view A reference to the new
 
772
            `activeView`.
 
773
      @param {Boolean} [options.prepend=false] Whether the `view` should be
 
774
        prepended instead of appended to the `viewContainer`.
 
775
      @param {Boolean} [options.render] Whether the `view` should be rendered.
 
776
        **Note:** If no value is specified, a view instance will only be
 
777
        rendered if it's newly created by this method.
 
778
      @param {Boolean} [options.update=false] Whether an existing view should
 
779
        have its attributes updated by passing the `config` object to its
 
780
        `setAttrs()` method. **Note:** This option does not have an effect if
 
781
        the `view` instance is created as a result of calling this method.
 
782
    @protected
 
783
    @since 3.5.0
 
784
    **/
 
785
    _uiSetActiveView: function (newView, oldView, options) {
 
786
        options || (options = {});
 
787
 
 
788
        var callback = options.callback,
 
789
            isChild  = this._isChildView(newView, oldView),
 
790
            isParent = !isChild && this._isParentView(newView, oldView),
 
791
            prepend  = !!options.prepend || isParent;
 
792
 
 
793
        // Prevent detaching (thus removing) the view we want to show. Also hard
 
794
        // to animate out and in, the same view.
 
795
        if (newView === oldView) {
 
796
            return callback && callback.call(this, newView);
 
797
        }
 
798
 
 
799
        this._attachView(newView, prepend);
 
800
        this._detachView(oldView);
 
801
 
 
802
        callback && callback.call(this, newView);
 
803
    },
 
804
 
 
805
    /**
 
806
    Upgrades a hash-based URL to a full-path URL, if necessary.
 
807
 
 
808
    The specified `url` will be upgraded if its of the same origin as the
 
809
    current URL and has a path-like hash. URLs that don't need upgrading will be
 
810
    returned as-is.
 
811
 
 
812
    @example
 
813
        app._upgradeURL('http://example.com/#/foo/'); // => 'http://example.com/foo/';
 
814
 
 
815
    @method _upgradeURL
 
816
    @param {String} url The URL to upgrade from hash-based to full-path.
 
817
    @return {String} The upgraded URL, or the specified URL untouched.
 
818
    @protected
 
819
    @since 3.5.0
 
820
    **/
 
821
    _upgradeURL: function (url) {
 
822
        // We should not try to upgrade paths for external URLs.
 
823
        if (!this._hasSameOrigin(url)) {
 
824
            return url;
 
825
        }
 
826
 
 
827
        // TODO: Should the `root` be removed first, and the hash only
 
828
        // considered if in the form of '/#/'?
 
829
        var hash       = (url.match(/#(.*)$/) || [])[1] || '',
 
830
            hashPrefix = Y.HistoryHash.hashPrefix;
 
831
 
 
832
        // Strip any hash prefix, like hash-bangs.
 
833
        if (hashPrefix && hash.indexOf(hashPrefix) === 0) {
 
834
            hash = hash.replace(hashPrefix, '');
 
835
        }
 
836
 
 
837
        // If the hash looks like a URL path, assume it is, and upgrade it!
 
838
        if (hash && hash.charAt(0) === '/') {
 
839
            // Re-join with configured `root` before resolving.
 
840
            url = this._resolveURL(this._joinURL(hash));
 
841
        }
 
842
 
 
843
        return url;
 
844
    },
 
845
 
 
846
    // -- Protected Event Handlers ---------------------------------------------
 
847
 
 
848
    /**
 
849
    Handles the application's `activeViewChange` event (which is fired when the
 
850
    `activeView` attribute changes) by detaching the old view, attaching the new
 
851
    view.
 
852
 
 
853
    The `activeView` attribute is read-only, so the public API to change its
 
854
    value is through the `showView()` method.
 
855
 
 
856
    @method _afterActiveViewChange
 
857
    @param {EventFacade} e
 
858
    @protected
 
859
    @since 3.5.0
 
860
    **/
 
861
    _afterActiveViewChange: function (e) {
 
862
        this._uiSetActiveView(e.newVal, e.prevVal, e.options);
 
863
    }
 
864
}, {
 
865
    ATTRS: {
 
866
        /**
 
867
        The application's active/visible view.
 
868
 
 
869
        This attribute is read-only, to set the `activeView` use the
 
870
        `showView()` method.
 
871
 
 
872
        @attribute activeView
 
873
        @type View
 
874
        @default null
 
875
        @readOnly
 
876
        @see App.Base.showView()
 
877
        @since 3.5.0
 
878
        **/
 
879
        activeView: {
 
880
            value   : null,
 
881
            readOnly: true
 
882
        },
 
883
 
 
884
        /**
 
885
        Container node which represents the application's bounding-box, into
 
886
        which this app's content will be rendered.
 
887
 
 
888
        The container node serves as the host for all DOM events attached by the
 
889
        app. Delegation is used to handle events on children of the container,
 
890
        allowing the container's contents to be re-rendered at any time without
 
891
        losing event subscriptions.
 
892
 
 
893
        The default container is the `<body>` Node, but you can override this in
 
894
        a subclass, or by passing in a custom `container` config value at
 
895
        instantiation time.
 
896
 
 
897
        When `container` is overridden by a subclass or passed as a config
 
898
        option at instantiation time, it may be provided as a selector string, a
 
899
        DOM element, or a `Y.Node` instance. During initialization, this app's
 
900
        `create()` method will be called to convert the container into a
 
901
        `Y.Node` instance if it isn't one already and stamp it with the CSS
 
902
        class: `"yui3-app"`.
 
903
 
 
904
        The container is not added to the page automatically. This allows you to
 
905
        have full control over how and when your app is actually rendered to
 
906
        the page.
 
907
 
 
908
        @attribute container
 
909
        @type HTMLElement|Node|String
 
910
        @default Y.one('body')
 
911
        @initOnly
 
912
        **/
 
913
        container: {
 
914
            valueFn: function () {
 
915
                return Y.one('body');
 
916
            }
 
917
        },
 
918
 
 
919
        /**
 
920
        Whether or not this browser is capable of using HTML5 history.
 
921
 
 
922
        This value is dependent on the value of `serverRouting` and will default
 
923
        accordingly.
 
924
 
 
925
        Setting this to `false` will force the use of hash-based history even on
 
926
        HTML5 browsers, but please don't do this unless you understand the
 
927
        consequences.
 
928
 
 
929
        @attribute html5
 
930
        @type Boolean
 
931
        @initOnly
 
932
        @see serverRouting
 
933
        **/
 
934
        html5: {
 
935
            valueFn: '_initHtml5'
 
936
        },
 
937
 
 
938
        /**
 
939
        CSS selector string used to filter link click events so that only the
 
940
        links which match it will have the enhanced-navigation behavior of pjax
 
941
        applied.
 
942
 
 
943
        When a link is clicked and that link matches this selector, navigating
 
944
        to the link's `href` URL using the enhanced, pjax, behavior will be
 
945
        attempted; and the browser's default way to navigate to new pages will
 
946
        be the fallback.
 
947
 
 
948
        By default this selector will match _all_ links on the page.
 
949
 
 
950
        @attribute linkSelector
 
951
        @type String|Function
 
952
        @default "a"
 
953
        **/
 
954
        linkSelector: {
 
955
            value: 'a'
 
956
        },
 
957
 
 
958
        /**
 
959
        Whether or not this application's server is capable of properly routing
 
960
        all requests and rendering the initial state in the HTML responses.
 
961
 
 
962
        This can have three different values, each having particular
 
963
        implications on how the app will handle routing and navigation:
 
964
 
 
965
          * `undefined`: The best form of URLs will be chosen based on the
 
966
            capabilities of the browser. Given no information about the server
 
967
            environmentm a balanced approach to routing and navigation is
 
968
            chosen.
 
969
 
 
970
            The server should be capable of handling full-path requests, since
 
971
            full-URLs will be generated by browsers using HTML5 history. If this
 
972
            is a client-side-only app the server could handle full-URL requests
 
973
            by sending a redirect back to the root with a hash-based URL, e.g:
 
974
 
 
975
                Request:     http://example.com/users/1
 
976
                Redirect to: http://example.com/#/users/1
 
977
 
 
978
          * `true`: The server is *fully* capable of properly handling requests
 
979
            to all full-path URLs the app can produce.
 
980
 
 
981
            This is the best option for progressive-enhancement because it will
 
982
            cause **all URLs to always have full-paths**, which means the server
 
983
            will be able to accurately handle all URLs this app produces. e.g.
 
984
 
 
985
                http://example.com/users/1
 
986
 
 
987
            To meet this strict full-URL requirement, browsers which are not
 
988
            capable of using HTML5 history will make requests to the server
 
989
            resulting in full-page reloads.
 
990
 
 
991
          * `false`: The server is *not* capable of properly handling requests
 
992
            to all full-path URLs the app can produce, therefore all routing
 
993
            will be handled by this App instance.
 
994
 
 
995
            Be aware that this will cause **all URLs to always be hash-based**,
 
996
            even in browsers that are capable of using HTML5 history. e.g.
 
997
 
 
998
                http://example.com/#/users/1
 
999
 
 
1000
            A single-page or client-side-only app where the server sends a
 
1001
            "shell" page with JavaScript to the client might have this
 
1002
            restriction. If you're setting this to `false`, read the following:
 
1003
 
 
1004
        **Note:** When this is set to `false`, the server will *never* receive
 
1005
        the full URL because browsers do not send the fragment-part to the
 
1006
        server, that is everything after and including the "#".
 
1007
 
 
1008
        Consider the following example:
 
1009
 
 
1010
            URL shown in browser: http://example.com/#/users/1
 
1011
            URL sent to server:   http://example.com/
 
1012
 
 
1013
        You should feel bad about hurting our precious web if you forcefully set
 
1014
        either `serverRouting` or `html5` to `false`, because you're basically
 
1015
        punching the web in the face here with your lossy URLs! Please make sure
 
1016
        you know what you're doing and that you understand the implications.
 
1017
 
 
1018
        Ideally you should always prefer full-path URLs (not /#/foo/), and want
 
1019
        full-page reloads when the client's browser is not capable of enhancing
 
1020
        the experience using the HTML5 history APIs. Setting this to `true` is
 
1021
        the best option for progressive-enhancement (and graceful-degradation).
 
1022
 
 
1023
        @attribute serverRouting
 
1024
        @type Boolean
 
1025
        @default undefined
 
1026
        @initOnly
 
1027
        @since 3.5.0
 
1028
        **/
 
1029
        serverRouting: {
 
1030
            value    : undefined,
 
1031
            writeOnce: 'initOnly'
 
1032
        },
 
1033
 
 
1034
        /**
 
1035
        The node into which this app's `views` will be rendered when they become
 
1036
        the `activeView`.
 
1037
 
 
1038
        The view container node serves as the container to hold the app's
 
1039
        `activeView`. Each time the `activeView` is set via `showView()`, the
 
1040
        previous view will be removed from this node, and the new active view's
 
1041
        `container` node will be appended.
 
1042
 
 
1043
        The default view container is a `<div>` Node, but you can override this
 
1044
        in a subclass, or by passing in a custom `viewContainer` config value at
 
1045
        instantiation time. The `viewContainer` may be provided as a selector
 
1046
        string, DOM element, or a `Y.Node` instance (having the `viewContainer`
 
1047
        and the `container` be the same node is also supported).
 
1048
 
 
1049
        The app's `render()` method will stamp the view container with the CSS
 
1050
        class `"yui3-app-views"` and append it to the app's `container` node if
 
1051
        it isn't already, and any `activeView` will be appended to this node if
 
1052
        it isn't already.
 
1053
 
 
1054
        @attribute viewContainer
 
1055
        @type HTMLElement|Node|String
 
1056
        @default Y.Node.create(this.containerTemplate)
 
1057
        @initOnly
 
1058
        @since 3.5.0
 
1059
        **/
 
1060
        viewContainer: {
 
1061
            getter   : '_getViewContainer',
 
1062
            setter   : Y.one,
 
1063
            writeOnce: true
 
1064
        }
 
1065
    },
 
1066
 
 
1067
    // TODO: Should these go on the `prototype`?
 
1068
    // TODO: These should also just go in a `CLASS_NAMES` object.
 
1069
 
 
1070
    /**
 
1071
    CSS class added to an app's `container` node.
 
1072
 
 
1073
    @property CSS_CLASS
 
1074
    @type String
 
1075
    @default "yui3-app"
 
1076
    @static
 
1077
    @since 3.5.0
 
1078
    **/
 
1079
    CSS_CLASS: getClassName('app'),
 
1080
 
 
1081
    /**
 
1082
    CSS class added to an app's `viewContainer` node.
 
1083
 
 
1084
    @property VIEWS_CSS_CLASS
 
1085
    @type String
 
1086
    @default "yui3-app-views"
 
1087
    @static
 
1088
    @since 3.5.0
 
1089
    **/
 
1090
    VIEWS_CSS_CLASS: getClassName('app', 'views'),
 
1091
 
 
1092
    /**
 
1093
    Properties that shouldn't be turned into ad-hoc attributes when passed to
 
1094
    App's constructor.
 
1095
 
 
1096
    @property _NON_ATTRS_CFG
 
1097
    @type Array
 
1098
    @static
 
1099
    @protected
 
1100
    @since 3.5.0
 
1101
    **/
 
1102
    _NON_ATTRS_CFG: ['views']
 
1103
});
 
1104
 
 
1105
// -- Namespace ----------------------------------------------------------------
 
1106
Y.namespace('App').Base = App;
 
1107
 
 
1108
/**
 
1109
Provides a top-level application component which manages navigation and views.
 
1110
 
 
1111
This gives you a foundation and structure on which to build your application; it
 
1112
combines robust URL navigation with powerful routing and flexible view
 
1113
management.
 
1114
 
 
1115
`Y.App` is both a namespace and constructor function. The `Y.App` class is
 
1116
special in that any `Y.App` class extensions that are included in the YUI
 
1117
instance will be **auto-mixed** on to the `Y.App` class. Consider this example:
 
1118
 
 
1119
    YUI().use('app-base', 'app-transitions', function (Y) {
 
1120
        // This will create two YUI Apps, `basicApp` will not have transitions,
 
1121
        // but `fancyApp` will have transitions support included and turn it on.
 
1122
        var basicApp = new Y.App.Base(),
 
1123
            fancyApp = new Y.App({transitions: true});
 
1124
    });
 
1125
 
 
1126
@class App
 
1127
@param {Object} [config] The following are configuration properties that can be
 
1128
    specified _in addition_ to default attribute values and the non-attribute
 
1129
    properties provided by `Y.Base`:
 
1130
  @param {Object} [config.views] Hash of view-name to metadata used to
 
1131
    declaratively describe an application's views and their relationship with
 
1132
    the app and other views. The views specified here will override any defaults
 
1133
    provided by the `views` object on the `prototype`.
 
1134
@constructor
 
1135
@extends App.Base
 
1136
@uses App.Transitions
 
1137
@since 3.5.0
 
1138
**/
 
1139
Y.App = Y.mix(Y.Base.create('app', Y.App.Base, []), Y.App, true);
 
1140
 
 
1141
 
 
1142
}, '3.5.1' ,{requires:['classnamemanager', 'pjax-base', 'router', 'view']});