From 18843cee48f233f2121580b3b120101459d672d1 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Wed, 20 Jul 2016 13:46:03 -0700 Subject: [PATCH] [API] Change approach to applies-to checking (#1072) * [API] Allow selection * [API] Keep in sync using model * [API] Add selection as EventEmitter * [API] Use selection from ToDo tutorial * [API] Add appliesTo-style method * [API] Remove destroy method, simplify show * [View] Return a no-op * [API] Use new applies-to checking * [API] Rename TodoView to TodoRenderer * [API] Rewire views * [API] Wire up so that things work * [API] Begin adding container ...to attempt to give views something to listen to for destroy-like events * [API] Begin using regions... * [API] Begin working through Region stuff * [API] Revise Region API ...for similarity with Marionette, https://github.com/nasa/openmct/pull/1072#issuecomment-230902986 * [API] Begin separating View, ViewDefinition * [API] Finish separating View/ViewDefinition * [API] Update MCTView ...to reflect updates to Region/View/ViewDefinition APIs * [API] Simplify View API ...merging closely-related populate/show methods, and restoring compatibility with todo tutorial * [API] Wire in from todo tutorial plugin * [API] Switch back to region constants * [API] Update method signature, add JSDoc * [API] Update variable name * [API] Remove unnecessary separate regions file * [API] Relocate Region; not external api * [API] Revert changes to api.js ...as these ended up becoming entirely superficial --- src/MCT.js | 16 +++++++---- src/adapter/directives/MCTView.js | 29 +++++++++++--------- src/adapter/directives/Region.js | 23 ++++++++++++++++ src/api/View.js | 26 +++++++++++++----- src/api/ViewDefinition.js | 44 +++++++++++++++++++++++++++++++ tutorials/todo/todo.js | 15 ++++++++--- 6 files changed, 125 insertions(+), 28 deletions(-) create mode 100644 src/adapter/directives/Region.js create mode 100644 src/api/ViewDefinition.js diff --git a/src/MCT.js b/src/MCT.js index afe9633180..1cba4bef1e 100644 --- a/src/MCT.js +++ b/src/MCT.js @@ -37,7 +37,13 @@ define([ this.legacyBundle.extensions[category].push(extension); }; - MCT.prototype.view = function (region, factory) { + /** + * Register a new type of view. + * + * @param region the region identifier (see mct.regions) + * @param {ViewDefinition} definition the definition for this view + */ + MCT.prototype.view = function (region, definition) { var viewKey = region + uuid(); var adaptedViewKey = "adapted-view-" + region; @@ -61,9 +67,9 @@ define([ this.legacyExtension('policies', { category: "view", implementation: function Policy() { - this.allow = function (view, domainObject) { - if (view.key === adaptedViewKey) { - return !!factory(domainObject); + this.allow = function (v, domainObject) { + if (v.key === adaptedViewKey) { + return definition.canView(domainObject); } return true; }; @@ -71,7 +77,7 @@ define([ }); this.legacyExtension('newViews', { - factory: factory, + factory: definition, region: region, key: viewKey }); diff --git a/src/adapter/directives/MCTView.js b/src/adapter/directives/MCTView.js index d098befd8c..b7ea00773d 100644 --- a/src/adapter/directives/MCTView.js +++ b/src/adapter/directives/MCTView.js @@ -1,24 +1,26 @@ -define(['angular'], function (angular) { +define([ + 'angular', + './Region' +], function (angular, Region) { function MCTView(newViews) { - var factories = {}; + var definitions = {}; newViews.forEach(function (newView) { - factories[newView.region] = factories[newView.region] || {}; - factories[newView.region][newView.key] = newView.factory; + definitions[newView.region] = definitions[newView.region] || {}; + definitions[newView.region][newView.key] = newView.factory; }); return { restrict: 'E', link: function (scope, element, attrs) { - var key, mctObject, region; + var key, mctObject, regionId, region; function maybeShow() { - if (!factories[region] || !factories[region][key] || !mctObject) { + if (!definitions[regionId] || !definitions[regionId][key] || !mctObject) { return; } - var view = factories[region][key](mctObject); - view.show(element[0]); + region.show(definitions[regionId][key].view(mctObject)); } function setKey(k) { @@ -31,15 +33,16 @@ define(['angular'], function (angular) { maybeShow(); } - function setRegion(r) { - region = r; + function setRegionId(r) { + regionId = r; maybeShow(); } - scope.$watch('key', setKey); - scope.$watch('region', setRegion); - scope.$watch('mctObject', setObject); + region = new Region(element[0]); + scope.$watch('key', setKey); + scope.$watch('region', setRegionId); + scope.$watch('mctObject', setObject); }, scope: { key: "=", diff --git a/src/adapter/directives/Region.js b/src/adapter/directives/Region.js new file mode 100644 index 0000000000..9b3c0afca2 --- /dev/null +++ b/src/adapter/directives/Region.js @@ -0,0 +1,23 @@ +define([], function () { + function Region(element) { + this.activeView = undefined; + this.element = element; + } + + Region.prototype.clear = function () { + if (this.activeView) { + this.activeView.destroy(); + this.activeView = undefined; + } + }; + + Region.prototype.show = function (view) { + this.clear(); + this.activeView = view; + if (this.activeView) { + this.activeView.show(this.element); + } + }; + + return Region; +}); diff --git a/src/api/View.js b/src/api/View.js index 3a24ba7856..382460e031 100644 --- a/src/api/View.js +++ b/src/api/View.js @@ -1,23 +1,37 @@ -define(function () { +define([], function () { + + /** + * A View is used to provide displayable content, and to react to + * associated life cycle events. + * + * @interface + */ function View() { + } /** - * Show this view in the specified container. If this view is already - * showing elsewhere, it will be removed from that location. + * Populate the supplied DOM element with the contents of this view. * - * @param {HTMLElement} container the element to populate + * View implementations should use this method to attach any + * listeners or acquire other resources that are necessary to keep + * the contents of this view up-to-date. + * + * @param {HTMLElement} container the DOM element to populate */ View.prototype.show = function (container) { + }; /** * Release any resources associated with this view. * - * Subclasses should override this method to release any resources - * they obtained during a `show` call. + * View implementations should use this method to detach any + * listeners or release other resources that are no longer necessary + * once a view is no longer used. */ View.prototype.destroy = function () { + }; return View; diff --git a/src/api/ViewDefinition.js b/src/api/ViewDefinition.js new file mode 100644 index 0000000000..8650ffc8af --- /dev/null +++ b/src/api/ViewDefinition.js @@ -0,0 +1,44 @@ +define(function () { + + /** + * Defines a kind of view. + * @interface + */ + function ViewDefinition() { + } + + /** + * Get metadata about this view, as may be used in the user interface + * to present options for this view. + * @param {*} object the object to be shown in this view + * @returns {mct.ViewMetadata} metadata about this view + */ + ViewDefinition.prototype.metadata = function (object) { + }; + + /** + * Instantiate a new view of this object. Callers of this method are + * responsible for calling `canView` before instantiating views in this + * manner. + * + * @param {*} object the object to be shown in this view + * @returns {mct.View} a view of this object + */ + ViewDefinition.prototype.view = function (object) { + }; + + /** + * Check if this view is capable of showing this object. Users of + * views should use this method before calling `show`. + * + * Subclasses should override this method to control the applicability + * of this view to other objects. + * + * @param {*} object the object to be shown in this view + * @returns {boolean} true if this view is applicable to this object + */ + ViewDefinition.prototype.canView = function (object) { + }; + + return ViewDefinition; +}); diff --git a/tutorials/todo/todo.js b/tutorials/todo/todo.js index 3b6449068b..5beebbc0d6 100644 --- a/tutorials/todo/todo.js +++ b/tutorials/todo/todo.js @@ -206,11 +206,18 @@ define([ }; mct.type('example.todo', todoType); - mct.view(mct.regions.main, function (domainObject) { - return todoType.check(domainObject) && new TodoView(domainObject); + mct.view(mct.regions.main, { + view: function (domainObject) { + return new TodoView(domainObject); + }, + canView: todoType.check.bind(todoType) }); - mct.view(mct.regions.toolbar, function (domainObject) { - return todoType.check(domainObject) && new TodoToolbarView(domainObject); + + mct.view(mct.regions.toolbar, { + view: function (domainObject) { + return new TodoToolbarView(domainObject); + }, + canView: todoType.check.bind(todoType) }); return mct;