Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Code clean-up, organization, and docs for Y.App.

  • Loading branch information...
commit 25de9d38287abdc19f4a3ac229785abeba30ae93 1 parent f936584
@ericf authored
Showing with 323 additions and 251 deletions.
  1. +289 −251 src/app/js/app-base.js
  2. +34 −0 src/app/js/app-transitions.js
View
540 src/app/js/app-base.js
@@ -5,20 +5,21 @@ Provides a top-level application component which manages navigation and views.
@since 3.5.0
**/
-var Lang = Y.Lang,
- win = Y.config.win,
- App;
+var Lang = Y.Lang,
+ View = Y.View,
+ Router = Y.Router,
+ PjaxBase = Y.PjaxBase,
+
+ win = Y.config.win,
-// TODO: Use module-scoped references to Y.Router and Y.View because they are
-// referenced throughout this code?
+ App;
/**
Provides a top-level application component which manages navigation and views.
- * TODO: Add more description.
- * TODO: Should this extend `Y.Base` and mix in `Y.Router` along with
- `Y.PjaxBase` and `Y.View`? Also need to make sure the `Y.Base`-based
- extensions are doing the proper thing w.r.t. multiple inheritance.
+This gives you a foundation and structure on which to build your application.
+`Y.App` combines robust URL navigation with powerful routing and flexible view
+management.
@class App
@constructor
@@ -28,7 +29,7 @@ Provides a top-level application component which manages navigation and views.
@uses PjaxBase
@since 3.5.0
**/
-App = Y.Base.create('app', Y.Base, [Y.Router, Y.PjaxBase, Y.View], {
+App = Y.Base.create('app', Y.Base, [View, Router, PjaxBase], {
// -- Public Properties ----------------------------------------------------
/**
@@ -61,6 +62,22 @@ App = Y.Base.create('app', Y.Base, [Y.Router, Y.PjaxBase, Y.View], {
If `views` are passed at instantiation time, they will override any views
set on the prototype.
+ @example
+ // Imagine that `Y.UsersView` and `Y.UserView` have been defined.
+ var app = new Y.App({
+ views: {
+ users: {
+ type : Y.UsersView,
+ preserve: true
+ },
+
+ user: {
+ type : Y.UserView,
+ parent: 'users'
+ }
+ }
+ });
+
@property views
@type Object
@default {}
@@ -86,7 +103,7 @@ App = Y.Base.create('app', Y.Base, [Y.Router, Y.PjaxBase, Y.View], {
config || (config = {});
this.views = config.views ?
- Y.merge(this.views, config.views) : this.views;
+ Y.merge(this.views, config.views) : this.views;
this._viewInfoMap = {};
@@ -102,42 +119,69 @@ App = Y.Base.create('app', Y.Base, [Y.Router, Y.PjaxBase, Y.View], {
// -- Public Methods -------------------------------------------------------
/**
- Creates and returns this app's `container` node from the specified HTML
+ Creates and returns this apps's container node from the specified selector
string, DOM element, or existing `Y.Node` instance. This method is called
- internally when the view is initialized.
+ internally when the app is initialized.
This node is also stamped with the CSS class specified by `Y.App.CSS_CLASS`.
By default, the created node is _not_ added to the DOM automatically.
@method create
- @param {HTMLElement|Node|String} container HTML string, DOM element, or
- `Y.Node` instance to use as the container node.
+ @param {String|Node|HTMLElement} container Selector string, `Y.Node`
+ instance, or DOM element to use as the container node.
@return {Node} Node instance of the created container node.
**/
create: function () {
- var container = Y.View.prototype.create.apply(this, arguments);
+ var container = View.prototype.create.apply(this, arguments);
return container && container.addClass(App.CSS_CLASS);
},
/**
- Renders this application by appending the `viewContainer` node to the
- `container` node, and showing the `activeView`.
+ Creates and returns a new view instance using the provided `name` to look up
+ the view info metadata defined in the `views` object. The passed-in `config`
+ object is passed to the view constructor function.
- You should call this method at least once, usually after the initialization
- of your `Y.App` instance.
+ This function also maps a view instance back to its view info metadata.
- @method render
- @chainable
+ @method createView
+ @param {String} name The name of a view defined on the `views` object.
+ @param {Object} [config] The configuration object passed to the view
+ constructor function when creating the new view instance.
+ @return {View} The new view instance.
**/
- render: function () {
- var viewContainer = this.get('viewContainer'),
- activeView = this.get('activeView');
+ createView: function (name, config) {
+ var viewInfo = this.getViewInfo(name),
+ type = (viewInfo && viewInfo.type) || View,
+ ViewConstructor = Lang.isString(type) ? Y[type] : type,
+ view;
- activeView && viewContainer.setContent(activeView.get('container'));
- viewContainer.appendTo(this.get('container'));
+ // Create the view instance and map it with its metadata.
+ view = new ViewConstructor(config);
+ this._viewInfoMap[Y.stamp(view, true)] = viewInfo;
- return this;
+ return view;
+ },
+
+ /**
+ Creates and returns this app's view-container node from the specified
+ selector string, DOM element, or existing `Y.Node` instance. This method is
+ called internally when the app is initialized.
+
+ This node is also stamped with the CSS class specified by
+ `Y.App.VIEWS_CSS_CLASS`.
+
+ By default, the created node is appended to the `container` node by the
+ `render()` method.
+
+ @method createViewContainer
+ @param {String|Node|HTMLElement} viewContainer Selector string, `Y.Node`
+ instance, or DOM element to use as the view-container node.
+ @return {Node} Node instance of the created view-container node.
+ **/
+ createViewContainer: function (viewContainer) {
+ viewContainer = Y.one(viewContainer);
+ return viewContainer && viewContainer.addClass(App.VIEWS_CSS_CLASS);
},
/**
@@ -151,7 +195,7 @@ App = Y.Base.create('app', Y.Base, [Y.Router, Y.PjaxBase, Y.View], {
not registered.
**/
getViewInfo: function (view) {
- if (view instanceof Y.View) {
+ if (view instanceof View) {
return this._viewInfoMap[Y.stamp(view, true)];
}
@@ -159,29 +203,27 @@ App = Y.Base.create('app', Y.Base, [Y.Router, Y.PjaxBase, Y.View], {
},
/**
- Creates and returns a new view instance using the provided `name` to look up
- the view info metadata defined in the `views` object. The passed-in `config`
- object is passed to the view constructor function.
+ Renders this application by appending the `viewContainer` node to the
+ `container` node, and showing the `activeView`.
- This function also maps a view instance back to its view info metadata.
+ You should call this method at least once, usually after the initialization
+ of your `Y.App` instance.
- @method createView
- @param {String} name The name of a view defined on the `views` object.
- @param {Object} [config] The configuration object passed to the view
- constructor function when creating the new view instance.
- @return {View} The new view instance.
+ You may override this method to customize the app's rendering, but it is
+ expected that the `viewContainer` is reserved for the app to manage its
+ `activeView`.
+
+ @method render
+ @chainable
**/
- createView: function (name, config) {
- var viewInfo = this.getViewInfo(name),
- type = (viewInfo && viewInfo.type) || Y.View,
- ViewConstructor = Lang.isString(type) ? Y[type] : type,
- view;
+ render: function () {
+ var viewContainer = this.get('viewContainer'),
+ activeView = this.get('activeView');
- // Create the view instance and map it with its metadata.
- view = new ViewConstructor(config);
- this._viewInfoMap[Y.stamp(view, true)] = viewInfo;
+ activeView && viewContainer.setContent(activeView.get('container'));
+ viewContainer.appendTo(this.get('container'));
- return view;
+ return this;
},
/**
@@ -196,6 +238,23 @@ App = Y.Base.create('app', Y.Base, [Y.Router, Y.PjaxBase, Y.View], {
and this function will be called after the new `view` is the `activeView`
and ready to use.
+ @example
+ var app = new Y.App({
+ views: {
+ users: {
+ // Imagine that `Y.UsersView` has been defined.
+ type: Y.UsersView
+ }
+ }
+ });
+
+ app.route('/users/', function () {
+ this.showView('users');
+ });
+
+ app.render();
+ app.navigate('/uses/'); // => Creates a new `Y.UsersView` and shows it.
+
@method showView
@param {String|View} view The name of a view defined in the `views` object,
or a view instance.
@@ -205,15 +264,9 @@ App = Y.Base.create('app', Y.Base, [Y.Router, Y.PjaxBase, Y.View], {
properties:
@param {Boolean} [options.prepend] Whether the new view should be
prepended instead of appended to the `viewContainer`.
- @param {Object} [options.transitions] An object that contains transition
- configuration overrides for the following properties:
- @param {Object} [options.transitions.viewIn] Transition overrides for
- the view being transitioned-in.
- @param {Object} [options.transitions.viewOut] Transition overrides for
- the view being transitioned-out.
@param {Function} [callback] Optional callback Function to call after the
new `activeView` is ready to use, the function will be passed:
- @param {View} view
+ @param {View} callback.view
@chainable
**/
showView: function (view, config, options, callback) {
@@ -248,6 +301,71 @@ App = Y.Base.create('app', Y.Base, [Y.Router, Y.PjaxBase, Y.View], {
// -- Protected Methods ----------------------------------------------------
/**
+ Helper method to attach the view instance to the application by making the
+ application a bubble target of the view, and assigning the view instance to
+ the `instance` property of the associated view info metadata.
+
+ @method _attachView
+ @param {View} view View to attach.
+ @param {Boolean} prepend Whether the view should be prepended instead of
+ appended to the `viewContainer`.
+ @protected
+ **/
+ _attachView: function (view, prepend) {
+ if (!view) {
+ return;
+ }
+
+ var viewInfo = this.getViewInfo(view),
+ viewContainer = this.get('viewContainer');
+
+ view.addTarget(this);
+ viewInfo && (viewInfo.instance = view);
+
+ // TODO: Attach events here for perseved Views? See TODO in _detachView.
+
+ // Insert view into the DOM.
+ viewContainer[prepend ? 'prepend' : 'append'](view.get('container'));
+ },
+
+ /**
+ Helper method to detach the view instance from the application by removing
+ the application as a bubble target of the view, and either just removing the
+ view if it is intended to be preserved, or destroying the instance
+ completely.
+
+ @method _detachView
+ @param {View} view View to detach.
+ @protected
+ **/
+ _detachView: function (view) {
+ if (!view) {
+ return;
+ }
+
+ var viewInfo = this.getViewInfo(view) || {};
+
+ if (viewInfo.preserve) {
+ view.remove();
+ // TODO: Detach events here for perserved Views? It is possible that
+ // some event subscriptions are made on elements other than the
+ // View's `container`.
+ } else {
+ view.destroy();
+
+ // Remove from view to view-info map.
+ delete this._viewInfoMap[Y.stamp(view, true)];
+
+ // Remove from view-info instance property.
+ if (view === viewInfo.instance) {
+ delete viewInfo.instance;
+ }
+ }
+
+ view.removeTarget(this);
+ },
+
+ /**
Provides the default value for the `html5` attribute.
The value returned is dependent on the value of the `serverRouting`
@@ -267,8 +385,58 @@ App = Y.Base.create('app', Y.Base, [Y.Router, Y.PjaxBase, Y.View], {
if (this.get('serverRouting') === false) {
return false;
} else {
- return Y.Router.html5;
+ return Router.html5;
+ }
+ },
+
+ /**
+ Determines if the `view` passed in is configured as a child of the `parent`
+ view passed in. This requires both views to be either named-views, or view
+ instanced created using configuration data that exists in the `views`
+ object.
+
+ @method _isChildView
+ @param {View|String} view The name of a view defined in the `views` object,
+ or a view instance.
+ @param {View|String} parent The name of a view defined in the `views`
+ object, or a view instance.
+ @return {Boolean} Whether the view is configured as a child of the parent.
+ @protected
+ **/
+ _isChildView: function (view, parent) {
+ var viewInfo = this.getViewInfo(view),
+ parentInfo = this.getViewInfo(parent);
+
+ if (viewInfo && parentInfo) {
+ return this.getViewInfo(viewInfo.parent) === parentInfo;
}
+
+ return false;
+ },
+
+ /**
+ Determines if the `view` passed in is configured as a parent of the `child`
+ view passed in. This requires both views to be either named-views, or view
+ instanced created using configuration data that exists in the `views`
+ object.
+
+ @method _isParentView
+ @param {View|String} view The name of a view defined in the `views` object,
+ or a view instance.
+ @param {View|String} parent The name of a view defined in the `views`
+ object, or a view instance.
+ @return {Boolean} Whether the view is configured as a parent of the child.
+ @protected
+ **/
+ _isParentView: function (view, child) {
+ var viewInfo = this.getViewInfo(view),
+ childInfo = this.getViewInfo(child);
+
+ if (viewInfo && childInfo) {
+ return this.getViewInfo(childInfo.parent) === viewInfo;
+ }
+
+ return false;
},
/**
@@ -332,49 +500,7 @@ App = Y.Base.create('app', Y.Base, [Y.Router, Y.PjaxBase, Y.View], {
Lang.isValue(options.force) || (options.force = true);
}
- return Y.PjaxBase.prototype._navigate.call(this, url, options);
- },
-
-
- /**
- Upgrades a hash-based URL to a full-path URL if necessary.
-
- The specified `url` will be upgraded if its of the same origin as the
- current URL and has a path-like hash. URLs that don't need upgrading will be
- returned as-is.
-
- @example
- app._upgradeURL('http://example.com/#/foo/');
- // => 'http://example.com/foo/';
-
- @method _upgradeURL
- @param {String} url The URL to upgrade from hash-based to full-path.
- @return {String} The upgraded URL, or the specified URL untouched.
- @protected
- **/
- _upgradeURL: function (url) {
- // We should not try to upgrade paths for external URLs.
- if (!this._hasSameOrigin(url)) {
- return url;
- }
-
- // TODO: Should the `root` be removed first, and the hash only
- // considered if in the form of '/#/'?
- var hash = (url.match(/#(.*)$/) || [])[1] || '',
- hashPrefix = Y.HistoryHash.hashPrefix;
-
- // Strip any hash prefix, like hash-bangs.
- if (hashPrefix && hash.indexOf(hashPrefix) === 0) {
- hash = hash.replace(hashPrefix, '');
- }
-
- // If the hash looks like a URL path, assume it is, and upgrade it!
- if (hash && hash.charAt(0) === '/') {
- // Re-joins with configured `root` before resolving.
- url = this._resolveURL(this._joinURL(hash));
- }
-
- return url;
+ return PjaxBase.prototype._navigate.call(this, url, options);
},
/**
@@ -423,134 +549,47 @@ App = Y.Base.create('app', Y.Base, [Y.Router, Y.PjaxBase, Y.View], {
return this;
}
- return Y.Router.prototype._save.apply(this, arguments);
- },
-
- /**
- Determines if the `view` passed in is configured as a child of the `parent`
- view passed in. This requires both views to be either named-views, or view
- instanced created using configuration data that exists in the `views`
- object.
-
- @method _isChildView
- @param {View|String} view The name of a view defined in the `views` object,
- or a view instance.
- @param {View|String} parent The name of a view defined in the `views`
- object, or a view instance.
- @return {Boolean} Whether the view is configured as a child of the parent.
- @protected
- **/
- _isChildView: function (view, parent) {
- var viewInfo = this.getViewInfo(view),
- parentInfo = this.getViewInfo(parent);
-
- if (viewInfo && parentInfo) {
- return this.getViewInfo(viewInfo.parent) === parentInfo;
- }
-
- return false;
- },
-
- /**
- Determines if the `view` passed in is configured as a parent of the `child`
- view passed in. This requires both views to be either named-views, or view
- instanced created using configuration data that exists in the `views`
- object.
-
- @method _isParentView
- @param {View|String} view The name of a view defined in the `views` object,
- or a view instance.
- @param {View|String} parent The name of a view defined in the `views`
- object, or a view instance.
- @return {Boolean} Whether the view is configured as a parent of the child.
- @protected
- **/
- _isParentView: function (view, child) {
- var viewInfo = this.getViewInfo(view),
- childInfo = this.getViewInfo(child);
-
- if (viewInfo && childInfo) {
- return this.getViewInfo(childInfo.parent) === viewInfo;
- }
-
- return false;
+ return Router.prototype._save.apply(this, arguments);
},
/**
- Adds the `Y.App.VIEWS_CSS_CLASS` to the `viewContainer`.
+ Upgrades a hash-based URL to a full-path URL if necessary.
- @method _setViewContainer
- @param {HTMLElement|Node|String} container HTML string, DOM element, or
- `Y.Node` instance to use as the container node.
- @return {Node} Node instance of the created container node.
- @protected
- **/
- _setViewContainer: function (viewContainer) {
- viewContainer = Y.one(viewContainer);
- return viewContainer && viewContainer.addClass(App.VIEWS_CSS_CLASS);
- },
+ The specified `url` will be upgraded if its of the same origin as the
+ current URL and has a path-like hash. URLs that don't need upgrading will be
+ returned as-is.
- /**
- Helper method to attach the view instance to the application by making the
- application a bubble target of the view, and assigning the view instance to
- the `instance` property of the associated view info metadata.
+ @example
+ app._upgradeURL('http://example.com/#/foo/'); // => 'http://example.com/foo/';
- @method _attachView
- @param {View} view View to attach.
- @param {Boolean} prepend Whether the view should be prepended instead of
- appended to the `viewContainer`.
+ @method _upgradeURL
+ @param {String} url The URL to upgrade from hash-based to full-path.
+ @return {String} The upgraded URL, or the specified URL untouched.
@protected
**/
- _attachView: function (view, prepend) {
- if (!view) {
- return;
+ _upgradeURL: function (url) {
+ // We should not try to upgrade paths for external URLs.
+ if (!this._hasSameOrigin(url)) {
+ return url;
}
- var viewInfo = this.getViewInfo(view),
- viewContainer = this.get('viewContainer');
-
- view.addTarget(this);
- viewInfo && (viewInfo.instance = view);
-
- // TODO: Attach events here?
-
- // Insert view into the DOM.
- viewContainer[prepend ? 'prepend' : 'append'](view.get('container'));
- },
-
- /**
- Helper method to detach the view instance from the application by removing
- the application as a bubble target of the view, and either just removing the
- view if it is intended to be preserved, or destroying the instance
- completely.
+ // TODO: Should the `root` be removed first, and the hash only
+ // considered if in the form of '/#/'?
+ var hash = (url.match(/#(.*)$/) || [])[1] || '',
+ hashPrefix = Y.HistoryHash.hashPrefix;
- @method _detachView
- @param {View} view View to detach.
- @protected
- **/
- _detachView: function (view) {
- if (!view) {
- return;
+ // Strip any hash prefix, like hash-bangs.
+ if (hashPrefix && hash.indexOf(hashPrefix) === 0) {
+ hash = hash.replace(hashPrefix, '');
}
- var viewInfo = this.getViewInfo(view) || {};
-
- if (viewInfo.preserve) {
- // TODO: Detach events here?
- view.remove();
- } else {
- view.destroy();
-
- // Remove from view to view-info map.
- delete this._viewInfoMap[Y.stamp(view, true)];
-
- // Remove from view-info instance property.
- if (view === viewInfo.instance) {
- delete viewInfo.instance;
- }
+ // If the hash looks like a URL path, assume it is, and upgrade it!
+ if (hash && hash.charAt(0) === '/') {
+ // Re-joins with configured `root` before resolving.
+ url = this._resolveURL(this._joinURL(hash));
}
- view.removeTarget(this);
+ return url;
},
// -- Protected Event Handlers ---------------------------------------------
@@ -590,6 +629,21 @@ App = Y.Base.create('app', Y.Base, [Y.Router, Y.PjaxBase, Y.View], {
}, {
ATTRS: {
/**
+ The application's active/visible view.
+
+ This attribute is read-only, to set the `activeView` use the
+ `showView()` method.
+
+ @attribute activeView
+ @type View
+ @readOnly
+ @see showView
+ **/
+ activeView: {
+ readOnly: true
+ },
+
+ /**
Container node which represents the application's bounding-box.
@attribute container
@@ -602,21 +656,22 @@ App = Y.Base.create('app', Y.Base, [Y.Router, Y.PjaxBase, Y.View], {
},
/**
- Container node into which all application views will be rendered.
+ Whether or not this browser is capable of using HTML5 history.
- @attribute viewContainer
- @type HTMLElement|Node|String
- @default `Y.Node.create('<div/>')`
+ This value is dependent on the value of `serverRouting` and will default
+ accordingly.
+
+ Setting this to `false` will force the use of hash-based history even on
+ HTML5 browsers, but please don't do this unless you understand the
+ consequences.
+
+ @attribute html5
+ @type Boolean
@initOnly
+ @see serverRouting
**/
- viewContainer: {
- valueFn: function () {
- return Y.Node.create('<div/>');
- },
-
- // TODO: Change to `createViewContainer()` to be like `create()`?
- setter : '_setViewContainer',
- writeOnce: 'initOnly'
+ html5: {
+ valueFn: '_initHtml5'
},
/**
@@ -632,21 +687,6 @@ App = Y.Base.create('app', Y.Base, [Y.Router, Y.PjaxBase, Y.View], {
},
/**
- The application's active/visible view.
-
- This attribute is read-only, to set the `activeView`, use the
- `showView()` method.
-
- @attribute activeView
- @type View
- @readOnly
- @see showView
- **/
- activeView: {
- readOnly: true
- },
-
- /**
Whether or not this application's server is capable of properly routing
all requests and rendering the initial state in the HTML responses.
@@ -717,22 +757,20 @@ App = Y.Base.create('app', Y.Base, [Y.Router, Y.PjaxBase, Y.View], {
},
/**
- Whether or not this browser is capable of using HTML5 history.
-
- This value is dependent on the value of `serverRouting` and will default
- accordingly.
-
- Setting this to `false` will force the use of hash-based history even on
- HTML5 browsers, but please don't do this unless you understand the
- consequences.
+ Container node into which all application views will be rendered.
- @attribute html5
- @type Boolean
+ @attribute viewContainer
+ @type HTMLElement|Node|String
+ @default `Y.Node.create('<div/>')`
@initOnly
- @see serverRouting
**/
- html5: {
- valueFn: '_initHtml5'
+ viewContainer: {
+ valueFn: function () {
+ return Y.Node.create('<div/>');
+ },
+
+ setter : 'createViewContainer',
+ writeOnce: 'initOnly'
}
},
View
34 src/app/js/app-transitions.js
@@ -95,6 +95,40 @@ AppTransitions.prototype = {
// -- Public Methods -------------------------------------------------------
/**
+ Sets which view is visible/active within the application.
+
+ This will set the application's `activeView` attribute to the view instance
+ passed-in, or when a view name is provided, the `activeView` attribute will
+ be set to either the preserved instance, or a new view instance will be
+ created using the passed in `config`.
+
+ A callback function can be specified as either the third or fourth argument,
+ and this function will be called after the new `view` is the `activeView`
+ and ready to use.
+
+ @method showView
+ @param {String|View} view The name of a view defined in the `views` object,
+ or a view instance.
+ @param {Object} [config] Optional configuration to use when creating a new
+ view instance.
+ @param {Object} [options] Optional object containing any of the following
+ properties:
+ @param {Boolean} [options.prepend] Whether the new view should be
+ prepended instead of appended to the `viewContainer`.
+ @param {Object} [options.transitions] An object that contains transition
+ configuration overrides for the following properties:
+ @param {Object} [options.transitions.viewIn] Transition overrides for
+ the view being transitioned-in.
+ @param {Object} [options.transitions.viewOut] Transition overrides for
+ the view being transitioned-out.
+ @param {Function} [callback] Optional callback Function to call after the
+ new `activeView` is ready to use, the function will be passed:
+ @param {View} view
+ @chainable
+ **/
+ // Does not override `showView()` but does use additional `options`.
+
+ /**
Transitions a view.
@method transitionView
Please sign in to comment.
Something went wrong with that request. Please try again.