app-base-debug.js revision 042de896986219bc3b2b86b6c356c0ec7d662d32
0N/AYUI.add('app-base', function(Y) {
0N/A
0N/A/**
0N/AProvides a top-level application component which manages navigation and views.
0N/A
0N/A@submodule app-base
0N/A@since 3.5.0
0N/A**/
0N/A
0N/Avar Lang = Y.Lang,
0N/A Transition = Y.Transition,
0N/A
0N/A App;
0N/A
0N/A/**
0N/AProvides a top-level application component which manages navigation and views.
0N/A
0N/A * TODO: Add more description.
0N/A * TODO: Should this extend `Y.Base` and mix in `Y.Router` along with
0N/A `Y.PjaxBase` and `Y.View`? Also need to make sure the `Y.Base`-based
1054N/A extensions are doing the proper thing w.r.t. multiple inheritance.
0N/A
0N/A@class App
65N/A@constructor
125N/A@extends Base
125N/A@uses View
58N/A@uses Router
77N/A@uses PjaxBase
125N/A@since 3.5.0
125N/A**/
125N/AApp = Y.App = Y.Base.create('app', Y.Base, [Y.View, Y.Router, Y.PjaxBase], {
1016N/A // -- Public Properties ----------------------------------------------------
125N/A
261N/A /**
261N/A Hash of view-name to meta data used to declaratively describe an
583N/A application's views and their relationship with the app and other views.
312N/A
1062N/A The view info in `views` is an Object keyed to a view name and can have any
312N/A or all of the following properties:
467N/A
428N/A * `type`: Function or a string representing the view constructor to use to
126N/A create view instances. If a string is used, the constructor function is
1088N/A assumed to be on the `Y` object; e.g. `"SomeView"` -> `Y.SomeView`.
58N/A
394N/A * `preserve`: Boolean for whether the view instance should be retained. By
8N/A default, the view instance will be destroyed when it is no longer the
77N/A active view. If `true` the view instance will simply be `removed()` from
0N/A the DOM when it is no longer active. This is useful when the view is
0N/A frequently used and may be expensive to re-create.
0N/A
0N/A * `parent`: String to another named view in this hash that represents
0N/A parent view within the application's view hierarchy; e.g. a `"photo"`
0N/A view could have `"album"` has its `parent` view. This parent/child
491N/A relationship is used a queue for which transition to use.
439N/A
491N/A * `instance`: Used internally to manage the current instance of this named
465N/A view. This can be used if your view instance is created up-front, or if
465N/A you would rather manage the View lifecyle, but you probably should just
491N/A let this be handled for you.
491N/A
491N/A * TODO: Should `transitions` be supported on the registered views?
886N/A
886N/A If `views` are passed at instantiation time, they will override any views
886N/A set on the prototype.
886N/A
886N/A @property views
886N/A @type Object
491N/A @default {}
491N/A **/
491N/A views: {},
491N/A
65N/A /**
65N/A Transitions to use when the `activeView` changes.
65N/A
65N/A Transition configurations contain a two properties: `viewIn` and `viewOut`;
464N/A there exists three configurations that represent the different scenarios of
0N/A the `activeView` changing:
58N/A
312N/A * `navigate`: The default set of transitions to use when changing the
312N/A `activeView` of the application.
260N/A
491N/A * `toChild`: The set of transitions to use when the `activeView` changes
428N/A to a named view who's `parent` property references the meta data of the
376N/A previously active view.
376N/A
0N/A * `toParent`: The set of transitions to use when the `activeView` changes
11N/A to a named view who's meta data is referenced by the previously active
0N/A view's `parent` property.
240N/A
1016N/A With the current state of `Y.Transition`, it is best to used named
58N/A transitions that registered on `Y.Transition.fx`. If `transitions` are
58N/A passed at instantiation time, they will override any transitions set on
58N/A the prototype.
58N/A
77N/A @property transitions
207N/A @type Object
207N/A @default
910N/A
1062N/A {
77N/A navigate: {
260N/A viewIn : 'app:fadeIn',
112N/A viewOut: 'app:fadeOut'
77N/A },
77N/A
77N/A toChild: {
77N/A viewIn : 'app:slideLeft',
260N/A viewOut: 'app:slideLeft'
77N/A },
77N/A
77N/A toParent: {
491N/A viewIn : 'app:slideRight',
1088N/A viewOut: 'app:slideRight'
77N/A }
491N/A }
111N/A
111N/A **/
111N/A transitions: {
111N/A navigate: {
1088N/A viewIn : 'app:fadeIn',
111N/A viewOut: 'app:fadeOut'
111N/A },
111N/A
491N/A toChild: {
1088N/A viewIn : 'app:slideLeft',
1088N/A viewOut: 'app:slideLeft'
1088N/A },
1088N/A
1088N/A toParent: {
1088N/A viewIn : 'app:slideRight',
886N/A viewOut: 'app:slideRight'
111N/A }
491N/A },
77N/A
77N/A // -- Protected Properties -------------------------------------------------
491N/A
491N/A /**
491N/A Map of view instance id (via `Y.stamp()`) to view-info object in `views`.
491N/A
77N/A This mapping is used to tie a specific view instance back to its meta data
491N/A by adding a reference to the the related view info on the `views` object.
1088N/A
1062N/A @property _viewInfoMap
491N/A @type Object
491N/A @default {}
1088N/A @protected
491N/A **/
491N/A
491N/A // -- Lifecycle Methods ----------------------------------------------------
491N/A initializer: function (config) {
491N/A config || (config = {});
491N/A
491N/A this.views = config.views ?
491N/A Y.merge(this.views, config.views) : this.views;
1088N/A
491N/A // TODO: Deep merge?
491N/A this.transitions = config.transitions ?
491N/A Y.merge(this.transitions, config.transitions) : this.transitions;
491N/A
491N/A this._viewInfoMap = {};
491N/A
491N/A this.after('activeViewChange', this._afterActiveViewChange);
491N/A },
1088N/A
491N/A // -- Public Methods -------------------------------------------------------
1088N/A
491N/A /**
491N/A Creates and returns this app's `container` node from the specified HTML
491N/A string, DOM element, or existing `Y.Node` instance. This method is called
491N/A internally when the view is initialized.
491N/A
491N/A This node is also stamped with the CSS class specified by `Y.App.CSS_CLASS`.
491N/A
491N/A By default, the created node is _not_ added to the DOM automatically.
491N/A
491N/A @method create
491N/A @param {HTMLElement|Node|String} container HTML string, DOM element, or
491N/A `Y.Node` instance to use as the container node.
1016N/A @return {Node} Node instance of the created container node.
1016N/A **/
1016N/A create: function () {
491N/A var container = Y.View.prototype.create.apply(this, arguments);
491N/A return container && container.addClass(App.CSS_CLASS);
491N/A },
491N/A
491N/A /**
491N/A Renders this application by appending the `viewContainer` node to the
491N/A `container` node, and showing the `activeView`.
491N/A
491N/A You should call this method at least once, usually after the initialization
799N/A of your `Y.App` instance.
1088N/A
799N/A @method render
886N/A @chainable
886N/A **/
886N/A render: function () {
886N/A var viewContainer = this.get('viewContainer'),
886N/A activeView = this.get('activeView');
891N/A
886N/A activeView && viewContainer.setContent(activeView.get('container'));
891N/A viewContainer.appendTo(this.get('container'));
886N/A
886N/A return this;
886N/A },
886N/A
886N/A /**
886N/A Returns the meta data associated with a view instance or view name defined
491N/A on the `views` object.
491N/A
491N/A @method getViewInfo
1088N/A @param {View|String} view View instance, or name of a view defined on the
491N/A `views` object.
1088N/A @return {Object} The meta data for the view.
491N/A **/
491N/A getViewInfo: function (view) {
491N/A if (view instanceof Y.View) {
491N/A return this._viewInfoMap[Y.stamp(view, true)];
491N/A }
491N/A
491N/A return this.views[view];
491N/A },
491N/A
1088N/A /**
491N/A Creates and returns a new view instance using the provided `name` to look up
1088N/A the view info meta data defined in the `views` object. The passed-in
491N/A `config` object is passed to the view constructor function.
1088N/A
491N/A This function also maps a view instance back to its view info meta data.
491N/A
491N/A @method createView
491N/A @param {String} name The name of a view defined on the `views` object.
491N/A @param {Object} [config] The configuration object passed to the view
1088N/A constructor function when creating the new view instance.
491N/A @return {View} The new view instance.
491N/A **/
491N/A createView: function (name, config) {
491N/A // TODO: Should `type` default to Y.View?
491N/A var viewInfo = this.getViewInfo(name),
1088N/A type = viewInfo && viewInfo.type,
1062N/A ViewConstructor = Lang.isString(type) ? Y[type] : type,
491N/A view;
77N/A
491N/A // TODO: Default to `Y.View` or throw error if `ViewConstructor` is not
491N/A // a function?
1088N/A if (Lang.isFunction(ViewConstructor)) {
1088N/A view = new ViewConstructor(config).render();
490N/A this._viewInfoMap[Y.stamp(view, true)] = viewInfo;
490N/A }
490N/A
490N/A return view;
489N/A },
490N/A
491N/A /**
491N/A Sets which view is visible/active within the application.
1088N/A
1088N/A This will set the application's `activeView` attribute to the view instance
490N/A passed-in, or when a view name is provided, the `activeView` attribute will
490N/A be set to either the preserved instance, or a new view instance will be
490N/A created using the passed in `config`.
490N/A
490N/A TODO: Document transition info and config.
490N/A
491N/A @method showView
1088N/A @param {String|View} view The name of a view defined in the `views` object,
491N/A or a view instance.
1026N/A @param {Object} [config] Optional configuration to use when creating a new
1088N/A view instance.
1026N/A @param {Function|Object} [options] Optional callback Function, or object
491N/A containing any of the following properties:
491N/A @param {Object} [options.transitions] An object that contains transition
491N/A configuration overrides for the following properties:
491N/A @param {Object} [options.transitions.viewIn] Transition overrides for
491N/A the view being transitioned-in.
1088N/A @param {Object} [options.transitions.viewOut] Transition overrides for
491N/A the view being transitioned-out.
1088N/A @param {Function} [options.callback] Function to callback after setting
491N/A the new active view.
491N/A @chainable
491N/A **/
491N/A showView: function (view, config, options) {
491N/A var viewInfo;
491N/A
491N/A if (Lang.isString(view)) {
491N/A viewInfo = this.getViewInfo(view) || {};
491N/A view = viewInfo.instance || this.createView(view, config);
1088N/A }
491N/A
491N/A Lang.isFunction(options) && (options = {callback: options});
491N/A
491N/A return this._set('activeView', view, options);
491N/A },
99N/A
491N/A // -- Protected Methods ----------------------------------------------------
491N/A
1088N/A /**
491N/A Determines if the `view` passed in is configured as a child of the `parent`
1088N/A view passed in. This requires both views to be either named-views, or view
491N/A instanced created using configuration data that exists in the `views`
491N/A object.
491N/A
491N/A @method _isChildView
126N/A @param {View|String} view The name of a view defined in the `views` object,
126N/A or a view instance.
126N/A @param {View|String} parent The name of a view defined in the `views`
491N/A object, or a view instance.
491N/A @return {Boolean} Whether the view is configured as a child of the parent.
491N/A @protected
491N/A **/
491N/A _isChildView: function (view, parent) {
491N/A var viewInfo = this.getViewInfo(view),
491N/A parentInfo = this.getViewInfo(parent);
491N/A
491N/A if (viewInfo && parentInfo) {
491N/A return this.getViewInfo(viewInfo.parent) === parentInfo;
491N/A }
491N/A },
491N/A
126N/A /**
491N/A Determines if the `view` passed in is configured as a parent of the `child`
491N/A view passed in. This requires both views to be either named-views, or view
491N/A instanced created using configuration data that exists in the `views`
491N/A object.
491N/A
491N/A @method _isParentView
491N/A @param {View|String} view The name of a view defined in the `views` object,
491N/A or a view instance.
491N/A @param {View|String} parent The name of a view defined in the `views`
491N/A object, or a view instance.
491N/A @return {Boolean} Whether the view is configured as a parent of the child.
1054N/A @protected
1054N/A **/
491N/A _isParentView: function (view, child) {
491N/A var viewInfo = this.getViewInfo(view),
491N/A childInfo = this.getViewInfo(child);
491N/A
491N/A if (viewInfo && childInfo) {
1088N/A return this.getViewInfo(childInfo.parent) === viewInfo;
491N/A }
491N/A },
126N/A
491N/A _setViewContainer: function (viewContainer) {
491N/A viewContainer = Y.one(viewContainer);
491N/A return viewContainer && viewContainer.addClass(App.VIEWS_CSS_CLASS);
126N/A },
126N/A
491N/A /**
994N/A Transitions the `oldView` out and the `newView` using the provided `fx` and
994N/A `fxConfigs` transition overrides.
1088N/A
994N/A @method _transitionViews
994N/A @param {View} newView The view instance to transition-in (if any).
994N/A @param {View} oldView The view instance to transition-out (if any).
994N/A @param {Object} fx The set of named transition effects to use which have
994N/A been registered on `Y.Transition.fx`. This object should contain two
491N/A properties:
491N/A @param {String} fx.viewIn The named transition for the new active view.
1088N/A @param {String} fx.viewOut The named transition for the old active view.
491N/A @param {Object} [fxConfigs] Optional set of transition overrides, this
1088N/A object can contain the following properties:
491N/A @param {Object} [fxConfigs.viewIn] Optional transition overrides for the
491N/A new active view.
491N/A @param {Object} [fxConfigs.viewOut] Optional transition overrides for the
491N/A old active view.
491N/A @param {Function} [callback] Optional function to call once the transition
491N/A has completed.
491N/A @protected
491N/A **/
491N/A _transitionViews: function (newView, oldView, fx, fxConfigs, callback) {
491N/A var self = this,
491N/A called = false;
491N/A
491N/A // TODO: A better way to handle the completion of the transitions?
491N/A // This currently assumes at least one view was passed in and that the
491N/A // transitions have the same duration.
491N/A
491N/A function done () {
491N/A if (!called) {
261N/A called = true;
0N/A callback && callback.call(self);
0N/A }
77N/A }
77N/A
491N/A newView && newView.get('container').transition(fx.viewIn,
77N/A fxConfigs.viewIn, done);
77N/A
77N/A oldView && oldView.get('container').transition(fx.viewOut,
77N/A fxConfigs.viewOut, done);
77N/A },
1062N/A
1062N/A /**
1088N/A Helper method to attach the view instance to the application by making the
1062N/A application a bubble target of the view, and assigning the view instance to
1064N/A the `instance` property of the associated view info meta data.
1062N/A
1062N/A // TODO: Should attachment handle the actual insertion into the DOM?
1088N/A // This might help for extracting the transitions into an app extension.
886N/A // `_detachView` does the removal of the view from the DOM.
886N/A
886N/A @method _attachView
886N/A @param {View} view View to attach.
886N/A @protected
886N/A **/
886N/A _attachView: function (view) {
886N/A var viewInfo;
886N/A
886N/A if (view) {
1088N/A view.addTarget(this);
886N/A // TODO: Should this happen eagerly, before the transition?
886N/A viewInfo = this.getViewInfo(view);
886N/A viewInfo && (viewInfo.instance = view);
886N/A }
886N/A },
886N/A
886N/A /**
886N/A Helper method to detach the view instance from the application by removing
886N/A the application as a bubble target of the view, and either just removing the
1088N/A view if it is intended to be preserved, or destroying the instance
1088N/A completely.
886N/A
1017N/A @method _detachView
1088N/A @param {View} view View to detach.
1088N/A @protected
1017N/A **/
1017N/A _detachView: function (view) {
1017N/A if (!view) {
1017N/A return;
1017N/A }
1017N/A
1017N/A var viewInfo = this.getViewInfo(view) || {};
1017N/A
1017N/A if (viewInfo.preserve) {
1017N/A view.remove();
1017N/A } else {
1088N/A view.destroy();
1088N/A
1088N/A // Remove from view to view-info map.
1088N/A delete this._viewInfoMap[Y.stamp(view, true)];
1088N/A
1088N/A // Remove from view-info instance property.
1016N/A if (view === viewInfo.instance) {
312N/A delete viewInfo.instance;
491N/A }
491N/A }
491N/A
491N/A view.removeTarget(this);
491N/A },
491N/A
491N/A // -- Protected Event Handlers ---------------------------------------------
491N/A
491N/A /**
491N/A Handles the application's `activeViewChange` event (which is fired when the
491N/A `activeView` attribute changes) by detaching the old view, attaching the new
491N/A view and transitioning between them.
491N/A
491N/A The `activeView` attribute is read-only, so the public API to change its
491N/A value is through the `showView()` method.
491N/A
312N/A @method _afterActiveViewChange
491N/A @param {EventFacade} e
491N/A @protected
491N/A **/
491N/A _afterActiveViewChange: function (e) {
491N/A var newView = e.newVal,
491N/A oldView = e.prevVal,
491N/A callback = e.callback,
491N/A isChild = this._isChildView(newView, oldView),
491N/A isParent = !isChild && this._isParentView(newView, oldView),
491N/A prepend = !!e.prepend || isParent,
491N/A fx = this.transitions,
491N/A fxConfigs = e.transitions || {};
491N/A
491N/A // Prevent detaching (thus removing) the view we want to show.
491N/A // Also hard to animate out and in, the same view.
491N/A if (newView === oldView) {
491N/A return callback && callback.call(this, newView);
491N/A }
491N/A
491N/A // Determine transitions to use.
491N/A if (isChild) {
491N/A fx = fx.toChild;
491N/A } else if (isParent) {
491N/A fx = fx.toParent;
491N/A } else {
491N/A fx = fx.navigate;
491N/A }
1054N/A
1054N/A // Insert the new view.
491N/A // TODO: Should the insertion move into the `_attachView()` method?
491N/A // TODO: Is the prepend/append too brittle/hacky just to handle the
1054N/A // slide transition implementation?
1062N/A if (newView && prepend) {
491N/A this.get('viewContainer').prepend(newView.get('container'));
491N/A } else if (newView) {
491N/A this.get('viewContainer').append(newView.get('container'));
491N/A }
491N/A
491N/A // TODO: Consider refactor based on a `app-transitions` extension which
491N/A // would either override or API `_attachView()` and `_detachView()`.
491N/A this._transitionViews(newView, oldView, fx, fxConfigs, function () {
491N/A this._detachView(oldView);
491N/A this._attachView(newView);
491N/A
491N/A callback && callback.call(this, newView);
491N/A });
491N/A }
491N/A
491N/A}, {
491N/A ATTRS: {
491N/A /**
491N/A Container node which represents the application's bounding-box.
1054N/A
491N/A @attribute container
491N/A @type HTMLElement|Node|String
491N/A @default "body"
312N/A @initOnly
491N/A **/
491N/A container: {
491N/A value: 'body'
491N/A },
491N/A
491N/A /**
491N/A Container node into which all application views will be rendered.
0N/A
491N/A @attribute viewContainer
491N/A @type HTMLElement|Node|String
77N/A @default Y.Node.create('<div/>')
491N/A @initOnly
491N/A **/
491N/A viewContainer: {
491N/A valueFn: function () {
491N/A return Y.Node.create('<div/>');
0N/A },
491N/A
491N/A setter : '_setViewContainer',
77N/A writeOnce: 'initOnly'
1054N/A },
1054N/A
1054N/A /**
1054N/A This attribute is provided by `PjaxBase`, but the default value is
491N/A overridden to match all links on the page.
491N/A
491N/A @attribute linkSelector
1062N/A @type String|Function
491N/A @default "a"
1062N/A **/
491N/A linkSelector: {
1062N/A value: 'a'
491N/A },
1062N/A
491N/A /**
491N/A The application's active/visible view.
491N/A
491N/A This attribute is read-only, to set the `activeView`, use the
491N/A `showView()` method.
491N/A
491N/A @attribute activeView
491N/A @type View
491N/A @readOnly
491N/A @see showView
491N/A **/
491N/A activeView: {
491N/A readOnly: true
491N/A }
491N/A },
491N/A
1062N/A CSS_CLASS : Y.ClassNameManager.getClassName('app'),
491N/A VIEWS_CSS_CLASS: Y.ClassNameManager.getClassName('app', 'views')
491N/A});
491N/A
491N/A// -- Transitions --------------------------------------------------------------
491N/AY.mix(Transition.fx, {
491N/A 'app:fadeIn': {
491N/A opacity : 1,
491N/A duration: 0.35,
491N/A
491N/A on: {
491N/A start: function () {
491N/A // TODO: Cross-fade transition that doesn't require a change in
491N/A // position?
491N/A
491N/A // var position = this.getStyle('position');
1054N/A // if (position !== 'absolute') {
491N/A // this._transitionPosition = position;
491N/A // this.setStyle('position', 'absolute');
491N/A // }
491N/A this.setStyle('opacity', 0);
491N/A },
491N/A
99N/A end: function () {
491N/A // if (this._transitionPosition) {
491N/A // this.setStyle('position', this._transitionPosition);
491N/A // delete this._transitionPosition;
58N/A // }
77N/A }
491N/A }
491N/A },
491N/A
77N/A 'app:fadeOut': {
491N/A opacity : 0,
1054N/A duration: 0.35,
99N/A
58N/A on: {
491N/A start: function () {
58N/A // TODO: Cross-fade transition that doesn't require a change in
491N/A // position?
491N/A
491N/A // var position = this.getStyle('position');
491N/A // if (position !== 'absolute') {
207N/A // this._transitionPosition = position;
1054N/A // this.setStyle('position', 'absolute');
491N/A // }
491N/A },
491N/A
491N/A end: function () {
491N/A // if (this._transitionPosition) {
491N/A // this.setStyle('position', this._transitionPosition);
297N/A // delete this._transitionPosition;
491N/A // }
508N/A }
910N/A }
508N/A },
508N/A
508N/A 'app:slideLeft': {
1054N/A duration : 0.35,
274N/A transform: 'translateX(-100%)',
274N/A
491N/A on: {
491N/A end: function () {
491N/A this.setStyle('transform', 'none');
491N/A }
491N/A }
491N/A },
491N/A
930N/A 'app:slideRight': {
930N/A duration : 0.35,
583N/A transform: 'translateX(0)',
583N/A
491N/A on: {
583N/A start: function () {
491N/A this.setStyle('transform', 'translateX(-100%)');
261N/A },
491N/A
491N/A end: function () {
491N/A this.setStyle('transform', 'none');
1054N/A }
491N/A }
491N/A }
491N/A});
491N/A
491N/A
979N/A}, '@VERSION@' ,{requires:['controller', 'pjax-base', 'view', 'transition']});
264N/A