Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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