From b7765ff388a0eb23b3435a40c8498de374bcfa01 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 11 Aug 2015 12:54:50 -0700 Subject: [PATCH] [Code Style] Use prototypes in platform WTD-1482 --- platform/core/src/actions/ActionAggregator.js | 44 +-- platform/core/src/actions/ActionCapability.js | 106 ++++--- platform/core/src/actions/ActionProvider.js | 127 ++++----- .../src/actions/LoggingActionDecorator.js | 45 ++- .../src/capabilities/CompositionCapability.js | 103 ++++--- .../src/capabilities/ContextCapability.js | 135 +++++---- .../capabilities/ContextualDomainObject.js | 1 + .../capabilities/CoreCapabilityProvider.js | 13 + .../src/capabilities/DelegationCapability.js | 112 ++++---- .../src/capabilities/MetadataCapability.js | 38 +-- .../src/capabilities/MutationCapability.js | 161 +++++------ .../src/capabilities/PersistenceCapability.js | 145 +++++----- .../capabilities/RelationshipCapability.js | 148 +++++----- .../core/src/models/CachingModelDecorator.js | 88 +++--- .../core/src/models/MissingModelDecorator.js | 37 +-- platform/core/src/models/ModelAggregator.js | 104 +++---- .../core/src/models/PersistedModelProvider.js | 80 +++--- platform/core/src/models/RootModelProvider.js | 47 ++-- .../core/src/models/StaticModelProvider.js | 39 +-- platform/core/src/objects/DomainObject.js | 134 --------- platform/core/src/objects/DomainObjectImpl.js | 143 ++++++++++ .../core/src/objects/DomainObjectProvider.js | 75 ++--- platform/core/src/services/Now.js | 1 - platform/core/src/services/Throttle.js | 1 - platform/core/src/services/Topic.js | 1 - platform/core/src/types/TypeCapability.js | 2 + platform/core/src/types/TypeImpl.js | 264 +++++++++--------- platform/core/src/types/TypeProperty.js | 210 +++++++------- .../core/src/types/TypePropertyConversion.js | 17 ++ platform/core/src/types/TypeProvider.js | 199 +++++++------ platform/core/src/views/ViewCapability.js | 26 +- platform/core/src/views/ViewProvider.js | 56 ++-- .../core/test/objects/DomainObjectSpec.js | 2 +- platform/core/test/types/TypeImplSpec.js | 4 +- platform/core/test/types/TypeProviderSpec.js | 2 +- 35 files changed, 1331 insertions(+), 1379 deletions(-) delete mode 100644 platform/core/src/objects/DomainObject.js create mode 100644 platform/core/src/objects/DomainObjectImpl.js diff --git a/platform/core/src/actions/ActionAggregator.js b/platform/core/src/actions/ActionAggregator.js index d0094a8a90..3056ab04e8 100644 --- a/platform/core/src/actions/ActionAggregator.js +++ b/platform/core/src/actions/ActionAggregator.js @@ -108,41 +108,19 @@ define( * of action services */ function ActionAggregator(actionProviders) { - - function getActions(context) { - // Get all actions from all providers, reduce down - // to one array by concatenation - return actionProviders.map(function (provider) { - return provider.getActions(context); - }).reduce(function (a, b) { - return a.concat(b); - }, []); - } - - return { - /** - * Get a list of actions which are valid in a given - * context. - * - * @param {ActionContext} the context in which - * the action will occur; this is a - * JavaScript object containing key-value - * pairs. Typically, this will contain a - * field "domainObject" which refers to - * the domain object that will be acted - * upon, but may contain arbitrary information - * recognized by specific providers. - * @return {Action[]} an array of actions which - * may be performed in the provided context. - * - * @method - * @memberof ActionAggregator - * @memberof platform/core.ActionAggregator# - */ - getActions: getActions - }; + this.actionProviders = actionProviders; } + ActionAggregator.prototype.getActions = function (context) { + // Get all actions from all providers, reduce down + // to one array by concatenation + return this.actionProviders.map(function (provider) { + return provider.getActions(context); + }).reduce(function (a, b) { + return a.concat(b); + }, []); + }; + return ActionAggregator; } ); diff --git a/platform/core/src/actions/ActionCapability.js b/platform/core/src/actions/ActionCapability.js index b5d055bcb0..2164969a05 100644 --- a/platform/core/src/actions/ActionCapability.js +++ b/platform/core/src/actions/ActionCapability.js @@ -49,71 +49,69 @@ define( * @constructor */ function ActionCapability($q, actionService, domainObject) { + this.$q = $q; + this.actionService = actionService; + this.domainObject = domainObject; + } + /** + * Perform an action. This will find and perform the + * first matching action available for the specified + * context or key. + * + * @param {ActionContext|string} context the context in which + * to perform the action; this is passed along to + * the action service to match against available + * actions. The "domainObject" field will automatically + * be populated with the domain object that exposed + * this capability. If given as a string, this will + * be taken as the "key" field to match against + * specific actions. + * @returns {Promise} the result of the action that was + * performed, or undefined if no matching action + * was found. + * @memberof platform/core.ActionCapability# + */ + ActionCapability.prototype.getActions = function (context) { // Get all actions which are valid in this context; // this simply redirects to the action service, // but additionally adds a domainObject field. - function getActions(context) { - var baseContext = typeof context === 'string' ? - { key: context } : - (context || {}), - actionContext = Object.create(baseContext); + var baseContext = typeof context === 'string' ? + { key: context } : (context || {}), + actionContext = Object.create(baseContext); - actionContext.domainObject = domainObject; + actionContext.domainObject = this.domainObject; - return actionService.getActions(actionContext); - } + return this.actionService.getActions(actionContext); + }; + /** + * Get actions which are available for this domain object, + * in this context. + * + * @param {ActionContext|string} context the context in which + * to perform the action; this is passed along to + * the action service to match against available + * actions. The "domainObject" field will automatically + * be populated with the domain object that exposed + * this capability. If given as a string, this will + * be taken as the "key" field to match against + * specific actions. + * @returns {Action[]} an array of matching actions + * @memberof platform/core.ActionCapability# + */ + ActionCapability.prototype.perform = function (context) { // Alias to getActions(context)[0].perform, with a // check for empty arrays. - function performAction(context) { - var actions = getActions(context); + var actions = this.getActions(context); - return $q.when( - (actions && actions.length > 0) ? - actions[0].perform() : - undefined - ); - } + return this.$q.when( + (actions && actions.length > 0) ? + actions[0].perform() : + undefined + ); + }; - return { - /** - * Perform an action. This will find and perform the - * first matching action available for the specified - * context or key. - * - * @param {ActionContext|string} context the context in which - * to perform the action; this is passed along to - * the action service to match against available - * actions. The "domainObject" field will automatically - * be populated with the domain object that exposed - * this capability. If given as a string, this will - * be taken as the "key" field to match against - * specific actions. - * @returns {Promise} the result of the action that was - * performed, or undefined if no matching action - * was found. - * @memberof platform/core.ActionCapability# - */ - perform: performAction, - /** - * Get actions which are available for this domain object, - * in this context. - * - * @param {ActionContext|string} context the context in which - * to perform the action; this is passed along to - * the action service to match against available - * actions. The "domainObject" field will automatically - * be populated with the domain object that exposed - * this capability. If given as a string, this will - * be taken as the "key" field to match against - * specific actions. - * @returns {Action[]} an array of matching actions - * @memberof platform/core.ActionCapability# - */ - getActions: getActions - }; - } return ActionCapability; } diff --git a/platform/core/src/actions/ActionProvider.js b/platform/core/src/actions/ActionProvider.js index 67bc56520f..dcb17eb6ce 100644 --- a/platform/core/src/actions/ActionProvider.js +++ b/platform/core/src/actions/ActionProvider.js @@ -36,11 +36,45 @@ define( * category of extension.) * * @memberof platform/core + * @imeplements {ActionService} * @constructor */ function ActionProvider(actions) { - var actionsByKey = {}, - actionsByCategory = {}; + var self = this; + + // Build up look-up tables + this.actions = actions; + this.actionsByKey = {}; + this.actionsByCategory = {}; + actions.forEach(function (Action) { + // Get an action's category or categories + var categories = Action.category || []; + + // Convert to an array if necessary + categories = Array.isArray(categories) ? + categories : [categories]; + + // Store action under all relevant categories + categories.forEach(function (category) { + self.actionsByCategory[category] = + self.actionsByCategory[category] || []; + self.actionsByCategory[category].push(Action); + }); + + // Store action by ekey as well + if (Action.key) { + self.actionsByKey[Action.key] = + self.actionsByKey[Action.key] || []; + self.actionsByKey[Action.key].push(Action); + } + }); + } + + ActionProvider.prototype.getActions = function (actionContext) { + var context = (actionContext || {}), + category = context.category, + key = context.key, + candidates; // Instantiate an action; invokes the constructor and // additionally fills in the action's getMetadata method @@ -71,86 +105,31 @@ define( function createIfApplicable(actions, context) { return (actions || []).filter(function (Action) { return Action.appliesTo ? - Action.appliesTo(context) : true; + Action.appliesTo(context) : true; }).map(function (Action) { return instantiateAction(Action, context); }); } - // Get an array of actions that are valid in the supplied context. - function getActions(actionContext) { - var context = (actionContext || {}), - category = context.category, - key = context.key, - candidates; - - // Match actions to the provided context by comparing "key" - // and/or "category" parameters, if specified. - candidates = actions; - if (key) { - candidates = actionsByKey[key]; - if (category) { - candidates = candidates.filter(function (Action) { - return Action.category === category; - }); - } - } else if (category) { - candidates = actionsByCategory[category]; + // Match actions to the provided context by comparing "key" + // and/or "category" parameters, if specified. + candidates = this.actions; + if (key) { + candidates = this.actionsByKey[key]; + if (category) { + candidates = candidates.filter(function (Action) { + return Action.category === category; + }); } - - // Instantiate those remaining actions, with additional - // filtering per any appliesTo methods defined on those - // actions. - return createIfApplicable(candidates, context); + } else if (category) { + candidates = this.actionsByCategory[category]; } - // Build up look-up tables - actions.forEach(function (Action) { - // Get an action's category or categories - var categories = Action.category || []; - - // Convert to an array if necessary - categories = Array.isArray(categories) ? - categories : [categories]; - - // Store action under all relevant categories - categories.forEach(function (category) { - actionsByCategory[category] = - actionsByCategory[category] || []; - actionsByCategory[category].push(Action); - }); - - // Store action by ekey as well - if (Action.key) { - actionsByKey[Action.key] = - actionsByKey[Action.key] || []; - actionsByKey[Action.key].push(Action); - } - }); - - return { - /** - * Get a list of actions which are valid in a given - * context. - * - * @param {ActionContext} the context in which - * the action will occur; this is a - * JavaScript object containing key-value - * pairs. Typically, this will contain a - * field "domainObject" which refers to - * the domain object that will be acted - * upon, but may contain arbitrary information - * recognized by specific providers. - * @return {Action[]} an array of actions which - * may be performed in the provided context. - * - * @method - * @memberof ActionProvider - * @memberof platform/core.ActionProvider# - */ - getActions: getActions - }; - } + // Instantiate those remaining actions, with additional + // filtering per any appliesTo methods defined on those + // actions. + return createIfApplicable(candidates, context); + }; return ActionProvider; } diff --git a/platform/core/src/actions/LoggingActionDecorator.js b/platform/core/src/actions/LoggingActionDecorator.js index cb6efa245e..0d6f170261 100644 --- a/platform/core/src/actions/LoggingActionDecorator.js +++ b/platform/core/src/actions/LoggingActionDecorator.js @@ -36,8 +36,19 @@ define( * * @memberof platform/core * @constructor + * @implements {ActionService} + * @param $log Angular's logging service + * @param {ActionService} actionService the decorated action service */ function LoggingActionDecorator($log, actionService) { + this.$log = $log; + this.actionService = actionService; + } + + LoggingActionDecorator.prototype.getActions = function () { + var actionService = this.actionService, + $log = this.$log; + // Decorate the perform method of the specified action, such that // it emits a log message whenever performed. function addLogging(action) { @@ -59,35 +70,11 @@ define( return logAction; } - return { - /** - * Get a list of actions which are valid in a given - * context. These actions will additionally log - * themselves when performed. - * - * @param {ActionContext} the context in which - * the action will occur; this is a - * JavaScript object containing key-value - * pairs. Typically, this will contain a - * field "domainObject" which refers to - * the domain object that will be acted - * upon, but may contain arbitrary information - * recognized by specific providers. - * @return {Action[]} an array of actions which - * may be performed in the provided context. - * - * @method - * @memberof LoggingActionDecorator - * @memberof platform/core.LoggingActionDecorator# - */ - getActions: function () { - return actionService.getActions.apply( - actionService, - arguments - ).map(addLogging); - } - }; - } + return actionService.getActions.apply( + actionService, + arguments + ).map(addLogging); + }; return LoggingActionDecorator; } diff --git a/platform/core/src/capabilities/CompositionCapability.js b/platform/core/src/capabilities/CompositionCapability.js index 88ed279dd7..f1b2532040 100644 --- a/platform/core/src/capabilities/CompositionCapability.js +++ b/platform/core/src/capabilities/CompositionCapability.js @@ -39,68 +39,59 @@ define( * * @memberof platform/core * @constructor + * @implements {Capability} */ function CompositionCapability($injector, domainObject) { - var objectService, - lastPromise, - lastModified; - // Get a reference to the object service from $injector - function injectObjectService() { - objectService = $injector.get("objectService"); - return objectService; - } - - // Get a reference to the object service (either cached or - // from the injector) - function getObjectService() { - return objectService || injectObjectService(); - } - - // Promise this domain object's composition (an array of domain - // object instances corresponding to ids in its model.) - function promiseComposition() { - var model = domainObject.getModel(), - ids; - - // Then filter out non-existent objects, - // and wrap others (such that they expose a - // "context" capability) - function contextualize(objects) { - return ids.filter(function (id) { - return objects[id]; - }).map(function (id) { - return new ContextualDomainObject( - objects[id], - domainObject - ); - }); - } - - // Make a new request if we haven't made one, or if the - // object has been modified. - if (!lastPromise || lastModified !== model.modified) { - ids = model.composition || []; - lastModified = model.modified; - // Load from the underlying object service - lastPromise = getObjectService().getObjects(ids) - .then(contextualize); - } - - return lastPromise; - } - - return { - /** - * Request the composition of this object. - * @returns {Promise.} a list of all domain - * objects which compose this domain object. - * @memberof platform/core.CompositionCapability# - */ - invoke: promiseComposition + this.injectObjectService = function () { + this.objectService = $injector.get("objectService"); }; + + this.domainObject = domainObject; } + /** + * Request the composition of this object. + * @returns {Promise.} a list of all domain + * objects which compose this domain object. + */ + CompositionCapability.prototype.invoke = function () { + var domainObject = this.domainObject, + model = domainObject.getModel(), + ids; + + // Then filter out non-existent objects, + // and wrap others (such that they expose a + // "context" capability) + function contextualize(objects) { + return ids.filter(function (id) { + return objects[id]; + }).map(function (id) { + return new ContextualDomainObject( + objects[id], + domainObject + ); + }); + } + + // Lazily acquire object service (avoids cyclical dependency) + if (!this.objectService) { + this.injectObjectService(); + } + + // Make a new request if we haven't made one, or if the + // object has been modified. + if (!this.lastPromise || this.lastModified !== model.modified) { + ids = model.composition || []; + this.lastModified = model.modified; + // Load from the underlying object service + this.lastPromise = this.objectService.getObjects(ids) + .then(contextualize); + } + + return this.lastPromise; + }; + /** * Test to determine whether or not this capability should be exposed * by a domain object based on its model. Checks for the presence of diff --git a/platform/core/src/capabilities/ContextCapability.js b/platform/core/src/capabilities/ContextCapability.js index d94c7ed3f6..9ffaf4a5bb 100644 --- a/platform/core/src/capabilities/ContextCapability.js +++ b/platform/core/src/capabilities/ContextCapability.js @@ -38,79 +38,76 @@ define( * * @memberof platform/core * @constructor + * @implements {Capability} */ function ContextCapability(parentObject, domainObject) { - return { - /** - * Get the immediate parent of a domain object. - * - * A domain object may be contained in multiple places; its - * parent (as exposed by this capability) is the domain - * object from which this object was accessed, usually - * by way of a `composition` capability. - * - * @returns {DomainObject} the immediate parent of this - * domain object. - * @memberof platform/core.ContextCapability# - */ - getParent: function () { - return parentObject; - }, - /** - * Get an array containing the complete direct ancestry - * of this domain object, including the domain object - * itself. - * - * A domain object may be contained in multiple places; its - * parent and all ancestors (as exposed by this capability) - * serve as a record of how this specific domain object - * instance was reached. - * - * The first element in the returned array is the deepest - * ancestor; subsequent elements are progressively more - * recent ancestors, with the domain object which exposed - * the capability occupying the last element of the array. - * - * @returns {DomainObject[]} the full composition ancestry - * of the domain object which exposed this - * capability. - * @memberof platform/core.ContextCapability# - */ - getPath: function () { - var parentPath = [], - parentContext; - - if (parentObject) { - parentContext = parentObject.getCapability("context"); - parentPath = parentContext ? - parentContext.getPath() : - [parentObject]; - } - - return parentPath.concat([domainObject]); - }, - /** - * Get the deepest ancestor available for this domain object; - * equivalent to `getPath()[0]`. - * - * See notes on `getPath()` for how ancestry is defined in - * the context of this capability. - * - * @returns {DomainObject} the deepest ancestor of the domain - * object which exposed this capability. - * @memberof platform/core.ContextCapability# - */ - getRoot: function () { - var parentContext = parentObject && - parentObject.getCapability('context'); - - return parentContext ? - parentContext.getRoot() : - (parentObject || domainObject); - } - }; + this.parentObject = parentObject; + this.domainObject = domainObject; } + /** + * Get the immediate parent of a domain object. + * + * A domain object may be contained in multiple places; its + * parent (as exposed by this capability) is the domain + * object from which this object was accessed, usually + * by way of a `composition` capability. + * + * @returns {DomainObject} the immediate parent of this + * domain object. + */ + ContextCapability.prototype.getParent = function () { + return this.parentObject; + }; + + /** + * Get an array containing the complete direct ancestry + * of this domain object, including the domain object + * itself. + * + * A domain object may be contained in multiple places; its + * parent and all ancestors (as exposed by this capability) + * serve as a record of how this specific domain object + * instance was reached. + * + * The first element in the returned array is the deepest + * ancestor; subsequent elements are progressively more + * recent ancestors, with the domain object which exposed + * the capability occupying the last element of the array. + * + * @returns {DomainObject[]} the full composition ancestry + * of the domain object which exposed this + * capability. + */ + ContextCapability.prototype.getPath = function () { + var parentObject = this.parentObject, + parentContext = + parentObject && parentObject.getCapability('context'), + parentPath = parentContext ? + parentContext.getPath() : [ this.parentObject ]; + + return parentPath.concat([this.domainObject]); + }; + + /** + * Get the deepest ancestor available for this domain object; + * equivalent to `getPath()[0]`. + * + * See notes on `getPath()` for how ancestry is defined in + * the context of this capability. + * + * @returns {DomainObject} the deepest ancestor of the domain + * object which exposed this capability. + */ + ContextCapability.prototype.getRoot = function () { + var parentContext = this.parentObject && + this.parentObject.getCapability('context'); + + return parentContext ? + parentContext.getRoot() : + (this.parentObject || this.domainObject); + }; + return ContextCapability; } ); diff --git a/platform/core/src/capabilities/ContextualDomainObject.js b/platform/core/src/capabilities/ContextualDomainObject.js index 1475d3d384..2955515ead 100644 --- a/platform/core/src/capabilities/ContextualDomainObject.js +++ b/platform/core/src/capabilities/ContextualDomainObject.js @@ -44,6 +44,7 @@ define( * * @memberof platform/core * @constructor + * @implements {DomainObject} */ function ContextualDomainObject(domainObject, parentObject) { // Prototypally inherit from the domain object, and diff --git a/platform/core/src/capabilities/CoreCapabilityProvider.js b/platform/core/src/capabilities/CoreCapabilityProvider.js index a1148430f2..7b1ba070d7 100644 --- a/platform/core/src/capabilities/CoreCapabilityProvider.js +++ b/platform/core/src/capabilities/CoreCapabilityProvider.js @@ -29,6 +29,19 @@ define( function () { "use strict"; + /** + * A capability provides an interface with dealing with some + * dynamic behavior associated with a domain object. + * @interface Capability + */ + + /** + * Optional; if present, will be used by `DomainObject#useCapability` + * to simplify interaction with a specific capability. Parameters + * and return values vary depending on capability type. + * @method Capability#invoke + */ + /** * Provides capabilities based on extension definitions, * matched to domain object models. diff --git a/platform/core/src/capabilities/DelegationCapability.js b/platform/core/src/capabilities/DelegationCapability.js index 2259e5c420..0c62c05f00 100644 --- a/platform/core/src/capabilities/DelegationCapability.js +++ b/platform/core/src/capabilities/DelegationCapability.js @@ -45,13 +45,40 @@ define( * in the type's definition, which contains an array of names of * capabilities to be delegated. * - * @param domainObject + * @param $q Angular's $q, for promises + * @param {DomainObject} domainObject the delegating domain object * @memberof platform/core * @constructor + * @implements {Capability} */ function DelegationCapability($q, domainObject) { - var delegateCapabilities = {}, - type = domainObject.getCapability("type"); + var type = domainObject.getCapability("type"), + self = this; + + this.$q = $q; + this.delegateCapabilities = {}; + this.domainObject = domainObject; + + // Generate set for easy lookup of capability delegation + if (type && type.getDefinition) { + (type.getDefinition().delegates || []).forEach(function (key) { + self.delegateCapabilities[key] = true; + }); + } + } + + + /** + * Get the domain objects which are intended to be delegated + * responsibility for some specific capability. + * + * @param {string} key the name of the delegated capability + * @returns {DomainObject[]} the domain objects to which + * responsibility for this capability is delegated. + * @memberof platform/core.DelegationCapability# + */ + DelegationCapability.prototype.getDelegates = function (key) { + var domainObject = this.domainObject; function filterObjectsWithCapability(capability) { return function (objects) { @@ -65,55 +92,40 @@ define( return domainObject.useCapability('composition'); } - function doesDelegate(key) { - return delegateCapabilities[key] || false; - } + return this.doesDelegateCapability(key) ? + promiseChildren().then( + filterObjectsWithCapability(key) + ) : + this.$q.when([]); + }; - function getDelegates(capability) { - return doesDelegate(capability) ? - promiseChildren().then( - filterObjectsWithCapability(capability) - ) : - $q.when([]); - } - - // Generate set for easy lookup of capability delegation - if (type && type.getDefinition) { - (type.getDefinition().delegates || []).forEach(function (key) { - delegateCapabilities[key] = true; - }); - } - - return { - /** - * Invoke this capability; alias of `getDelegates`, used to - * simplify usage, e.g.: - * - * `domainObject.useCapability("delegation", "telemetry")` - * - * ...will retrieve all members of a domain object's - * composition which have a "telemetry" capability. - * - * @param {string} the name of the delegated capability - * @returns {DomainObject[]} the domain objects to which - * responsibility for this capability is delegated. - * @memberof platform/core.DelegationCapability# - */ - invoke: getDelegates, - /** - * Get the domain objects which are intended to be delegated - * responsibility for some specific capability. - * - * @param {string} the name of the delegated capability - * @returns {DomainObject[]} the domain objects to which - * responsibility for this capability is delegated. - * @memberof platform/core.DelegationCapability# - */ - getDelegates: getDelegates, - doesDelegateCapability: doesDelegate - }; - } + /** + * Check if the domain object which exposed this capability + * wishes to delegate another capability. + * + * @param {string} key the capability to check for + * @returns {boolean} true if the capability is delegated + */ + DelegationCapability.prototype.doesDelegateCapability = function (key) { + return !!(this.delegateCapabilities[key]); + }; + /** + * Invoke this capability; alias of `getDelegates`, used to + * simplify usage, e.g.: + * + * `domainObject.useCapability("delegation", "telemetry")` + * + * ...will retrieve all members of a domain object's + * composition which have a "telemetry" capability. + * + * @param {string} the name of the delegated capability + * @returns {DomainObject[]} the domain objects to which + * responsibility for this capability is delegated. + * @memberof platform/core.DelegationCapability# + */ + DelegationCapability.prototype.invoke = + DelegationCapability.prototype.getDelegates; return DelegationCapability; diff --git a/platform/core/src/capabilities/MetadataCapability.js b/platform/core/src/capabilities/MetadataCapability.js index 027c9ffe1d..242b35b6dc 100644 --- a/platform/core/src/capabilities/MetadataCapability.js +++ b/platform/core/src/capabilities/MetadataCapability.js @@ -11,8 +11,6 @@ define( * @property {string} name the human-readable name of this property * @property {string} value the human-readable value of this property, * for this specific domain object - * @constructor - * @memberof platform/core */ var TIME_FORMAT = "YYYY-MM-DD HH:mm:ss"; @@ -27,10 +25,23 @@ define( * `value` properties describing that domain object (suitable for * display.) * + * @param {DomainObject} domainObject the domain object whose + * metadata is to be exposed + * @implements {Capability} * @constructor + * @memberof platform/core */ function MetadataCapability(domainObject) { - var model = domainObject.getModel(); + this.domainObject = domainObject; + } + + /** + * Get metadata about this object. + * @returns {MetadataProperty[]} metadata about this object + */ + MetadataCapability.prototype.invoke = function () { + var domainObject = this.domainObject, + model = domainObject.getModel(); function hasDisplayableValue(metadataProperty) { var t = typeof metadataProperty.value; @@ -39,8 +50,8 @@ define( function formatTimestamp(timestamp) { return typeof timestamp === 'number' ? - (moment.utc(timestamp).format(TIME_FORMAT) + " UTC") : - undefined; + (moment.utc(timestamp).format(TIME_FORMAT) + " UTC") : + undefined; } function getProperties() { @@ -75,20 +86,9 @@ define( ]; } - function getMetadata() { - return getProperties().concat(getCommonMetadata()) - .filter(hasDisplayableValue); - } - - return { - /** - * Get metadata about this object. - * @returns {MetadataProperty[]} metadata about this object - * @memberof platform/core.MetadataCapability# - */ - invoke: getMetadata - }; - } + return getProperties().concat(getCommonMetadata()) + .filter(hasDisplayableValue); + }; return MetadataCapability; } diff --git a/platform/core/src/capabilities/MutationCapability.js b/platform/core/src/capabilities/MutationCapability.js index 8ba11be13b..b9f49ca969 100644 --- a/platform/core/src/capabilities/MutationCapability.js +++ b/platform/core/src/capabilities/MutationCapability.js @@ -69,100 +69,103 @@ define( * }); * ``` * + * @param {Function} topic a service for creating listeners + * @param {Function} now a service to get the current time * @param {DomainObject} domainObject the domain object * which will expose this capability * @memberof platform/core * @constructor + * @implements {Capability} */ function MutationCapability(topic, now, domainObject) { - var t = topic(TOPIC_PREFIX + domainObject.getId()); + this.mutationTopic = topic(TOPIC_PREFIX + domainObject.getId()); + this.now = now; + this.domainObject = domainObject; + } - function mutate(mutator, timestamp) { - // Get the object's model and clone it, so the - // mutator function has a temporary copy to work with. - var model = domainObject.getModel(), - clone = JSON.parse(JSON.stringify(model)), - useTimestamp = arguments.length > 1; + /** + * Modify the domain object's model, using a provided + * function. This function will receive a copy of the + * domain object's model as an argument; behavior + * varies depending on that function's return value: + * + * * If no value (or undefined) is returned by the mutator, + * the state of the model object delivered as the mutator's + * argument will become the domain object's new model. + * This is useful for writing code that modifies the model + * directly. + * * If a plain object is returned, that object will be used + * as the domain object's new model. + * * If boolean `false` is returned, the mutation will be + * cancelled. + * * If a promise is returned, its resolved value will be + * handled as one of the above. + * + * + * @param {Function} mutator the function which will make + * changes to the domain object's model. + * @param {number} [timestamp] timestamp to record for + * this mutation (otherwise, system time will be + * used) + * @returns {Promise.} a promise for the result + * of the mutation; true if changes were made. + */ + MutationCapability.prototype.mutate = function (mutator, timestamp) { + // Get the object's model and clone it, so the + // mutator function has a temporary copy to work with. + var domainObject = this.domainObject, + now = this.now, + t = this.mutationTopic, + model = domainObject.getModel(), + clone = JSON.parse(JSON.stringify(model)), + useTimestamp = arguments.length > 1; - // Function to handle copying values to the actual - function handleMutation(mutationResult) { - // If mutation result was undefined, just use - // the clone; this allows the mutator to omit return - // values and just change the model directly. - var result = mutationResult || clone; + // Function to handle copying values to the actual + function handleMutation(mutationResult) { + // If mutation result was undefined, just use + // the clone; this allows the mutator to omit return + // values and just change the model directly. + var result = mutationResult || clone; - // Allow mutators to change their mind by - // returning false. - if (mutationResult !== false) { - // Copy values if result was a different object - // (either our clone or some other new thing) - if (model !== result) { - copyValues(model, result); - } - model.modified = useTimestamp ? timestamp : now(); - t.notify(model); + // Allow mutators to change their mind by + // returning false. + if (mutationResult !== false) { + // Copy values if result was a different object + // (either our clone or some other new thing) + if (model !== result) { + copyValues(model, result); } - - // Report the result of the mutation - return mutationResult !== false; + model.modified = useTimestamp ? timestamp : now(); + t.notify(model); } - // Invoke the provided mutator, then make changes to - // the underlying model (if applicable.) - return fastPromise(mutator(clone)).then(handleMutation); + // Report the result of the mutation + return mutationResult !== false; } - function listen(listener) { - return t.listen(listener); - } + // Invoke the provided mutator, then make changes to + // the underlying model (if applicable.) + return fastPromise(mutator(clone)).then(handleMutation); + }; - return { - /** - * Alias of `mutate`, used to support useCapability. - * @memberof platform/core.MutationCapability# - */ - invoke: mutate, - /** - * Modify the domain object's model, using a provided - * function. This function will receive a copy of the - * domain object's model as an argument; behavior - * varies depending on that function's return value: - * - * * If no value (or undefined) is returned by the mutator, - * the state of the model object delivered as the mutator's - * argument will become the domain object's new model. - * This is useful for writing code that modifies the model - * directly. - * * If a plain object is returned, that object will be used - * as the domain object's new model. - * * If boolean `false` is returned, the mutation will be - * cancelled. - * * If a promise is returned, its resolved value will be - * handled as one of the above. - * - * - * @param {function} mutator the function which will make - * changes to the domain object's model. - * @param {number} [timestamp] timestamp to record for - * this mutation (otherwise, system time will be - * used) - * @returns {Promise.} a promise for the result - * of the mutation; true if changes were made. - * @memberof platform/core.MutationCapability# - */ - mutate: mutate, - /** - * Listen for mutations of this domain object's model. - * The provided listener will be invoked with the domain - * object's new model after any changes. To stop listening, - * invoke the function returned by this method. - * @param {Function} listener function to call on mutation - * @returns {Function} a function to stop listening - * @memberof platform/core.MutationCapability# - */ - listen: listen - }; - } + /** + * Listen for mutations of this domain object's model. + * The provided listener will be invoked with the domain + * object's new model after any changes. To stop listening, + * invoke the function returned by this method. + * @param {Function} listener function to call on mutation + * @returns {Function} a function to stop listening + * @memberof platform/core.MutationCapability# + */ + MutationCapability.prototype.listen = function (listener) { + return this.mutationTopic.listen(listener); + }; + + /** + * Alias of `mutate`, used to support useCapability. + */ + MutationCapability.prototype.invoke = + MutationCapability.prototype.mutate; return MutationCapability; } diff --git a/platform/core/src/capabilities/PersistenceCapability.js b/platform/core/src/capabilities/PersistenceCapability.js index de18b69222..8c7e08e17d 100644 --- a/platform/core/src/capabilities/PersistenceCapability.js +++ b/platform/core/src/capabilities/PersistenceCapability.js @@ -33,7 +33,7 @@ define( * * @param {PersistenceService} persistenceService the underlying * provider of persistence capabilities. - * @param {string} SPACE the name of the persistence space to + * @param {string} space the name of the persistence space to * use (this is an arbitrary string, useful in principle * for distinguishing different persistence stores from * one another.) @@ -42,10 +42,60 @@ define( * * @memberof platform/core * @constructor + * @implements {Capability} */ - function PersistenceCapability(persistenceService, SPACE, domainObject) { + function PersistenceCapability(persistenceService, space, domainObject) { // Cache modified timestamp - var modified = domainObject.getModel().modified; + this.modified = domainObject.getModel().modified; + + this.domainObject = domainObject; + this.space = space; + this.persistenceService = persistenceService; + } + + // Utility function for creating promise-like objects which + // resolve synchronously when possible + function fastPromise(value) { + return (value || {}).then ? value : { + then: function (callback) { + return fastPromise(callback(value)); + } + }; + } + + /** + * Persist any changes which have been made to this + * domain object's model. + * @returns {Promise} a promise which will be resolved + * if persistence is successful, and rejected + * if not. + */ + PersistenceCapability.prototype.persist = function () { + var domainObject = this.domainObject, + modified = domainObject.getModel().modified; + + // Update persistence timestamp... + domainObject.useCapability("mutation", function (model) { + model.persisted = modified; + }, modified); + + // ...and persist + return this.persistenceService.updateObject( + this.getSpace(), + domainObject.getId(), + domainObject.getModel() + ); + }; + + /** + * Update this domain object to match the latest from + * persistence. + * @returns {Promise} a promise which will be resolved + * when the update is complete + */ + PersistenceCapability.prototype.refresh = function () { + var domainObject = this.domainObject, + model = domainObject.getModel(); // Update a domain object's model upon refresh function updateModel(model) { @@ -55,75 +105,28 @@ define( }, modified); } - // For refresh; update a domain object model, only if there - // are no unsaved changes. - function updatePersistenceTimestamp() { - var modified = domainObject.getModel().modified; - domainObject.useCapability("mutation", function (model) { - model.persisted = modified; - }, modified); - } + // Only update if we don't have unsaved changes + return (model.modified === model.persisted) ? + this.persistenceService.readObject( + this.getSpace(), + this.domainObject.getId() + ).then(updateModel) : + fastPromise(false); + }; - // Utility function for creating promise-like objects which - // resolve synchronously when possible - function fastPromise(value) { - return (value || {}).then ? value : { - then: function (callback) { - return fastPromise(callback(value)); - } - }; - } - - return { - /** - * Persist any changes which have been made to this - * domain object's model. - * @returns {Promise} a promise which will be resolved - * if persistence is successful, and rejected - * if not. - * @memberof platform/core.PersistenceCapability# - */ - persist: function () { - updatePersistenceTimestamp(); - return persistenceService.updateObject( - SPACE, - domainObject.getId(), - domainObject.getModel() - ); - }, - /** - * Update this domain object to match the latest from - * persistence. - * @returns {Promise} a promise which will be resolved - * when the update is complete - * @memberof platform/core.PersistenceCapability# - */ - refresh: function () { - var model = domainObject.getModel(); - // Only update if we don't have unsaved changes - return (model.modified === model.persisted) ? - persistenceService.readObject( - SPACE, - domainObject.getId() - ).then(updateModel) : - fastPromise(false); - }, - /** - * Get the space in which this domain object is persisted; - * this is useful when, for example, decided which space a - * newly-created domain object should be persisted to (by - * default, this should be the space of its containing - * object.) - * - * @returns {string} the name of the space which should - * be used to persist this object - * @memberof platform/core.PersistenceCapability# - */ - getSpace: function () { - return SPACE; - } - }; - } + /** + * Get the space in which this domain object is persisted; + * this is useful when, for example, decided which space a + * newly-created domain object should be persisted to (by + * default, this should be the space of its containing + * object.) + * + * @returns {string} the name of the space which should + * be used to persist this object + */ + PersistenceCapability.prototype.getSpace = function () { + return this.space; + }; return PersistenceCapability; } diff --git a/platform/core/src/capabilities/RelationshipCapability.js b/platform/core/src/capabilities/RelationshipCapability.js index 3bbc210b36..7eb6d01bb9 100644 --- a/platform/core/src/capabilities/RelationshipCapability.js +++ b/platform/core/src/capabilities/RelationshipCapability.js @@ -40,92 +40,80 @@ define( * * @memberof platform/core * @constructor + * @implements {Capability} */ function RelationshipCapability($injector, domainObject) { - var objectService, - lastPromise = {}, - lastModified; - // Get a reference to the object service from $injector - function injectObjectService() { - objectService = $injector.get("objectService"); - return objectService; - } - - // Get a reference to the object service (either cached or - // from the injector) - function getObjectService() { - return objectService || injectObjectService(); - } - - // Promise this domain object's composition (an array of domain - // object instances corresponding to ids in its model.) - function promiseRelationships(key) { - var model = domainObject.getModel(), - ids; - - // Package objects as an array - function packageObject(objects) { - return ids.map(function (id) { - return objects[id]; - }).filter(function (obj) { - return obj; - }); - } - - // Clear cached promises if modification has occurred - if (lastModified !== model.modified) { - lastPromise = {}; - lastModified = model.modified; - } - - // Make a new request if needed - if (!lastPromise[key]) { - ids = (model.relationships || {})[key] || []; - lastModified = model.modified; - // Load from the underlying object service - lastPromise[key] = getObjectService().getObjects(ids) - .then(packageObject); - } - - return lastPromise[key]; - } - - // List types of relationships which this object has - function listRelationships() { - var relationships = - (domainObject.getModel() || {}).relationships || {}; - - // Check if this key really does expose an array of ids - // (to filter out malformed relationships) - function isArray(key) { - return Array.isArray(relationships[key]); - } - - return Object.keys(relationships).filter(isArray).sort(); - } - - return { - /** - * List all types of relationships exposed by this - * object. - * @returns {string[]} a list of all relationship types - * @memberof platform/core.RelationshipCapability# - */ - listRelationships: listRelationships, - /** - * Request related objects, with a given relationship type. - * This will typically require asynchronous lookup, so this - * returns a promise. - * @param {string} key the type of relationship - * @returns {Promise.} a promise for related - * domain objects - * @memberof platform/core.RelationshipCapability# - */ - getRelatedObjects: promiseRelationships + this.injectObjectService = function () { + this.objectService = $injector.get("objectService"); }; + + this.lastPromise = {}; + this.domainObject = domainObject; } + /** + * List all types of relationships exposed by this + * object. + * @returns {string[]} a list of all relationship types + */ + RelationshipCapability.prototype.listRelationships = function listRelationships() { + var relationships = + (this.domainObject.getModel() || {}).relationships || {}; + + // Check if this key really does expose an array of ids + // (to filter out malformed relationships) + function isArray(key) { + return Array.isArray(relationships[key]); + } + + return Object.keys(relationships).filter(isArray).sort(); + }; + + /** + * Request related objects, with a given relationship type. + * This will typically require asynchronous lookup, so this + * returns a promise. + * @param {string} key the type of relationship + * @returns {Promise.} a promise for related + * domain objects + */ + RelationshipCapability.prototype.getRelatedObjects = function (key) { + var model = this.domainObject.getModel(), + ids; + + // Package objects as an array + function packageObject(objects) { + return ids.map(function (id) { + return objects[id]; + }).filter(function (obj) { + return obj; + }); + } + + // Clear cached promises if modification has occurred + if (this.lastModified !== model.modified) { + this.lastPromise = {}; + this.lastModified = model.modified; + } + + // Make a new request if needed + if (!this.lastPromise[key]) { + ids = (model.relationships || {})[key] || []; + this.lastModified = model.modified; + // Lazily initialize object service now that we need it + if (!this.objectService) { + this.injectObjectService(); + } + // Load from the underlying object service + this.lastPromise[key] = this.objectService.getObjects(ids) + .then(packageObject); + } + + return this.lastPromise[key]; + }; + + /** * Test to determine whether or not this capability should be exposed * by a domain object based on its model. Checks for the presence of diff --git a/platform/core/src/models/CachingModelDecorator.js b/platform/core/src/models/CachingModelDecorator.js index c74cea05f0..a338d6770f 100644 --- a/platform/core/src/models/CachingModelDecorator.js +++ b/platform/core/src/models/CachingModelDecorator.js @@ -32,10 +32,30 @@ define( * object are not provided. * @memberof platform/core * @constructor + * @param {ModelService} modelService this service to decorate + * @implements {ModelService} */ function CachingModelDecorator(modelService) { - var cache = {}, - cached = {}; + this.cache = {}; + this.cached = {}; + this.modelService = modelService; + } + + // Fast-resolving promise + function fastPromise(value) { + return (value || {}).then ? value : { + then: function (callback) { + return fastPromise(callback(value)); + } + }; + } + + CachingModelDecorator.prototype.getModels = function (ids) { + var cache = this.cache, + cached = this.cached, + neededIds = ids.filter(function notCached(id) { + return !cached[id]; + }); // Update the cached instance of a model to a new value. // We update in-place to ensure there is only ever one instance @@ -68,30 +88,12 @@ define( return oldModel; } - // Fast-resolving promise - function fastPromise(value) { - return (value || {}).then ? value : { - then: function (callback) { - return fastPromise(callback(value)); - } - }; - } - - // Store this model in the cache - function cacheModel(id, model) { - cache[id] = cached[id] ? updateModel(id, model) : model; - cached[id] = true; - } - - // Check if an id is not in cache, for lookup filtering - function notCached(id) { - return !cached[id]; - } - // Store the provided models in our cache function cacheAll(models) { Object.keys(models).forEach(function (id) { - cacheModel(id, models[id]); + cache[id] = cached[id] ? + updateModel(id, models[id]) : models[id]; + cached[id] = true; }); } @@ -100,38 +102,16 @@ define( return cache; } - return { - /** - * Get models for these specified string identifiers. - * These will be given as an object containing keys - * and values, where keys are object identifiers and - * values are models. - * This result may contain either a subset or a - * superset of the total objects. - * - * @param {Array} ids the string identifiers for - * models of interest. - * @returns {Promise} a promise for an object - * containing key-value pairs, where keys are - * ids and values are models - * @method - * @memberof platform/core.CachingModelDecorator# - */ - getModels: function (ids) { - var neededIds = ids.filter(notCached); + // Look up if we have unknown IDs + if (neededIds.length > 0) { + return this.modelService.getModels(neededIds) + .then(cacheAll) + .then(giveCache); + } - // Look up if we have unknown IDs - if (neededIds.length > 0) { - return modelService.getModels(neededIds) - .then(cacheAll) - .then(giveCache); - } - - // Otherwise, just expose the cache directly - return fastPromise(cache); - } - }; - } + // Otherwise, just expose the cache directly + return fastPromise(cache); + }; return CachingModelDecorator; } diff --git a/platform/core/src/models/MissingModelDecorator.js b/platform/core/src/models/MissingModelDecorator.js index fca716733e..d3eb8f3159 100644 --- a/platform/core/src/models/MissingModelDecorator.js +++ b/platform/core/src/models/MissingModelDecorator.js @@ -29,33 +29,34 @@ define( /** * Adds placeholder domain object models for any models which * fail to load from the underlying model service. - * @implements {ModelService} * @constructor * @memberof platform/core + * @param {ModelService} modelService this service to decorate + * @implements {ModelService} */ function MissingModelDecorator(modelService) { - function missingModel(id) { - return { - type: "unknown", - name: "Missing: " + id - }; - } + this.modelService = modelService; + } + function missingModel(id) { return { - getModels: function (ids) { - function addMissingModels(models) { - var result = {}; - ids.forEach(function (id) { - result[id] = models[id] || missingModel(id); - }); - return result; - } - - return modelService.getModels(ids).then(addMissingModels); - } + type: "unknown", + name: "Missing: " + id }; } + MissingModelDecorator.prototype.getModels = function (ids) { + function addMissingModels(models) { + var result = {}; + ids.forEach(function (id) { + result[id] = models[id] || missingModel(id); + }); + return result; + } + + return this.modelService.getModels(ids).then(addMissingModels); + }; + return MissingModelDecorator; } ); diff --git a/platform/core/src/models/ModelAggregator.js b/platform/core/src/models/ModelAggregator.js index 437bba4ec3..d5f4060415 100644 --- a/platform/core/src/models/ModelAggregator.js +++ b/platform/core/src/models/ModelAggregator.js @@ -29,67 +29,71 @@ define( function () { "use strict"; + /** + * Allow domain object models to be looked up by their identifiers. + * + * @interface ModelService + */ + + /** + * Get domain object models. + * + * This may provide either a superset or a subset of the models + * requested. Absence of a model means it does not exist within + * this service instance. + * + * @method ModelService#getModels + * @param {string[]} ids identifiers for models desired. + * @returns {Promise.} a promise for an object mapping + * string identifiers to domain object models. + */ + /** * Allows multiple services which provide models for domain objects * to be treated as one. * * @memberof platform/core * @constructor - * @param {ModelProvider[]} providers the model providers to be + * @implements {ModelService} + * @param $q Angular's $q, for promises + * @param {ModelService[]} providers the model providers to be * aggregated */ function ModelAggregator($q, providers) { - - // Pick a domain object model to use, favoring the one - // with the most recent timestamp - function pick(a, b) { - var aModified = (a || {}).modified || Number.NEGATIVE_INFINITY, - bModified = (b || {}).modified || Number.NEGATIVE_INFINITY; - return (aModified > bModified) ? a : (b || a); - } - - // Merge results from multiple providers into one - // large result object. - function mergeModels(provided, ids) { - var result = {}; - ids.forEach(function (id) { - provided.forEach(function (models) { - if (models[id]) { - result[id] = pick(result[id], models[id]); - } - }); - }); - return result; - } - - return { - /** - * Get models with the specified identifiers. - * - * This will invoke the `getModels()` method of all providers - * given at constructor-time, and aggregate the result into - * one object. - * - * Note that the returned object may contain a subset or a - * superset of the models requested. - * - * @param {string[]} ids an array of domain object identifiers - * @returns {Promise.} a promise for an object - * containing key-value pairs, - * where keys are object identifiers and values - * are object models. - * @memberof platform/core.ModelAggregator# - */ - getModels: function (ids) { - return $q.all(providers.map(function (provider) { - return provider.getModels(ids); - })).then(function (provided) { - return mergeModels(provided, ids); - }); - } - }; + this.providers = providers; + this.$q = $q; } + // Pick a domain object model to use, favoring the one + // with the most recent timestamp + function pick(a, b) { + var aModified = (a || {}).modified || Number.NEGATIVE_INFINITY, + bModified = (b || {}).modified || Number.NEGATIVE_INFINITY; + return (aModified > bModified) ? a : (b || a); + } + + // Merge results from multiple providers into one + // large result object. + function mergeModels(provided, ids) { + var result = {}; + ids.forEach(function (id) { + provided.forEach(function (models) { + if (models[id]) { + result[id] = pick(result[id], models[id]); + } + }); + }); + return result; + } + + ModelAggregator.prototype.getModels = function (ids) { + return this.$q.all(this.providers.map(function (provider) { + return provider.getModels(ids); + })).then(function (provided) { + return mergeModels(provided, ids); + }); + }; + return ModelAggregator; } ); diff --git a/platform/core/src/models/PersistedModelProvider.js b/platform/core/src/models/PersistedModelProvider.js index 311f40eeaa..59ab020b14 100644 --- a/platform/core/src/models/PersistedModelProvider.js +++ b/platform/core/src/models/PersistedModelProvider.js @@ -35,61 +35,47 @@ define( * * @memberof platform/core * @constructor + * @implements {ModelService} * @param {PersistenceService} persistenceService the service in which * domain object models are persisted. * @param $q Angular's $q service, for working with promises * @param {string} SPACE the name of the persistence space from which * models should be retrieved. */ - function PersistedModelProvider(persistenceService, $q, SPACE) { - // Load a single object model from persistence - function loadModel(id) { - return persistenceService.readObject(SPACE, id); - } - - // Promise all persisted models (in id->model form) - function promiseModels(ids) { - // Package the result as id->model - function packageResult(models) { - var result = {}; - ids.forEach(function (id, index) { - result[id] = models[index]; - }); - return result; - } - - // Filter out "namespaced" identifiers; these are - // not expected to be found in database. See WTD-659. - ids = ids.filter(function (id) { - return id.indexOf(":") === -1; - }); - - // Give a promise for all persistence lookups... - return $q.all(ids.map(loadModel)).then(packageResult); - } - - return { - /** - * Get models with the specified identifiers. - * - * This will invoke the underlying persistence service to - * retrieve object models which match the provided - * identifiers. - * - * Note that the returned object may contain a subset or a - * superset of the models requested. - * - * @param {string[]} ids an array of domain object identifiers - * @returns {Promise.} a promise for an object - * containing key-value pairs, - * where keys are object identifiers and values - * are object models. - * @memberof platform/core.PersistedModelProvider# - */ - getModels: promiseModels - }; + function PersistedModelProvider(persistenceService, $q, space) { + this.persistenceService = persistenceService; + this.$q = $q; + this.space = space; } + PersistedModelProvider.prototype.getModels = function (ids) { + var persistenceService = this.persistenceService, + $q = this.$q, + space = this.space; + + // Load a single object model from persistence + function loadModel(id) { + return persistenceService.readObject(space, id); + } + + // Package the result as id->model + function packageResult(models) { + var result = {}; + ids.forEach(function (id, index) { + result[id] = models[index]; + }); + return result; + } + + // Filter out "namespaced" identifiers; these are + // not expected to be found in database. See WTD-659. + ids = ids.filter(function (id) { + return id.indexOf(":") === -1; + }); + + // Give a promise for all persistence lookups... + return $q.all(ids.map(loadModel)).then(packageResult); + }; return PersistedModelProvider; } diff --git a/platform/core/src/models/RootModelProvider.js b/platform/core/src/models/RootModelProvider.js index bf819d51ae..f218504b64 100644 --- a/platform/core/src/models/RootModelProvider.js +++ b/platform/core/src/models/RootModelProvider.js @@ -41,42 +41,31 @@ define( * * @memberof platform/core * @constructor + * @implements {ModelService} + * @param {Array} roots all `roots[]` extensions + * @param $q Angular's $q, for promises + * @param $log Anuglar's $log, for logging */ function RootModelProvider(roots, $q, $log) { // Pull out identifiers to used as ROOT's - var ids = roots.map(function (root) { return root.id; }), - baseProvider = new StaticModelProvider(roots, $q, $log); + var ids = roots.map(function (root) { return root.id; }); - function addRoot(models) { - models.ROOT = { - name: "The root object", - type: "root", - composition: ids - }; - return models; - } - - return { - - /** - * Get models with the specified identifiers. - * - * Note that the returned object may contain a subset or a - * superset of the models requested. - * - * @param {string[]} ids an array of domain object identifiers - * @returns {Promise.} a promise for an object - * containing key-value pairs, - * where keys are object identifiers and values - * are object models. - * @memberof platform/core.RootModelProvider# - */ - getModels: function (ids) { - return baseProvider.getModels(ids).then(addRoot); - } + this.baseProvider = new StaticModelProvider(roots, $q, $log); + this.rootModel = { + name: "The root object", + type: "root", + composition: ids }; } + RootModelProvider.prototype.getModels = function (ids) { + var rootModel = this.rootModel; + return this.baseProvider.getModels(ids).then(function (models) { + models.ROOT = rootModel; + return models; + }); + }; + return RootModelProvider; } ); diff --git a/platform/core/src/models/StaticModelProvider.js b/platform/core/src/models/StaticModelProvider.js index 68b76d6b43..ea5846b07a 100644 --- a/platform/core/src/models/StaticModelProvider.js +++ b/platform/core/src/models/StaticModelProvider.js @@ -41,7 +41,7 @@ define( // Skip models which don't look right if (typeof model !== 'object' || typeof model.id !== 'string' || - typeof model.model !== 'object') { + typeof model.model !== 'object') { $log.warn([ "Skipping malformed domain object model exposed by ", ((model || {}).bundle || {}).path @@ -54,34 +54,19 @@ define( // Prepoulate maps with models to make subsequent lookup faster. models.forEach(addModelToMap); - return { - /** - * Get models for these specified string identifiers. - * These will be given as an object containing keys - * and values, where keys are object identifiers and - * values are models. - * This result may contain either a subset or a - * superset of the total objects. - * - * @param {Array} ids the string identifiers for - * models of interest. - * @returns {Promise} a promise for an object - * containing key-value pairs, where keys are - * ids and values are models - * @method - * @memberof StaticModelProvider# - * @memberof platform/core.StaticModelProvider# - */ - getModels: function (ids) { - var result = {}; - ids.forEach(function (id) { - result[id] = modelMap[id]; - }); - return $q.when(result); - } - }; + this.modelMap = modelMap; + this.$q = $q; } + StaticModelProvider.prototype.getModels = function (ids) { + var modelMap = this.modelMap, + result = {}; + ids.forEach(function (id) { + result[id] = modelMap[id]; + }); + return this.$q.when(result); + }; + return StaticModelProvider; } ); diff --git a/platform/core/src/objects/DomainObject.js b/platform/core/src/objects/DomainObject.js deleted file mode 100644 index c36e5db516..0000000000 --- a/platform/core/src/objects/DomainObject.js +++ /dev/null @@ -1,134 +0,0 @@ -/***************************************************************************** - * Open MCT Web, Copyright (c) 2014-2015, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT Web is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT Web includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ -/*global define,Promise*/ - -/** - * Module defining DomainObject. Created by vwoeltje on 11/7/14. - */ -define( - [], - function () { - "use strict"; - - /** - * Construct a new domain object with the specified - * identifier, model, and capabilities. - * - * @param {string} id the object's unique identifier - * @param {object} model the "JSONifiable" state of the object - * @param {Object.|function} capabilities all - * capabilities to be exposed by this object - * @memberof platform/core - * @constructor - */ - function DomainObject(id, model, capabilities) { - return { - /** - * Get the unique identifier for this domain object. - * @return {string} the domain object's unique identifier - * @memberof DomainObject# - * @memberof platform/core.DomainObject# - */ - getId: function () { - return id; - }, - - /** - * Get the domain object's model. This is useful to - * directly look up known properties of an object, but - * direct modification of a returned model is generally - * discouraged and may result in errors. Instead, an - * object's "mutation" capability should be used. - * - * @return {object} the domain object's persistent state - * @memberof DomainObject# - * @memberof platform/core.DomainObject# - */ - getModel: function () { - return model; - }, - - /** - * Get a capability associated with this object. - * Capabilities are looked up by string identifiers; - * prior knowledge of a capability's interface is - * necessary. - * - * @return {Capability} the named capability, or undefined - * if not present. - * @memberof DomainObject# - * @memberof platform/core.DomainObject# - */ - getCapability: function (name) { - var capability = capabilities[name]; - return typeof capability === 'function' ? - capability(this) : capability; - }, - - /**g - * Check if this domain object supports a capability - * with the provided name. - * - * @param {string} name the name of the capability to - * check for - * @returns {boolean} true if provided - * @memberof platform/core.DomainObject# - */ - hasCapability: function hasCapability(name) { - return this.getCapability(name) !== undefined; - }, - - /** - * Use a capability of an object; this is a shorthand - * for: - * - * ``` - * hasCapability(name) ? - * getCapability(name).invoke(args...) : - * undefined - * ``` - * - * That is, it handles both the check-for-existence and - * invocation of the capability, and checks for existence - * before invoking the capability. - * - * @param {string} name the name of the capability to invoke - * @param {...*} [arguments] to pass to the invocation - * @returns {*} - * @memberof DomainObject# - * @memberof platform/core.DomainObject# - */ - useCapability: function (name) { - // Get tail of args to pass to invoke - var args = Array.prototype.slice.apply(arguments, [1]), - capability = this.getCapability(name); - - return (capability && capability.invoke) ? - capability.invoke.apply(capability, args) : - capability; - } - }; - } - - return DomainObject; - } -); diff --git a/platform/core/src/objects/DomainObjectImpl.js b/platform/core/src/objects/DomainObjectImpl.js new file mode 100644 index 0000000000..5c2c270a23 --- /dev/null +++ b/platform/core/src/objects/DomainObjectImpl.js @@ -0,0 +1,143 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define,Promise*/ + +/** + * Module defining DomainObject. Created by vwoeltje on 11/7/14. + */ +define( + [], + function () { + "use strict"; + + /** + * A domain object is an entity of interest to the user. + * + * @interface DomainObject + */ + + /** + * Get the unique identifier for this domain object. + * + * @method DomainObject#getId + * @return {string} the domain object's unique identifier + */ + + /** + * Get the domain object's model. This is useful to + * directly look up known properties of an object, but + * direct modification of a returned model is generally + * discouraged and may result in errors. Instead, an + * object's `mutation` capability should be used. + * + * @method DomainObject#getModel + * @return {object} the domain object's persistent state + */ + + /** + * Get a capability associated with this object. + * Capabilities are looked up by string identifiers; + * prior knowledge of a capability's interface is + * necessary. + * + * @method DomainObject#getCapability + * @param {string} key the identifier for the capability + * @return {Capability} the named capability, or undefined + * if not present. + */ + + /** + * Check if this domain object supports a capability + * with the provided name. + * + * @method DomainObject#hasCapability + * @param {string} key the identifier for the capability + * @return {boolean} true if this domain object has this capability + */ + + /** + * Use a capability of an object; the behavior of this method + * depends on the interface of the capability, and whether + * or not it is present. + * + * * If the capability is not present for this object, + * no operation occurs. + * * If the capability is present and has an `invoke` method, + * that method is called with any additional arguments + * provided, and its return value is returned. + * * If the capability is present but has no `invoke` method, + * this capability itself is returned. + * + * @method DomainObject#useCapability + * @param {string} name the name of the capability to invoke + * @param {...*} [arguments] to pass to the invocation + * @returns {*|Capability} the result of invocation (see description) + */ + + /** + * Construct a new domain object with the specified + * identifier, model, and capabilities. + * + * @param {string} id the object's unique identifier + * @param {object} model the "JSONifiable" state of the object + * @param {Object.|function} capabilities all + * capabilities to be exposed by this object + * @memberof platform/core + * @constructor + */ + function DomainObjectImpl(id, model, capabilities) { + this.id = id; + this.model = model; + this.capabilities = capabilities; + } + + DomainObjectImpl.prototype.getId = function () { + return this.id; + }; + + DomainObjectImpl.prototype.getModel = function () { + return this.model; + }; + + DomainObjectImpl.prototype.getCapability = function (name) { + var capability = this.capabilities[name]; + return typeof capability === 'function' ? + capability(this) : capability; + }; + + DomainObjectImpl.prototype.hasCapability = function (name) { + return this.getCapability(name) !== undefined; + }; + + DomainObjectImpl.prototype.useCapability = function (name) { + // Get tail of args to pass to invoke + var args = Array.prototype.slice.apply(arguments, [1]), + capability = this.getCapability(name); + + return (capability && capability.invoke) ? + capability.invoke.apply(capability, args) : + capability; + }; + + return DomainObjectImpl; + } +); diff --git a/platform/core/src/objects/DomainObjectProvider.js b/platform/core/src/objects/DomainObjectProvider.js index 732da80518..c846cbf665 100644 --- a/platform/core/src/objects/DomainObjectProvider.js +++ b/platform/core/src/objects/DomainObjectProvider.js @@ -27,10 +27,31 @@ * @namespace platform/core */ define( - ["./DomainObject"], - function (DomainObject) { + ["./DomainObjectImpl"], + function (DomainObjectImpl) { "use strict"; + /** + * Provides instances of domain objects, as retrieved by their + * identifiers. + * + * @interface ObjectService + */ + + /** + * Get a set of objects associated with a list of identifiers. + * The provided result may contain a subset or a superset of + * the total number of objects. + * + * @method ObjectService#getObjects + * @param {string[]} ids the identifiers for domain objects + * of interest. + * @return {Promise>} a promise + * for an object containing key-value pairs, where keys + * are string identifiers for domain objects, and + * values are the corresponding domain objects themselves. + */ + /** * Construct a new provider for domain objects. * @@ -44,6 +65,16 @@ define( * @constructor */ function DomainObjectProvider(modelService, capabilityService, $q) { + this.modelService = modelService; + this.capabilityService = capabilityService; + this.$q = $q; + } + + DomainObjectProvider.prototype.getObjects = function getObjects(ids) { + var modelService = this.modelService, + capabilityService = this.capabilityService, + $q = this.$q; + // Given a models object (containing key-value id-model pairs) // create a function that will look up from the capability // service based on id; for handy mapping below. @@ -51,8 +82,8 @@ define( return function (id) { var model = models[id]; return model ? - capabilityService.getCapabilities(model) : - undefined; + capabilityService.getCapabilities(model) : + undefined; }; } @@ -65,7 +96,7 @@ define( ids.forEach(function (id, index) { if (models[id]) { // Create the domain object - result[id] = new DomainObject( + result[id] = new DomainObjectImpl( id, models[id], capabilities[index] @@ -75,36 +106,14 @@ define( return result; } - // Get object instances; this is the useful API exposed by the - // domain object provider. - function getObjects(ids) { - return modelService.getModels(ids).then(function (models) { - return $q.all( - ids.map(capabilityResolver(models)) - ).then(function (capabilities) { + return modelService.getModels(ids).then(function (models) { + return $q.all( + ids.map(capabilityResolver(models)) + ).then(function (capabilities) { return assembleResult(ids, models, capabilities); }); - }); - } - - return { - /** - * Get a set of objects associated with a list of identifiers. - * The provided result may contain a subset or a superset of - * the total number of objects. - * - * @param {Array} ids the identifiers for domain objects - * of interest. - * @return {Promise>} a promise - * for an object containing key-value pairs, where keys - * are string identifiers for domain objects, and - * values are the corresponding domain objects themselves. - * @memberof module:core/object/object-provider.ObjectProvider# - * @memberof platform/core.DomainObjectProvider# - */ - getObjects: getObjects - }; - } + }); + }; return DomainObjectProvider; } diff --git a/platform/core/src/services/Now.js b/platform/core/src/services/Now.js index b799d80391..e1a639fe11 100644 --- a/platform/core/src/services/Now.js +++ b/platform/core/src/services/Now.js @@ -31,7 +31,6 @@ define( * `Date.now()` which can be injected to support testability. * * @returns {Function} a function which returns current system time - * @constructor * @memberof platform/core */ function Now() { diff --git a/platform/core/src/services/Throttle.js b/platform/core/src/services/Throttle.js index 16b258ba13..3d68988d6b 100644 --- a/platform/core/src/services/Throttle.js +++ b/platform/core/src/services/Throttle.js @@ -42,7 +42,6 @@ define( * resolve to the returned value of `fn` whenever that is invoked. * * @returns {Function} - * @constructor * @memberof platform/core */ function Throttle($timeout) { diff --git a/platform/core/src/services/Topic.js b/platform/core/src/services/Topic.js index cc57b4314c..ca38dfcde7 100644 --- a/platform/core/src/services/Topic.js +++ b/platform/core/src/services/Topic.js @@ -44,7 +44,6 @@ define( * arguments) are private; each call returns a new instance. * * @returns {Function} - * @constructor * @memberof platform/core */ function Topic() { diff --git a/platform/core/src/types/TypeCapability.js b/platform/core/src/types/TypeCapability.js index ca311aab2a..883c8dc7ab 100644 --- a/platform/core/src/types/TypeCapability.js +++ b/platform/core/src/types/TypeCapability.js @@ -36,6 +36,8 @@ define( * * @memberof platform/core * @constructor + * @augments {Type} + * @implements {Capability} * @param {TypeService} typeService the service which * provides type information * @param {DomainObject} domainObject the domain object diff --git a/platform/core/src/types/TypeImpl.js b/platform/core/src/types/TypeImpl.js index 526675e611..dbecaa2e6a 100644 --- a/platform/core/src/types/TypeImpl.js +++ b/platform/core/src/types/TypeImpl.js @@ -26,10 +26,95 @@ define( function (TypeProperty) { "use strict"; + /** + * Describes a type of domain object. + * + * @interface Type + */ + + /** + * Get the string key which identifies this type. + * This is the type's machine-readable name/identifier, + * and will correspond to the "type" field of the models + * of domain objects of this type. + * + * @returns {string} the key which identifies this type + * @method Type#getKey + */ + /** + * Get the human-readable name for this type, as should + * be displayed in the user interface when referencing + * this type. + * + * @returns {string} the human-readable name of this type + * @method Type#getName + */ + /** + * Get the human-readable description for this type, as should + * be displayed in the user interface when describing + * this type. + * + * @returns {string} the human-readable description of this type + * @method Type#getDescription + */ + /** + * Get the glyph associated with this type. Glyphs are + * single-character strings which will appear as icons (when + * displayed in an appropriate font) which visually + * distinguish types from one another. + * + * @returns {string} the glyph to be displayed + * @method Type#getGlyph + */ + /** + * Get an array of properties associated with objects of + * this type, as might be shown in a Create wizard or + * an Edit Properties view. + * + * @return {Array} properties associated with - * objects of this type - * @memberof platform/core.TypeImpl# - */ - getProperties: function () { - return (typeDef.properties || []).map(TypeProperty); - }, - /** - * Get the initial state of a model for domain objects of - * this type. - * - * @return {object} initial domain object model - * @memberof platform/core.TypeImpl# - */ - getInitialModel: function () { - return typeDef.model || {}; - }, - /** - * Get the raw type definition for this type. This is an - * object containing key-value pairs of type metadata; - * this allows the retrieval and use of custom type - * properties which are not recognized within this interface. - * - * @returns {object} the raw definition for this type - * @memberof module:core/type/type-impl.TypeImpl# - * @memberof platform/core.TypeImpl# - */ - getDefinition: function () { - return typeDef; - }, - /** - * Check if this type is or inherits from some other type. - * - * TODO: Rename, "instanceOf" is a misnomer (since there is - * no "instance", so to speak.) - * - * @param {string|module:core/type/type-implTypeImpl} key either - * a string key for a type, or an instance of a type - * object, which this - * @returns {boolean} true - * @memberof module:core/type/type-impl.TypeImpl# - * @memberof platform/core.TypeImpl# - */ - instanceOf: function instanceOf(key) { - - if (key === typeDef.key) { - return true; - } else if (inheritList.indexOf(key) > -1) { - return true; - } else if (!key) { - return true; - } else if (key !== null && typeof key === 'object') { - return key.getKey ? instanceOf(key.getKey()) : false; - } else { - return false; - } - }, - /** - * Check if a type should support a given feature. This simply - * checks for the presence or absence of the feature key in - * the type definition's "feature" field. - * @param {string} feature a string identifying the feature - * @returns {boolean} true if the feature is supported - * @memberof platform/core.TypeImpl# - */ - hasFeature: function (feature) { - return featureSet[feature] || false; - } - }; + this.typeDef = typeDef; + this.featureSet = featureSet; + this.inheritList = inheritList; } + TypeImpl.prototype.getKey = function () { + return this.typeDef.key; + }; + + TypeImpl.prototype.getName = function () { + return this.typeDef.name; + }; + + TypeImpl.prototype.getDescription = function () { + return this.typeDef.description; + }; + + TypeImpl.prototype.getGlyph = function () { + return this.typeDef.glyph; + }; + + TypeImpl.prototype.getProperties = function () { + return (this.typeDef.properties || []).map(function (propertyDef) { + return new TypeProperty(propertyDef); + }); + }; + + TypeImpl.prototype.getInitialModel = function () { + return this.typeDef.model || {}; + }; + + TypeImpl.prototype.getDefinition = function () { + return this.typeDef; + }; + + TypeImpl.prototype.instanceOf = function instanceOf(key) { + var typeDef = this.typeDef, + inheritList = this.inheritList; + + if (key === typeDef.key) { + return true; + } else if (inheritList.indexOf(key) > -1) { + return true; + } else if (!key) { + return true; + } else if (key !== null && typeof key === 'object') { + return key.getKey ? this.instanceOf(key.getKey()) : false; + } else { + return false; + } + } + + TypeImpl.prototype.hasFeature = function (feature) { + return this.featureSet[feature] || false; + }; + return TypeImpl; } ); diff --git a/platform/core/src/types/TypeProperty.js b/platform/core/src/types/TypeProperty.js index 2b1cfea0ac..70aaf8fbf8 100644 --- a/platform/core/src/types/TypeProperty.js +++ b/platform/core/src/types/TypeProperty.js @@ -35,128 +35,130 @@ define( */ function TypeProperty(propertyDefinition) { // Load an appropriate conversion - var conversion = new TypePropertyConversion( + this.conversion = new TypePropertyConversion( propertyDefinition.conversion || "identity" ); + this.propertyDefinition = propertyDefinition; + } - // Check if a value is defined; used to check if initial array - // values have been populated. - function isUnpopulatedArray(value) { - var i; + // Check if a value is defined; used to check if initial array + // values have been populated. + function isUnpopulatedArray(value) { + var i; - if (!Array.isArray(value) || value.length === 0) { - return false; - } - - for (i = 0; i < value.length; i += 1) { - if (value[i] !== undefined) { - return false; - } - } - - return true; + if (!Array.isArray(value) || value.length === 0) { + return false; } - // Perform a lookup for a value from an object, - // which may recursively look at contained objects - // based on the path provided. - function lookupValue(object, propertyPath) { - var value; - - // Can't look up from a non-object - if (!object) { - return undefined; + for (i = 0; i < value.length; i += 1) { + if (value[i] !== undefined) { + return false; } + } - // If path is not an array, just look up the property - if (!Array.isArray(propertyPath)) { - return object[propertyPath]; - } + return true; + } - // Otherwise, look up in the sequence defined in the array - if (propertyPath.length > 0) { - value = object[propertyPath[0]]; - return propertyPath.length > 1 ? - lookupValue(value, propertyPath.slice(1)) : - value; - } + // Specify a field deeply within an object + function specifyValue(object, propertyPath, value) { + // If path is not an array, just set the property + if (!Array.isArray(propertyPath)) { + object[propertyPath] = value; + } else if (propertyPath.length > 1) { + // Otherwise, look up in defined sequence + object[propertyPath[0]] = object[propertyPath[0]] || {}; + specifyValue( + object[propertyPath[0]], + propertyPath.slice(1), + value + ); + } else if (propertyPath.length === 1) { + object[propertyPath[0]] = value; + } + } - // Fallback; property path was empty + // Perform a lookup for a value from an object, + // which may recursively look at contained objects + // based on the path provided. + function lookupValue(object, propertyPath) { + var value; + + // Can't look up from a non-object + if (!object) { return undefined; } - function specifyValue(object, propertyPath, value) { - - // If path is not an array, just set the property - if (!Array.isArray(propertyPath)) { - object[propertyPath] = value; - } else if (propertyPath.length > 1) { - // Otherwise, look up in defined sequence - object[propertyPath[0]] = object[propertyPath[0]] || {}; - specifyValue( - object[propertyPath[0]], - propertyPath.slice(1), - value - ); - } else if (propertyPath.length === 1) { - object[propertyPath[0]] = value; - } - + // If path is not an array, just look up the property + if (!Array.isArray(propertyPath)) { + return object[propertyPath]; } - return { - /** - * Retrieve the value associated with this property - * from a given model. - * @memberof platform/core.TypeProperty# - */ - getValue: function (model) { - var property = propertyDefinition.property || - propertyDefinition.key, - initialValue = - property && lookupValue(model, property); + // Otherwise, look up in the sequence defined in the array + if (propertyPath.length > 0) { + value = object[propertyPath[0]]; + return propertyPath.length > 1 ? + lookupValue(value, propertyPath.slice(1)) : + value; + } - // Provide an empty array if this is a multi-item - // property. - if (Array.isArray(propertyDefinition.items)) { - initialValue = initialValue || - new Array(propertyDefinition.items.length); - } - - return conversion.toFormValue(initialValue); - }, - /** - * Set a value associated with this property in - * an object's model. - * @memberof platform/core.TypeProperty# - */ - setValue: function setValue(model, value) { - var property = propertyDefinition.property || - propertyDefinition.key; - - // If an array contains all undefined values, treat it - // as undefined, to filter back out arrays for input - // that never got entered. - value = isUnpopulatedArray(value) ? undefined : value; - - // Convert to a value suitable for storage in the - // domain object's model - value = conversion.toModelValue(value); - - return property ? - specifyValue(model, property, value) : - undefined; - }, - /** - * Get the raw definition for this property. - * @memberof platform/core.TypeProperty# - */ - getDefinition: function () { - return propertyDefinition; - } - }; + // Fallback; property path was empty + return undefined; } + /** + * Retrieve the value associated with this property + * from a given model. + * @param {object} model a domain object model to read from + * @returns {*} the value for this property, as read from the model + */ + TypeProperty.prototype.getValue = function (model) { + var property = this.propertyDefinition.property || + this.propertyDefinition.key, + initialValue = + property && lookupValue(model, property); + + // Provide an empty array if this is a multi-item + // property. + if (Array.isArray(this.propertyDefinition.items)) { + initialValue = initialValue || + new Array(this.propertyDefinition.items.length); + } + + return this.conversion.toFormValue(initialValue); + }; + + /** + * Set a value associated with this property in + * an object's model. + * @param {object} model a domain object model to update + * @param {*} value the new value to set for this property + */ + TypeProperty.prototype.setValue = function (model, value) { + var property = this.propertyDefinition.property || + this.propertyDefinition.key; + + // If an array contains all undefined values, treat it + // as undefined, to filter back out arrays for input + // that never got entered. + value = isUnpopulatedArray(value) ? undefined : value; + + // Convert to a value suitable for storage in the + // domain object's model + value = this.conversion.toModelValue(value); + + return property ? + specifyValue(model, property, value) : + undefined; + }; + + /** + * Get the raw definition for this property. + * @returns {TypePropertyDefinition} + */ + TypeProperty.prototype.getDefinition = function () { + return this.propertyDefinition; + }; + return TypeProperty; } ); diff --git a/platform/core/src/types/TypePropertyConversion.js b/platform/core/src/types/TypePropertyConversion.js index 56ce1cc5e5..6f8344e3d7 100644 --- a/platform/core/src/types/TypePropertyConversion.js +++ b/platform/core/src/types/TypePropertyConversion.js @@ -76,6 +76,23 @@ define( } } + /** + * Convert a value from its format as read from a form, to a + * format appropriate to store in a model. + * @method platform/core.TypePropertyConversion#toModelValue + * @param {*} formValue value as read from a form + * @returns {*} value to store in a model + */ + + /** + * Convert a value from its format as stored in a model, to a + * format appropriate to display in a form. + * @method platform/core.TypePropertyConversion#toFormValue + * @param {*} modelValue value as stored in a model + * @returns {*} value to display within a form + */ + + return TypePropertyConversion; } ); diff --git a/platform/core/src/types/TypeProvider.js b/platform/core/src/types/TypeProvider.js index a55c834da8..ca3f6e641e 100644 --- a/platform/core/src/types/TypeProvider.js +++ b/platform/core/src/types/TypeProvider.js @@ -26,6 +26,27 @@ define( function (TypeImpl, mergeModels) { 'use strict'; + /** + * Provides domain object types that are available/recognized within + * the system. + * + * @interface TypeService + */ + /** + * Get a specific type by name. + * + * @method TypeService#getType + * @param {string} key the key (machine-readable identifier) + * for the type of interest + * @returns {Type} the type identified by this key + */ + /** + * List all known types. + * + * @method TypeService#listTypes + * @returns {Type[]} all known types + */ + var TO_CONCAT = ['inherits', 'capabilities', 'properties', 'features'], TO_MERGE = ['model']; @@ -49,11 +70,44 @@ define( }) : array; } + // Reduce an array of type definitions to a single type definiton, + // which has merged all properties in order. + function collapse(typeDefs) { + var collapsed = typeDefs.reduce(function (a, b) { + var result = {}; + copyKeys(result, a); + copyKeys(result, b); + + // Special case: Do a merge, e.g. on "model" + TO_MERGE.forEach(function (k) { + if (a[k] && b[k]) { + result[k] = mergeModels(a[k], b[k]); + } + }); + + // Special case: Concatenate certain arrays + TO_CONCAT.forEach(function (k) { + if (a[k] || b[k]) { + result[k] = (a[k] || []).concat(b[k] || []); + } + }); + return result; + }, {}); + + // Remove any duplicates from the collapsed array + TO_CONCAT.forEach(function (k) { + if (collapsed[k]) { + collapsed[k] = removeDuplicates(collapsed[k]); + } + }); + return collapsed; + } + /** * A type provider provides information about types of domain objects * within the running Open MCT Web instance. * - * @param {Array} options.definitions the raw type + * @param {Array} types the raw type * definitions for this type. * @memberof platform/core * @constructor @@ -69,46 +123,34 @@ define( } }); return result; - }(rawTypeDefinitions)), - typeMap = {}, - undefinedType; + }(rawTypeDefinitions)); - // Reduce an array of type definitions to a single type definiton, - // which has merged all properties in order. - function collapse(typeDefs) { - var collapsed = typeDefs.reduce(function (a, b) { - var result = {}; - copyKeys(result, a); - copyKeys(result, b); - // Special case: Do a merge, e.g. on "model" - TO_MERGE.forEach(function (k) { - if (a[k] && b[k]) { - result[k] = mergeModels(a[k], b[k]); - } - }); + this.typeMap = {}; + this.typeDefinitions = typeDefinitions; + this.rawTypeDefinitions = types; + } - // Special case: Concatenate certain arrays - TO_CONCAT.forEach(function (k) { - if (a[k] || b[k]) { - result[k] = (a[k] || []).concat(b[k] || []); - } - }); - return result; - }, {}); + TypeProvider.prototype.listTypes = function () { + var self = this; + return removeDuplicates( + this.rawTypeDefinitions.filter(function (def) { + return def.key; + }).map(function (def) { + return def.key; + }).map(function (key) { + return self.getType(key); + }) + ); + }; - // Remove any duplicates from the collapsed array - TO_CONCAT.forEach(function (k) { - if (collapsed[k]) { - collapsed[k] = removeDuplicates(collapsed[k]); - } - }); - return collapsed; - } + TypeProvider.prototype.getType = function (key) { + var typeDefinitions = this.typeDefinitions, + self = this; function getUndefinedType() { - return (undefinedType = undefinedType || collapse( - rawTypeDefinitions.filter(function (typeDef) { + return (self.undefinedType = self.undefinedType || collapse( + self.rawTypeDefinitions.filter(function (typeDef) { return !typeDef.key; }) )); @@ -118,79 +160,36 @@ define( return Array.isArray(value) ? value : [value]; } - function lookupTypeDef(typeKey) { - function buildTypeDef(typeKey) { - var typeDefs = typeDefinitions[typeKey] || [], - inherits = typeDefs.map(function (typeDef) { - return asArray(typeDef.inherits || []); - }).reduce(function (a, b) { - return a.concat(b); - }, []), - def = collapse( - [getUndefinedType()].concat( - inherits.map(lookupTypeDef) - ).concat(typeDefs) - ); - - // Always provide a default name - def.model = def.model || {}; - def.model.name = def.model.name || ( - "Unnamed " + (def.name || "Object") + function buildTypeDef(typeKey) { + var typeDefs = typeDefinitions[typeKey] || [], + inherits = typeDefs.map(function (typeDef) { + return asArray(typeDef.inherits || []); + }).reduce(function (a, b) { + return a.concat(b); + }, []), + def = collapse( + [getUndefinedType()].concat( + inherits.map(lookupTypeDef) + ).concat(typeDefs) ); - return def; + // Always provide a default name + def.model = def.model || {}; + def.model.name = def.model.name || + ("Unnamed " + (def.name || "Object")); - } - - return (typeMap[typeKey] = typeMap[typeKey] || buildTypeDef(typeKey)); + return def; } + function lookupTypeDef(typeKey) { + return (self.typeMap[typeKey] = + self.typeMap[typeKey] || buildTypeDef(typeKey)); + } - return { - /** - * Get a list of all types defined by this service. - * - * @returns {Promise>} a - * promise for an array of all type instances defined - * by this service. - * @memberof module:core/type/type-provider.TypeProvider# - * @memberof platform/core.TypeProvider# - */ - listTypes: function () { - var self = this; - return removeDuplicates( - rawTypeDefinitions.filter(function (def) { - return def.key; - }).map(function (def) { - return def.key; - }).map(function (key) { - return self.getType(key); - }) - ); - }, - - /** - * Get a specific type by name. - * - * @param {string} [key] the key (machine-readable identifier) - * for the type of interest - * @returns {Promise} a - * promise for a type object identified by this key. - * @memberof module:core/type/type-provider.TypeProvider# - * @memberof platform/core.TypeProvider# - */ - getType: function (key) { - return new TypeImpl(lookupTypeDef(key)); - } - }; - } - - // Services framework is designed to expect factories - TypeProvider.instantiate = TypeProvider; + return new TypeImpl(lookupTypeDef(key)); + }; return TypeProvider; - - } ); diff --git a/platform/core/src/views/ViewCapability.js b/platform/core/src/views/ViewCapability.js index 8cdf7eaecb..38862cf2fc 100644 --- a/platform/core/src/views/ViewCapability.js +++ b/platform/core/src/views/ViewCapability.js @@ -36,23 +36,25 @@ define( * object. * * @memberof platform/core + * @implements {Capability} * @constructor */ function ViewCapability(viewService, domainObject) { - return { - /** - * Get all view definitions which are applicable to - * this object. - * @returns {View[]} an array of view definitions - * which are applicable to this object. - * @memberof platform/core.ViewCapability# - */ - invoke: function () { - return viewService.getViews(domainObject); - } - }; + this.viewService = viewService; + this.domainObject = domainObject; } + /** + * Get all view definitions which are applicable to + * this object. + * @returns {View[]} an array of view definitions + * which are applicable to this object. + * @memberof platform/core.ViewCapability# + */ + ViewCapability.prototype.invoke = function () { + return this.viewService.getViews(this.domainObject); + }; + return ViewCapability; } ); diff --git a/platform/core/src/views/ViewProvider.js b/platform/core/src/views/ViewProvider.js index e3e9e550d2..e2034c3d6b 100644 --- a/platform/core/src/views/ViewProvider.js +++ b/platform/core/src/views/ViewProvider.js @@ -29,6 +29,22 @@ define( function () { "use strict"; + /** + * Provides definitions for views that are available for specific + * domain objects. + * + * @interface ViewService + */ + + /** + * Get all views which are applicable to this domain object. + * + * @method ViewService#getViews + * @param {DomainObject} domainObject the domain object to view + * @returns {View[]} all views which can be used to visualize + * this domain object. + */ + /** * A view provider allows view definitions (defined as extensions) * to be read, and takes responsibility for filtering these down @@ -58,6 +74,8 @@ define( * @memberof platform/core * @constructor * @param {View[]} an array of view definitions + * @param $log Angular's logging service + * @implements {ViewService} */ function ViewProvider(views, $log) { @@ -79,6 +97,13 @@ define( return key; } + // Filter out any key-less views + this.views = views.filter(validate); + } + + ViewProvider.prototype.getViews = function (domainObject) { + var type = domainObject.useCapability("type"); + // Check if an object has all capabilities designated as `needs` // for a view. Exposing a capability via delegation is taken to // satisfy this filter if `allowDelegation` is true. @@ -122,35 +147,16 @@ define( return matches; } - function getViews(domainObject) { - var type = domainObject.useCapability("type"); - - // First, filter views by type (matched to domain object type.) - // Second, filter by matching capabilities. - return views.filter(function (view) { - return viewMatchesType(view, type) && capabilitiesMatch( + // First, filter views by type (matched to domain object type.) + // Second, filter by matching capabilities. + return this.views.filter(function (view) { + return viewMatchesType(view, type) && capabilitiesMatch( domainObject, view.needs || [], view.delegation || false ); - }); - } - - // Filter out any key-less views - views = views.filter(validate); - - return { - /** - * Get all views which are applicable to this domain object. - * - * @param {DomainObject} domainObject the domain object to view - * @returns {View[]} all views which can be used to visualize - * this domain object. - * @memberof platform/core.ViewProvider# - */ - getViews: getViews - }; - } + }); + }; return ViewProvider; } diff --git a/platform/core/test/objects/DomainObjectSpec.js b/platform/core/test/objects/DomainObjectSpec.js index 29862461b3..13e8968e0d 100644 --- a/platform/core/test/objects/DomainObjectSpec.js +++ b/platform/core/test/objects/DomainObjectSpec.js @@ -25,7 +25,7 @@ * DomainObjectSpec. Created by vwoeltje on 11/6/14. */ define( - ["../../src/objects/DomainObject"], + ["../../src/objects/DomainObjectImpl"], function (DomainObject) { "use strict"; diff --git a/platform/core/test/types/TypeImplSpec.js b/platform/core/test/types/TypeImplSpec.js index c11075870c..d29c4f2712 100644 --- a/platform/core/test/types/TypeImplSpec.js +++ b/platform/core/test/types/TypeImplSpec.js @@ -23,7 +23,7 @@ define( ['../../src/types/TypeImpl'], - function (typeImpl) { + function (TypeImpl) { "use strict"; describe("Type definition wrapper", function () { @@ -41,7 +41,7 @@ define( properties: [ {} ], model: {someKey: "some value"} }; - type = typeImpl(testTypeDef); + type = new TypeImpl(testTypeDef); }); it("exposes key from definition", function () { diff --git a/platform/core/test/types/TypeProviderSpec.js b/platform/core/test/types/TypeProviderSpec.js index 0da1f5910e..24f0a77c73 100644 --- a/platform/core/test/types/TypeProviderSpec.js +++ b/platform/core/test/types/TypeProviderSpec.js @@ -128,7 +128,7 @@ define( }); it("includes capabilities from undefined type in all types", function () { - captured.type = TypeProvider.instantiate( + captured.type = new TypeProvider( testTypeDefinitions.concat([ { capabilities: ['a', 'b', 'c'] }, { capabilities: ['x', 'y', 'z'] }