[Code Style] Use prototypes in platform

WTD-1482
This commit is contained in:
Victor Woeltjen 2015-08-11 12:54:50 -07:00
parent f377c7cb71
commit b7765ff388
35 changed files with 1331 additions and 1379 deletions

View File

@ -108,41 +108,19 @@ define(
* of action services * of action services
*/ */
function ActionAggregator(actionProviders) { function ActionAggregator(actionProviders) {
this.actionProviders = 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
};
} }
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; return ActionAggregator;
} }
); );

View File

@ -49,71 +49,69 @@ define(
* @constructor * @constructor
*/ */
function ActionCapability($q, actionService, domainObject) { 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; // Get all actions which are valid in this context;
// this simply redirects to the action service, // this simply redirects to the action service,
// but additionally adds a domainObject field. // but additionally adds a domainObject field.
function getActions(context) { var baseContext = typeof context === 'string' ?
var baseContext = typeof context === 'string' ? { key: context } : (context || {}),
{ key: context } : actionContext = Object.create(baseContext);
(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 // Alias to getActions(context)[0].perform, with a
// check for empty arrays. // check for empty arrays.
function performAction(context) { var actions = this.getActions(context);
var actions = getActions(context);
return $q.when( return this.$q.when(
(actions && actions.length > 0) ? (actions && actions.length > 0) ?
actions[0].perform() : actions[0].perform() :
undefined 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; return ActionCapability;
} }

View File

@ -36,11 +36,45 @@ define(
* category of extension.) * category of extension.)
* *
* @memberof platform/core * @memberof platform/core
* @imeplements {ActionService}
* @constructor * @constructor
*/ */
function ActionProvider(actions) { function ActionProvider(actions) {
var actionsByKey = {}, var self = this;
actionsByCategory = {};
// 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 // Instantiate an action; invokes the constructor and
// additionally fills in the action's getMetadata method // additionally fills in the action's getMetadata method
@ -71,86 +105,31 @@ define(
function createIfApplicable(actions, context) { function createIfApplicable(actions, context) {
return (actions || []).filter(function (Action) { return (actions || []).filter(function (Action) {
return Action.appliesTo ? return Action.appliesTo ?
Action.appliesTo(context) : true; Action.appliesTo(context) : true;
}).map(function (Action) { }).map(function (Action) {
return instantiateAction(Action, context); return instantiateAction(Action, context);
}); });
} }
// Get an array of actions that are valid in the supplied context. // Match actions to the provided context by comparing "key"
function getActions(actionContext) { // and/or "category" parameters, if specified.
var context = (actionContext || {}), candidates = this.actions;
category = context.category, if (key) {
key = context.key, candidates = this.actionsByKey[key];
candidates; if (category) {
candidates = candidates.filter(function (Action) {
// Match actions to the provided context by comparing "key" return Action.category === category;
// 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];
} }
} else if (category) {
// Instantiate those remaining actions, with additional candidates = this.actionsByCategory[category];
// filtering per any appliesTo methods defined on those
// actions.
return createIfApplicable(candidates, context);
} }
// Build up look-up tables // Instantiate those remaining actions, with additional
actions.forEach(function (Action) { // filtering per any appliesTo methods defined on those
// Get an action's category or categories // actions.
var categories = Action.category || []; return createIfApplicable(candidates, context);
};
// 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
};
}
return ActionProvider; return ActionProvider;
} }

View File

@ -36,8 +36,19 @@ define(
* *
* @memberof platform/core * @memberof platform/core
* @constructor * @constructor
* @implements {ActionService}
* @param $log Angular's logging service
* @param {ActionService} actionService the decorated action service
*/ */
function LoggingActionDecorator($log, actionService) { 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 // Decorate the perform method of the specified action, such that
// it emits a log message whenever performed. // it emits a log message whenever performed.
function addLogging(action) { function addLogging(action) {
@ -59,35 +70,11 @@ define(
return logAction; return logAction;
} }
return { return actionService.getActions.apply(
/** actionService,
* Get a list of actions which are valid in a given arguments
* context. These actions will additionally log ).map(addLogging);
* 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 LoggingActionDecorator; return LoggingActionDecorator;
} }

View File

@ -39,68 +39,59 @@ define(
* *
* @memberof platform/core * @memberof platform/core
* @constructor * @constructor
* @implements {Capability}
*/ */
function CompositionCapability($injector, domainObject) { function CompositionCapability($injector, domainObject) {
var objectService,
lastPromise,
lastModified;
// Get a reference to the object service from $injector // Get a reference to the object service from $injector
function injectObjectService() { this.injectObjectService = function () {
objectService = $injector.get("objectService"); this.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.<DomainObject[]>} a list of all domain
* objects which compose this domain object.
* @memberof platform/core.CompositionCapability#
*/
invoke: promiseComposition
}; };
this.domainObject = domainObject;
} }
/**
* Request the composition of this object.
* @returns {Promise.<DomainObject[]>} 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 * Test to determine whether or not this capability should be exposed
* by a domain object based on its model. Checks for the presence of * by a domain object based on its model. Checks for the presence of

View File

@ -38,79 +38,76 @@ define(
* *
* @memberof platform/core * @memberof platform/core
* @constructor * @constructor
* @implements {Capability}
*/ */
function ContextCapability(parentObject, domainObject) { function ContextCapability(parentObject, domainObject) {
return { 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.
* @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);
}
};
} }
/**
* 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; return ContextCapability;
} }
); );

View File

@ -44,6 +44,7 @@ define(
* *
* @memberof platform/core * @memberof platform/core
* @constructor * @constructor
* @implements {DomainObject}
*/ */
function ContextualDomainObject(domainObject, parentObject) { function ContextualDomainObject(domainObject, parentObject) {
// Prototypally inherit from the domain object, and // Prototypally inherit from the domain object, and

View File

@ -29,6 +29,19 @@ define(
function () { function () {
"use strict"; "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, * Provides capabilities based on extension definitions,
* matched to domain object models. * matched to domain object models.

View File

@ -45,13 +45,40 @@ define(
* in the type's definition, which contains an array of names of * in the type's definition, which contains an array of names of
* capabilities to be delegated. * capabilities to be delegated.
* *
* @param domainObject * @param $q Angular's $q, for promises
* @param {DomainObject} domainObject the delegating domain object
* @memberof platform/core * @memberof platform/core
* @constructor * @constructor
* @implements {Capability}
*/ */
function DelegationCapability($q, domainObject) { function DelegationCapability($q, domainObject) {
var delegateCapabilities = {}, var type = domainObject.getCapability("type"),
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) { function filterObjectsWithCapability(capability) {
return function (objects) { return function (objects) {
@ -65,55 +92,40 @@ define(
return domainObject.useCapability('composition'); return domainObject.useCapability('composition');
} }
function doesDelegate(key) { return this.doesDelegateCapability(key) ?
return delegateCapabilities[key] || false; promiseChildren().then(
} filterObjectsWithCapability(key)
) :
this.$q.when([]);
};
function getDelegates(capability) { /**
return doesDelegate(capability) ? * Check if the domain object which exposed this capability
promiseChildren().then( * wishes to delegate another capability.
filterObjectsWithCapability(capability) *
) : * @param {string} key the capability to check for
$q.when([]); * @returns {boolean} true if the capability is delegated
} */
DelegationCapability.prototype.doesDelegateCapability = function (key) {
// Generate set for easy lookup of capability delegation return !!(this.delegateCapabilities[key]);
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
};
}
/**
* 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; return DelegationCapability;

View File

@ -11,8 +11,6 @@ define(
* @property {string} name the human-readable name of this property * @property {string} name the human-readable name of this property
* @property {string} value the human-readable value of this property, * @property {string} value the human-readable value of this property,
* for this specific domain object * for this specific domain object
* @constructor
* @memberof platform/core
*/ */
var TIME_FORMAT = "YYYY-MM-DD HH:mm:ss"; var TIME_FORMAT = "YYYY-MM-DD HH:mm:ss";
@ -27,10 +25,23 @@ define(
* `value` properties describing that domain object (suitable for * `value` properties describing that domain object (suitable for
* display.) * display.)
* *
* @param {DomainObject} domainObject the domain object whose
* metadata is to be exposed
* @implements {Capability}
* @constructor * @constructor
* @memberof platform/core
*/ */
function MetadataCapability(domainObject) { 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) { function hasDisplayableValue(metadataProperty) {
var t = typeof metadataProperty.value; var t = typeof metadataProperty.value;
@ -39,8 +50,8 @@ define(
function formatTimestamp(timestamp) { function formatTimestamp(timestamp) {
return typeof timestamp === 'number' ? return typeof timestamp === 'number' ?
(moment.utc(timestamp).format(TIME_FORMAT) + " UTC") : (moment.utc(timestamp).format(TIME_FORMAT) + " UTC") :
undefined; undefined;
} }
function getProperties() { function getProperties() {
@ -75,20 +86,9 @@ define(
]; ];
} }
function getMetadata() { return getProperties().concat(getCommonMetadata())
return getProperties().concat(getCommonMetadata()) .filter(hasDisplayableValue);
.filter(hasDisplayableValue); };
}
return {
/**
* Get metadata about this object.
* @returns {MetadataProperty[]} metadata about this object
* @memberof platform/core.MetadataCapability#
*/
invoke: getMetadata
};
}
return MetadataCapability; return MetadataCapability;
} }

View File

@ -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 * @param {DomainObject} domainObject the domain object
* which will expose this capability * which will expose this capability
* @memberof platform/core * @memberof platform/core
* @constructor * @constructor
* @implements {Capability}
*/ */
function MutationCapability(topic, now, domainObject) { 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 * Modify the domain object's model, using a provided
// mutator function has a temporary copy to work with. * function. This function will receive a copy of the
var model = domainObject.getModel(), * domain object's model as an argument; behavior
clone = JSON.parse(JSON.stringify(model)), * varies depending on that function's return value:
useTimestamp = arguments.length > 1; *
* * 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.<boolean>} 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 to handle copying values to the actual
function handleMutation(mutationResult) { function handleMutation(mutationResult) {
// If mutation result was undefined, just use // If mutation result was undefined, just use
// the clone; this allows the mutator to omit return // the clone; this allows the mutator to omit return
// values and just change the model directly. // values and just change the model directly.
var result = mutationResult || clone; var result = mutationResult || clone;
// Allow mutators to change their mind by // Allow mutators to change their mind by
// returning false. // returning false.
if (mutationResult !== false) { if (mutationResult !== false) {
// Copy values if result was a different object // Copy values if result was a different object
// (either our clone or some other new thing) // (either our clone or some other new thing)
if (model !== result) { if (model !== result) {
copyValues(model, result); copyValues(model, result);
}
model.modified = useTimestamp ? timestamp : now();
t.notify(model);
} }
model.modified = useTimestamp ? timestamp : now();
// Report the result of the mutation t.notify(model);
return mutationResult !== false;
} }
// Invoke the provided mutator, then make changes to // Report the result of the mutation
// the underlying model (if applicable.) return mutationResult !== false;
return fastPromise(mutator(clone)).then(handleMutation);
} }
function listen(listener) { // Invoke the provided mutator, then make changes to
return t.listen(listener); // the underlying model (if applicable.)
} return fastPromise(mutator(clone)).then(handleMutation);
};
return { /**
/** * Listen for mutations of this domain object's model.
* Alias of `mutate`, used to support useCapability. * The provided listener will be invoked with the domain
* @memberof platform/core.MutationCapability# * object's new model after any changes. To stop listening,
*/ * invoke the function returned by this method.
invoke: mutate, * @param {Function} listener function to call on mutation
/** * @returns {Function} a function to stop listening
* Modify the domain object's model, using a provided * @memberof platform/core.MutationCapability#
* function. This function will receive a copy of the */
* domain object's model as an argument; behavior MutationCapability.prototype.listen = function (listener) {
* varies depending on that function's return value: return this.mutationTopic.listen(listener);
* };
* * 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. * Alias of `mutate`, used to support useCapability.
* This is useful for writing code that modifies the model */
* directly. MutationCapability.prototype.invoke =
* * If a plain object is returned, that object will be used MutationCapability.prototype.mutate;
* 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.<boolean>} 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
};
}
return MutationCapability; return MutationCapability;
} }

View File

@ -33,7 +33,7 @@ define(
* *
* @param {PersistenceService} persistenceService the underlying * @param {PersistenceService} persistenceService the underlying
* provider of persistence capabilities. * 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 * use (this is an arbitrary string, useful in principle
* for distinguishing different persistence stores from * for distinguishing different persistence stores from
* one another.) * one another.)
@ -42,10 +42,60 @@ define(
* *
* @memberof platform/core * @memberof platform/core
* @constructor * @constructor
* @implements {Capability}
*/ */
function PersistenceCapability(persistenceService, SPACE, domainObject) { function PersistenceCapability(persistenceService, space, domainObject) {
// Cache modified timestamp // 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 // Update a domain object's model upon refresh
function updateModel(model) { function updateModel(model) {
@ -55,75 +105,28 @@ define(
}, modified); }, modified);
} }
// For refresh; update a domain object model, only if there // Only update if we don't have unsaved changes
// are no unsaved changes. return (model.modified === model.persisted) ?
function updatePersistenceTimestamp() { this.persistenceService.readObject(
var modified = domainObject.getModel().modified; this.getSpace(),
domainObject.useCapability("mutation", function (model) { this.domainObject.getId()
model.persisted = modified; ).then(updateModel) :
}, modified); fastPromise(false);
} };
// Utility function for creating promise-like objects which /**
// resolve synchronously when possible * Get the space in which this domain object is persisted;
function fastPromise(value) { * this is useful when, for example, decided which space a
return (value || {}).then ? value : { * newly-created domain object should be persisted to (by
then: function (callback) { * default, this should be the space of its containing
return fastPromise(callback(value)); * object.)
} *
}; * @returns {string} the name of the space which should
} * be used to persist this object
*/
return { PersistenceCapability.prototype.getSpace = function () {
/** return this.space;
* 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;
}
};
}
return PersistenceCapability; return PersistenceCapability;
} }

View File

@ -40,92 +40,80 @@ define(
* *
* @memberof platform/core * @memberof platform/core
* @constructor * @constructor
* @implements {Capability}
*/ */
function RelationshipCapability($injector, domainObject) { function RelationshipCapability($injector, domainObject) {
var objectService,
lastPromise = {},
lastModified;
// Get a reference to the object service from $injector // Get a reference to the object service from $injector
function injectObjectService() { this.injectObjectService = function () {
objectService = $injector.get("objectService"); this.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.<DomainObject[]>} a promise for related
* domain objects
* @memberof platform/core.RelationshipCapability#
*/
getRelatedObjects: promiseRelationships
}; };
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.<DomainObject[]>} 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 * Test to determine whether or not this capability should be exposed
* by a domain object based on its model. Checks for the presence of * by a domain object based on its model. Checks for the presence of

View File

@ -32,10 +32,30 @@ define(
* object are not provided. * object are not provided.
* @memberof platform/core * @memberof platform/core
* @constructor * @constructor
* @param {ModelService} modelService this service to decorate
* @implements {ModelService}
*/ */
function CachingModelDecorator(modelService) { function CachingModelDecorator(modelService) {
var cache = {}, this.cache = {};
cached = {}; 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. // Update the cached instance of a model to a new value.
// We update in-place to ensure there is only ever one instance // We update in-place to ensure there is only ever one instance
@ -68,30 +88,12 @@ define(
return oldModel; 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 // Store the provided models in our cache
function cacheAll(models) { function cacheAll(models) {
Object.keys(models).forEach(function (id) { 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 cache;
} }
return { // Look up if we have unknown IDs
/** if (neededIds.length > 0) {
* Get models for these specified string identifiers. return this.modelService.getModels(neededIds)
* These will be given as an object containing keys .then(cacheAll)
* and values, where keys are object identifiers and .then(giveCache);
* values are models. }
* This result may contain either a subset or a
* superset of the total objects.
*
* @param {Array<string>} ids the string identifiers for
* models of interest.
* @returns {Promise<object>} 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 // Otherwise, just expose the cache directly
if (neededIds.length > 0) { return fastPromise(cache);
return modelService.getModels(neededIds) };
.then(cacheAll)
.then(giveCache);
}
// Otherwise, just expose the cache directly
return fastPromise(cache);
}
};
}
return CachingModelDecorator; return CachingModelDecorator;
} }

View File

@ -29,33 +29,34 @@ define(
/** /**
* Adds placeholder domain object models for any models which * Adds placeholder domain object models for any models which
* fail to load from the underlying model service. * fail to load from the underlying model service.
* @implements {ModelService}
* @constructor * @constructor
* @memberof platform/core * @memberof platform/core
* @param {ModelService} modelService this service to decorate
* @implements {ModelService}
*/ */
function MissingModelDecorator(modelService) { function MissingModelDecorator(modelService) {
function missingModel(id) { this.modelService = modelService;
return { }
type: "unknown",
name: "Missing: " + id
};
}
function missingModel(id) {
return { return {
getModels: function (ids) { type: "unknown",
function addMissingModels(models) { name: "Missing: " + id
var result = {};
ids.forEach(function (id) {
result[id] = models[id] || missingModel(id);
});
return result;
}
return modelService.getModels(ids).then(addMissingModels);
}
}; };
} }
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; return MissingModelDecorator;
} }
); );

View File

@ -29,67 +29,71 @@ define(
function () { function () {
"use strict"; "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.<Object>} a promise for an object mapping
* string identifiers to domain object models.
*/
/** /**
* Allows multiple services which provide models for domain objects * Allows multiple services which provide models for domain objects
* to be treated as one. * to be treated as one.
* *
* @memberof platform/core * @memberof platform/core
* @constructor * @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 * aggregated
*/ */
function ModelAggregator($q, providers) { function ModelAggregator($q, providers) {
this.providers = providers;
// Pick a domain object model to use, favoring the one this.$q = $q;
// 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.<object>} 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);
});
}
};
} }
// 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; return ModelAggregator;
} }
); );

View File

@ -35,61 +35,47 @@ define(
* *
* @memberof platform/core * @memberof platform/core
* @constructor * @constructor
* @implements {ModelService}
* @param {PersistenceService} persistenceService the service in which * @param {PersistenceService} persistenceService the service in which
* domain object models are persisted. * domain object models are persisted.
* @param $q Angular's $q service, for working with promises * @param $q Angular's $q service, for working with promises
* @param {string} SPACE the name of the persistence space from which * @param {string} SPACE the name of the persistence space from which
* models should be retrieved. * models should be retrieved.
*/ */
function PersistedModelProvider(persistenceService, $q, SPACE) { function PersistedModelProvider(persistenceService, $q, space) {
// Load a single object model from persistence this.persistenceService = persistenceService;
function loadModel(id) { this.$q = $q;
return persistenceService.readObject(SPACE, id); this.space = space;
}
// 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.<object>} 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
};
} }
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; return PersistedModelProvider;
} }

View File

@ -41,42 +41,31 @@ define(
* *
* @memberof platform/core * @memberof platform/core
* @constructor * @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) { function RootModelProvider(roots, $q, $log) {
// Pull out identifiers to used as ROOT's // Pull out identifiers to used as ROOT's
var ids = roots.map(function (root) { return root.id; }), var ids = roots.map(function (root) { return root.id; });
baseProvider = new StaticModelProvider(roots, $q, $log);
function addRoot(models) { this.baseProvider = new StaticModelProvider(roots, $q, $log);
models.ROOT = { this.rootModel = {
name: "The root object", name: "The root object",
type: "root", type: "root",
composition: ids 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.<object>} 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);
}
}; };
} }
RootModelProvider.prototype.getModels = function (ids) {
var rootModel = this.rootModel;
return this.baseProvider.getModels(ids).then(function (models) {
models.ROOT = rootModel;
return models;
});
};
return RootModelProvider; return RootModelProvider;
} }
); );

