src/cm/media/js/lib/yui/yui3-3.15.0/build/app-base/app-base-debug.js
changeset 602 e16a97fb364a
equal deleted inserted replaced
601:d334a616c023 602:e16a97fb364a
       
     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 }, '@VERSION@', {"requires": ["classnamemanager", "pjax-base", "router", "view"]});