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