View File

@ -41,7 +41,7 @@ define(
// Skip models which don't look right // Skip models which don't look right
if (typeof model !== 'object' || if (typeof model !== 'object' ||
typeof model.id !== 'string' || typeof model.id !== 'string' ||
typeof model.model !== 'object') { typeof model.model !== 'object') {
$log.warn([ $log.warn([
"Skipping malformed domain object model exposed by ", "Skipping malformed domain object model exposed by ",
((model || {}).bundle || {}).path ((model || {}).bundle || {}).path
@ -54,34 +54,19 @@ define(
// Prepoulate maps with models to make subsequent lookup faster. // Prepoulate maps with models to make subsequent lookup faster.
models.forEach(addModelToMap); models.forEach(addModelToMap);
return { this.modelMap = modelMap;
/** this.$q = $q;
* 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<string>} ids the string identifiers for
* models of interest.
* @returns {Promise<object>} 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);
}
};
} }
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; return StaticModelProvider;
} }
); );

View File

@ -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.<string, Capability>|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;
}
);

View File

@ -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.<string, Capability>|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;
}
);

View File

@ -27,10 +27,31 @@
* @namespace platform/core * @namespace platform/core
*/ */
define( define(
["./DomainObject"], ["./DomainObjectImpl"],
function (DomainObject) { function (DomainObjectImpl) {
"use strict"; "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<object<string, DomainObject>>} 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. * Construct a new provider for domain objects.
* *
@ -44,6 +65,16 @@ define(
* @constructor * @constructor
*/ */
function DomainObjectProvider(modelService, capabilityService, $q) { 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) // Given a models object (containing key-value id-model pairs)
// create a function that will look up from the capability // create a function that will look up from the capability
// service based on id; for handy mapping below. // service based on id; for handy mapping below.
@ -51,8 +82,8 @@ define(
return function (id) { return function (id) {
var model = models[id]; var model = models[id];
return model ? return model ?
capabilityService.getCapabilities(model) : capabilityService.getCapabilities(model) :
undefined; undefined;
}; };
} }
@ -65,7 +96,7 @@ define(
ids.forEach(function (id, index) { ids.forEach(function (id, index) {
if (models[id]) { if (models[id]) {
// Create the domain object // Create the domain object
result[id] = new DomainObject( result[id] = new DomainObjectImpl(
id, id,
models[id], models[id],
capabilities[index] capabilities[index]
@ -75,36 +106,14 @@ define(
return result; return result;
} }
// Get object instances; this is the useful API exposed by the return modelService.getModels(ids).then(function (models) {
// domain object provider. return $q.all(
function getObjects(ids) { ids.map(capabilityResolver(models))
return modelService.getModels(ids).then(function (models) { ).then(function (capabilities) {
return $q.all(
ids.map(capabilityResolver(models))
).then(function (capabilities) {
return assembleResult(ids, models, 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<string>} ids the identifiers for domain objects
* of interest.
* @return {Promise<object<string, DomainObject>>} 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; return DomainObjectProvider;
} }

View File

@ -31,7 +31,6 @@ define(
* `Date.now()` which can be injected to support testability. * `Date.now()` which can be injected to support testability.
* *
* @returns {Function} a function which returns current system time * @returns {Function} a function which returns current system time
* @constructor
* @memberof platform/core * @memberof platform/core
*/ */
function Now() { function Now() {

View File

@ -42,7 +42,6 @@ define(
* resolve to the returned value of `fn` whenever that is invoked. * resolve to the returned value of `fn` whenever that is invoked.
* *
* @returns {Function} * @returns {Function}
* @constructor
* @memberof platform/core * @memberof platform/core
*/ */
function Throttle($timeout) { function Throttle($timeout) {

View File

@ -44,7 +44,6 @@ define(
* arguments) are private; each call returns a new instance. * arguments) are private; each call returns a new instance.
* *
* @returns {Function} * @returns {Function}
* @constructor
* @memberof platform/core * @memberof platform/core
*/ */
function Topic() { function Topic() {

View File

@ -36,6 +36,8 @@ define(
* *
* @memberof platform/core * @memberof platform/core
* @constructor * @constructor
* @augments {Type}
* @implements {Capability}
* @param {TypeService} typeService the service which * @param {TypeService} typeService the service which
* provides type information * provides type information
* @param {DomainObject} domainObject the domain object * @param {DomainObject} domainObject the domain object

View File

@ -26,10 +26,95 @@ define(
function (TypeProperty) { function (TypeProperty) {
"use strict"; "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<TypeProperty[]} properties associated with
* objects of this type
* @method Type#getPropertiees
*/
/**
* Get the initial state of a model for domain objects of
* this type.
*
* @return {object} initial domain object model
* @method Type#getInitialModel
*/
/**
* 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
* @method Type#getDefinition
*/
/**
* Check if this type is or inherits from some other type.
*
* @param {string|Type} key either
* a string key for a type, or an instance of a type
* object, which this
* @returns {boolean} true
* @method Type#instanceOf
*/
/**
* 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
* @method Type#hasFeature
*/
/** /**
* Construct a new type. Types describe categories of * Construct a new type. Types describe categories of
* domain objects. * domain objects.
* *
* @implements {Type}
* @param {TypeDefinition} typeDef an object containing * @param {TypeDefinition} typeDef an object containing
* key-value pairs describing a type and its * key-value pairs describing a type and its
* relationship to other types. * relationship to other types.
@ -44,133 +129,62 @@ define(
featureSet[feature] = true; featureSet[feature] = true;
}); });
return { this.typeDef = typeDef;
/** this.featureSet = featureSet;
* Get the string key which identifies this type. this.inheritList = inheritList;
* 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
* @memberof module:core/type/type-impl.TypeImpl#
* @memberof platform/core.TypeImpl#
*/
getKey: function () {
return typeDef.key;
},
/**
* 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
* @memberof module:core/type/type-impl.TypeImpl#
* @memberof platform/core.TypeImpl#
*/
getName: function () {
return typeDef.name;
},
/**
* 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
* @memberof module:core/type/type-impl.TypeImpl#
* @memberof platform/core.TypeImpl#
*/
getDescription: function () {
return typeDef.description;
},
/**
* 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
* @memberof module:core/type/type-impl.TypeImpl#
* @memberof platform/core.TypeImpl#
*/
getGlyph: function () {
return typeDef.glyph;
},
/**
* 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<TypeProperty>} 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;
}
};
} }
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; return TypeImpl;
} }
); );

View File

@ -35,128 +35,130 @@ define(
*/ */
function TypeProperty(propertyDefinition) { function TypeProperty(propertyDefinition) {
// Load an appropriate conversion // Load an appropriate conversion
var conversion = new TypePropertyConversion( this.conversion = new TypePropertyConversion(
propertyDefinition.conversion || "identity" propertyDefinition.conversion || "identity"
); );
this.propertyDefinition = propertyDefinition;
}
// Check if a value is defined; used to check if initial array // Check if a value is defined; used to check if initial array
// values have been populated. // values have been populated.
function isUnpopulatedArray(value) { function isUnpopulatedArray(value) {
var i; var i;
if (!Array.isArray(value) || value.length === 0) { if (!Array.isArray(value) || value.length === 0) {
return false; return false;
}
for (i = 0; i < value.length; i += 1) {
if (value[i] !== undefined) {
return false;
}
}
return true;
} }
// Perform a lookup for a value from an object, for (i = 0; i < value.length; i += 1) {
// which may recursively look at contained objects if (value[i] !== undefined) {
// based on the path provided. return false;
function lookupValue(object, propertyPath) {
var value;
// Can't look up from a non-object
if (!object) {
return undefined;
} }
}
// If path is not an array, just look up the property return true;
if (!Array.isArray(propertyPath)) { }
return object[propertyPath];
}
// Otherwise, look up in the sequence defined in the array // Specify a field deeply within an object
if (propertyPath.length > 0) { function specifyValue(object, propertyPath, value) {
value = object[propertyPath[0]]; // If path is not an array, just set the property
return propertyPath.length > 1 ? if (!Array.isArray(propertyPath)) {
lookupValue(value, propertyPath.slice(1)) : object[propertyPath] = value;
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; return undefined;
} }
function specifyValue(object, propertyPath, value) { // If path is not an array, just look up the property
if (!Array.isArray(propertyPath)) {
// If path is not an array, just set the property return object[propertyPath];
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;
}
} }
return { // Otherwise, look up in the sequence defined in the array
/** if (propertyPath.length > 0) {
* Retrieve the value associated with this property value = object[propertyPath[0]];
* from a given model. return propertyPath.length > 1 ?
* @memberof platform/core.TypeProperty# lookupValue(value, propertyPath.slice(1)) :
*/ value;
getValue: function (model) { }
var property = propertyDefinition.property ||
propertyDefinition.key,
initialValue =
property && lookupValue(model, property);
// Provide an empty array if this is a multi-item // Fallback; property path was empty
// property. return undefined;
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;
}
};
} }
/**
* 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; return TypeProperty;
} }
); );

View File

@ -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; return TypePropertyConversion;
} }
); );

View File

@ -26,6 +26,27 @@ define(
function (TypeImpl, mergeModels) { function (TypeImpl, mergeModels) {
'use strict'; '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'], var TO_CONCAT = ['inherits', 'capabilities', 'properties', 'features'],
TO_MERGE = ['model']; TO_MERGE = ['model'];
@ -49,11 +70,44 @@ define(
}) : array; }) : 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 * A type provider provides information about types of domain objects
* within the running Open MCT Web instance. * within the running Open MCT Web instance.
* *
* @param {Array<TypeDefinition>} options.definitions the raw type * @param {Array<TypeDefinition>} types the raw type
* definitions for this type. * definitions for this type.
* @memberof platform/core * @memberof platform/core
* @constructor * @constructor
@ -69,46 +123,34 @@ define(
} }
}); });
return result; return result;
}(rawTypeDefinitions)), }(rawTypeDefinitions));
typeMap = {},
undefinedType;
// 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" this.typeMap = {};
TO_MERGE.forEach(function (k) { this.typeDefinitions = typeDefinitions;
if (a[k] && b[k]) { this.rawTypeDefinitions = types;
result[k] = mergeModels(a[k], b[k]); }
}
});
// Special case: Concatenate certain arrays TypeProvider.prototype.listTypes = function () {
TO_CONCAT.forEach(function (k) { var self = this;
if (a[k] || b[k]) { return removeDuplicates(
result[k] = (a[k] || []).concat(b[k] || []); this.rawTypeDefinitions.filter(function (def) {
} return def.key;
}); }).map(function (def) {
return result; return def.key;
}, {}); }).map(function (key) {
return self.getType(key);
})
);
};
// Remove any duplicates from the collapsed array TypeProvider.prototype.getType = function (key) {
TO_CONCAT.forEach(function (k) { var typeDefinitions = this.typeDefinitions,
if (collapsed[k]) { self = this;
collapsed[k] = removeDuplicates(collapsed[k]);
}
});
return collapsed;
}
function getUndefinedType() { function getUndefinedType() {
return (undefinedType = undefinedType || collapse( return (self.undefinedType = self.undefinedType || collapse(
rawTypeDefinitions.filter(function (typeDef) { self.rawTypeDefinitions.filter(function (typeDef) {
return !typeDef.key; return !typeDef.key;
}) })
)); ));
@ -118,79 +160,36 @@ define(
return Array.isArray(value) ? value : [value]; return Array.isArray(value) ? value : [value];
} }
function lookupTypeDef(typeKey) { function buildTypeDef(typeKey) {
function buildTypeDef(typeKey) { var typeDefs = typeDefinitions[typeKey] || [],
var typeDefs = typeDefinitions[typeKey] || [], inherits = typeDefs.map(function (typeDef) {
inherits = typeDefs.map(function (typeDef) { return asArray(typeDef.inherits || []);
return asArray(typeDef.inherits || []); }).reduce(function (a, b) {
}).reduce(function (a, b) { return a.concat(b);
return a.concat(b); }, []),
}, []), def = collapse(
def = collapse( [getUndefinedType()].concat(
[getUndefinedType()].concat( inherits.map(lookupTypeDef)
inherits.map(lookupTypeDef) ).concat(typeDefs)
).concat(typeDefs)
);
// Always provide a default name
def.model = def.model || {};
def.model.name = def.model.name || (
"Unnamed " + (def.name || "Object")
); );
return def; // Always provide a default name
def.model = def.model || {};
def.model.name = def.model.name ||
("Unnamed " + (def.name || "Object"));
} return def;
return (typeMap[typeKey] = typeMap[typeKey] || buildTypeDef(typeKey));
} }
function lookupTypeDef(typeKey) {
return (self.typeMap[typeKey] =
self.typeMap[typeKey] || buildTypeDef(typeKey));
}
return { return new TypeImpl(lookupTypeDef(key));
/** };
* Get a list of all types defined by this service.
*
* @returns {Promise<Array<module:core/type/type-impl.TypeImpl>>} 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<module:core/type/type-impl.TypeImpl>} 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 TypeProvider; return TypeProvider;
} }
); );

View File

@ -36,23 +36,25 @@ define(
* object. * object.
* *
* @memberof platform/core * @memberof platform/core
* @implements {Capability}
* @constructor * @constructor
*/ */
function ViewCapability(viewService, domainObject) { function ViewCapability(viewService, domainObject) {
return { 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#
*/
invoke: function () {
return viewService.getViews(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; return ViewCapability;
} }
); );

View File

@ -29,6 +29,22 @@ define(
function () { function () {
"use strict"; "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) * A view provider allows view definitions (defined as extensions)
* to be read, and takes responsibility for filtering these down * to be read, and takes responsibility for filtering these down
@ -58,6 +74,8 @@ define(
* @memberof platform/core * @memberof platform/core
* @constructor * @constructor
* @param {View[]} an array of view definitions * @param {View[]} an array of view definitions
* @param $log Angular's logging service
* @implements {ViewService}
*/ */
function ViewProvider(views, $log) { function ViewProvider(views, $log) {
@ -79,6 +97,13 @@ define(
return key; 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` // Check if an object has all capabilities designated as `needs`
// for a view. Exposing a capability via delegation is taken to // for a view. Exposing a capability via delegation is taken to
// satisfy this filter if `allowDelegation` is true. // satisfy this filter if `allowDelegation` is true.
@ -122,35 +147,16 @@ define(
return matches; return matches;
} }
function getViews(domainObject) { // First, filter views by type (matched to domain object type.)
var type = domainObject.useCapability("type"); // Second, filter by matching capabilities.
return this.views.filter(function (view) {
// First, filter views by type (matched to domain object type.) return viewMatchesType(view, type) && capabilitiesMatch(
// Second, filter by matching capabilities.
return views.filter(function (view) {
return viewMatchesType(view, type) && capabilitiesMatch(
domainObject, domainObject,
view.needs || [], view.needs || [],
view.delegation || false 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; return ViewProvider;
} }

View File

@ -25,7 +25,7 @@
* DomainObjectSpec. Created by vwoeltje on 11/6/14. * DomainObjectSpec. Created by vwoeltje on 11/6/14.
*/ */
define( define(
["../../src/objects/DomainObject"], ["../../src/objects/DomainObjectImpl"],
function (DomainObject) { function (DomainObject) {
"use strict"; "use strict";

View File

@ -23,7 +23,7 @@
define( define(
['../../src/types/TypeImpl'], ['../../src/types/TypeImpl'],
function (typeImpl) { function (TypeImpl) {
"use strict"; "use strict";
describe("Type definition wrapper", function () { describe("Type definition wrapper", function () {
@ -41,7 +41,7 @@ define(
properties: [ {} ], properties: [ {} ],
model: {someKey: "some value"} model: {someKey: "some value"}
}; };
type = typeImpl(testTypeDef); type = new TypeImpl(testTypeDef);
}); });
it("exposes key from definition", function () { it("exposes key from definition", function () {

View File

@ -128,7 +128,7 @@ define(
}); });
it("includes capabilities from undefined type in all types", function () { it("includes capabilities from undefined type in all types", function () {
captured.type = TypeProvider.instantiate( captured.type = new TypeProvider(
testTypeDefinitions.concat([ testTypeDefinitions.concat([
{ capabilities: ['a', 'b', 'c'] }, { capabilities: ['a', 'b', 'c'] },
{ capabilities: ['x', 'y', 'z'] } { capabilities: ['x', 'y', 'z'] }