mirror of
https://github.com/nasa/openmct.git
synced 2025-03-21 03:25:44 +00:00
[Code Style] Use prototypes in platform
WTD-1482
This commit is contained in:
parent
f377c7cb71
commit
b7765ff388
@ -108,41 +108,19 @@ define(
|
||||
* of action services
|
||||
*/
|
||||
function ActionAggregator(actionProviders) {
|
||||
|
||||
function getActions(context) {
|
||||
// Get all actions from all providers, reduce down
|
||||
// to one array by concatenation
|
||||
return actionProviders.map(function (provider) {
|
||||
return provider.getActions(context);
|
||||
}).reduce(function (a, b) {
|
||||
return a.concat(b);
|
||||
}, []);
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get a list of actions which are valid in a given
|
||||
* context.
|
||||
*
|
||||
* @param {ActionContext} the context in which
|
||||
* the action will occur; this is a
|
||||
* JavaScript object containing key-value
|
||||
* pairs. Typically, this will contain a
|
||||
* field "domainObject" which refers to
|
||||
* the domain object that will be acted
|
||||
* upon, but may contain arbitrary information
|
||||
* recognized by specific providers.
|
||||
* @return {Action[]} an array of actions which
|
||||
* may be performed in the provided context.
|
||||
*
|
||||
* @method
|
||||
* @memberof ActionAggregator
|
||||
* @memberof platform/core.ActionAggregator#
|
||||
*/
|
||||
getActions: getActions
|
||||
};
|
||||
this.actionProviders = actionProviders;
|
||||
}
|
||||
|
||||
ActionAggregator.prototype.getActions = function (context) {
|
||||
// Get all actions from all providers, reduce down
|
||||
// to one array by concatenation
|
||||
return this.actionProviders.map(function (provider) {
|
||||
return provider.getActions(context);
|
||||
}).reduce(function (a, b) {
|
||||
return a.concat(b);
|
||||
}, []);
|
||||
};
|
||||
|
||||
return ActionAggregator;
|
||||
}
|
||||
);
|
||||
|
@ -49,71 +49,69 @@ define(
|
||||
* @constructor
|
||||
*/
|
||||
function ActionCapability($q, actionService, domainObject) {
|
||||
this.$q = $q;
|
||||
this.actionService = actionService;
|
||||
this.domainObject = domainObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an action. This will find and perform the
|
||||
* first matching action available for the specified
|
||||
* context or key.
|
||||
*
|
||||
* @param {ActionContext|string} context the context in which
|
||||
* to perform the action; this is passed along to
|
||||
* the action service to match against available
|
||||
* actions. The "domainObject" field will automatically
|
||||
* be populated with the domain object that exposed
|
||||
* this capability. If given as a string, this will
|
||||
* be taken as the "key" field to match against
|
||||
* specific actions.
|
||||
* @returns {Promise} the result of the action that was
|
||||
* performed, or undefined if no matching action
|
||||
* was found.
|
||||
* @memberof platform/core.ActionCapability#
|
||||
*/
|
||||
ActionCapability.prototype.getActions = function (context) {
|
||||
// Get all actions which are valid in this context;
|
||||
// this simply redirects to the action service,
|
||||
// but additionally adds a domainObject field.
|
||||
function getActions(context) {
|
||||
var baseContext = typeof context === 'string' ?
|
||||
{ key: context } :
|
||||
(context || {}),
|
||||
actionContext = Object.create(baseContext);
|
||||
var baseContext = typeof context === 'string' ?
|
||||
{ key: context } : (context || {}),
|
||||
actionContext = Object.create(baseContext);
|
||||
|
||||
actionContext.domainObject = domainObject;
|
||||
actionContext.domainObject = this.domainObject;
|
||||
|
||||
return actionService.getActions(actionContext);
|
||||
}
|
||||
return this.actionService.getActions(actionContext);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get actions which are available for this domain object,
|
||||
* in this context.
|
||||
*
|
||||
* @param {ActionContext|string} context the context in which
|
||||
* to perform the action; this is passed along to
|
||||
* the action service to match against available
|
||||
* actions. The "domainObject" field will automatically
|
||||
* be populated with the domain object that exposed
|
||||
* this capability. If given as a string, this will
|
||||
* be taken as the "key" field to match against
|
||||
* specific actions.
|
||||
* @returns {Action[]} an array of matching actions
|
||||
* @memberof platform/core.ActionCapability#
|
||||
*/
|
||||
ActionCapability.prototype.perform = function (context) {
|
||||
// Alias to getActions(context)[0].perform, with a
|
||||
// check for empty arrays.
|
||||
function performAction(context) {
|
||||
var actions = getActions(context);
|
||||
var actions = this.getActions(context);
|
||||
|
||||
return $q.when(
|
||||
(actions && actions.length > 0) ?
|
||||
actions[0].perform() :
|
||||
undefined
|
||||
);
|
||||
}
|
||||
return this.$q.when(
|
||||
(actions && actions.length > 0) ?
|
||||
actions[0].perform() :
|
||||
undefined
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
/**
|
||||
* Perform an action. This will find and perform the
|
||||
* first matching action available for the specified
|
||||
* context or key.
|
||||
*
|
||||
* @param {ActionContext|string} context the context in which
|
||||
* to perform the action; this is passed along to
|
||||
* the action service to match against available
|
||||
* actions. The "domainObject" field will automatically
|
||||
* be populated with the domain object that exposed
|
||||
* this capability. If given as a string, this will
|
||||
* be taken as the "key" field to match against
|
||||
* specific actions.
|
||||
* @returns {Promise} the result of the action that was
|
||||
* performed, or undefined if no matching action
|
||||
* was found.
|
||||
* @memberof platform/core.ActionCapability#
|
||||
*/
|
||||
perform: performAction,
|
||||
/**
|
||||
* Get actions which are available for this domain object,
|
||||
* in this context.
|
||||
*
|
||||
* @param {ActionContext|string} context the context in which
|
||||
* to perform the action; this is passed along to
|
||||
* the action service to match against available
|
||||
* actions. The "domainObject" field will automatically
|
||||
* be populated with the domain object that exposed
|
||||
* this capability. If given as a string, this will
|
||||
* be taken as the "key" field to match against
|
||||
* specific actions.
|
||||
* @returns {Action[]} an array of matching actions
|
||||
* @memberof platform/core.ActionCapability#
|
||||
*/
|
||||
getActions: getActions
|
||||
};
|
||||
}
|
||||
|
||||
return ActionCapability;
|
||||
}
|
||||
|
@ -36,11 +36,45 @@ define(
|
||||
* category of extension.)
|
||||
*
|
||||
* @memberof platform/core
|
||||
* @imeplements {ActionService}
|
||||
* @constructor
|
||||
*/
|
||||
function ActionProvider(actions) {
|
||||
var actionsByKey = {},
|
||||
actionsByCategory = {};
|
||||
var self = this;
|
||||
|
||||
// Build up look-up tables
|
||||
this.actions = actions;
|
||||
this.actionsByKey = {};
|
||||
this.actionsByCategory = {};
|
||||
actions.forEach(function (Action) {
|
||||
// Get an action's category or categories
|
||||
var categories = Action.category || [];
|
||||
|
||||
// Convert to an array if necessary
|
||||
categories = Array.isArray(categories) ?
|
||||
categories : [categories];
|
||||
|
||||
// Store action under all relevant categories
|
||||
categories.forEach(function (category) {
|
||||
self.actionsByCategory[category] =
|
||||
self.actionsByCategory[category] || [];
|
||||
self.actionsByCategory[category].push(Action);
|
||||
});
|
||||
|
||||
// Store action by ekey as well
|
||||
if (Action.key) {
|
||||
self.actionsByKey[Action.key] =
|
||||
self.actionsByKey[Action.key] || [];
|
||||
self.actionsByKey[Action.key].push(Action);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ActionProvider.prototype.getActions = function (actionContext) {
|
||||
var context = (actionContext || {}),
|
||||
category = context.category,
|
||||
key = context.key,
|
||||
candidates;
|
||||
|
||||
// Instantiate an action; invokes the constructor and
|
||||
// additionally fills in the action's getMetadata method
|
||||
@ -71,86 +105,31 @@ define(
|
||||
function createIfApplicable(actions, context) {
|
||||
return (actions || []).filter(function (Action) {
|
||||
return Action.appliesTo ?
|
||||
Action.appliesTo(context) : true;
|
||||
Action.appliesTo(context) : true;
|
||||
}).map(function (Action) {
|
||||
return instantiateAction(Action, context);
|
||||
});
|
||||
}
|
||||
|
||||
// Get an array of actions that are valid in the supplied context.
|
||||
function getActions(actionContext) {
|
||||
var context = (actionContext || {}),
|
||||
category = context.category,
|
||||
key = context.key,
|
||||
candidates;
|
||||
|
||||
// Match actions to the provided context by comparing "key"
|
||||
// and/or "category" parameters, if specified.
|
||||
candidates = actions;
|
||||
if (key) {
|
||||
candidates = actionsByKey[key];
|
||||
if (category) {
|
||||
candidates = candidates.filter(function (Action) {
|
||||
return Action.category === category;
|
||||
});
|
||||
}
|
||||
} else if (category) {
|
||||
candidates = actionsByCategory[category];
|
||||
// Match actions to the provided context by comparing "key"
|
||||
// and/or "category" parameters, if specified.
|
||||
candidates = this.actions;
|
||||
if (key) {
|
||||
candidates = this.actionsByKey[key];
|
||||
if (category) {
|
||||
candidates = candidates.filter(function (Action) {
|
||||
return Action.category === category;
|
||||
});
|
||||
}
|
||||
|
||||
// Instantiate those remaining actions, with additional
|
||||
// filtering per any appliesTo methods defined on those
|
||||
// actions.
|
||||
return createIfApplicable(candidates, context);
|
||||
} else if (category) {
|
||||
candidates = this.actionsByCategory[category];
|
||||
}
|
||||
|
||||
// Build up look-up tables
|
||||
actions.forEach(function (Action) {
|
||||
// Get an action's category or categories
|
||||
var categories = Action.category || [];
|
||||
|
||||
// Convert to an array if necessary
|
||||
categories = Array.isArray(categories) ?
|
||||
categories : [categories];
|
||||
|
||||
// Store action under all relevant categories
|
||||
categories.forEach(function (category) {
|
||||
actionsByCategory[category] =
|
||||
actionsByCategory[category] || [];
|
||||
actionsByCategory[category].push(Action);
|
||||
});
|
||||
|
||||
// Store action by ekey as well
|
||||
if (Action.key) {
|
||||
actionsByKey[Action.key] =
|
||||
actionsByKey[Action.key] || [];
|
||||
actionsByKey[Action.key].push(Action);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get a list of actions which are valid in a given
|
||||
* context.
|
||||
*
|
||||
* @param {ActionContext} the context in which
|
||||
* the action will occur; this is a
|
||||
* JavaScript object containing key-value
|
||||
* pairs. Typically, this will contain a
|
||||
* field "domainObject" which refers to
|
||||
* the domain object that will be acted
|
||||
* upon, but may contain arbitrary information
|
||||
* recognized by specific providers.
|
||||
* @return {Action[]} an array of actions which
|
||||
* may be performed in the provided context.
|
||||
*
|
||||
* @method
|
||||
* @memberof ActionProvider
|
||||
* @memberof platform/core.ActionProvider#
|
||||
*/
|
||||
getActions: getActions
|
||||
};
|
||||
}
|
||||
// Instantiate those remaining actions, with additional
|
||||
// filtering per any appliesTo methods defined on those
|
||||
// actions.
|
||||
return createIfApplicable(candidates, context);
|
||||
};
|
||||
|
||||
return ActionProvider;
|
||||
}
|
||||
|
@ -36,8 +36,19 @@ define(
|
||||
*
|
||||
* @memberof platform/core
|
||||
* @constructor
|
||||
* @implements {ActionService}
|
||||
* @param $log Angular's logging service
|
||||
* @param {ActionService} actionService the decorated action service
|
||||
*/
|
||||
function LoggingActionDecorator($log, actionService) {
|
||||
this.$log = $log;
|
||||
this.actionService = actionService;
|
||||
}
|
||||
|
||||
LoggingActionDecorator.prototype.getActions = function () {
|
||||
var actionService = this.actionService,
|
||||
$log = this.$log;
|
||||
|
||||
// Decorate the perform method of the specified action, such that
|
||||
// it emits a log message whenever performed.
|
||||
function addLogging(action) {
|
||||
@ -59,35 +70,11 @@ define(
|
||||
return logAction;
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get a list of actions which are valid in a given
|
||||
* context. These actions will additionally log
|
||||
* themselves when performed.
|
||||
*
|
||||
* @param {ActionContext} the context in which
|
||||
* the action will occur; this is a
|
||||
* JavaScript object containing key-value
|
||||
* pairs. Typically, this will contain a
|
||||
* field "domainObject" which refers to
|
||||
* the domain object that will be acted
|
||||
* upon, but may contain arbitrary information
|
||||
* recognized by specific providers.
|
||||
* @return {Action[]} an array of actions which
|
||||
* may be performed in the provided context.
|
||||
*
|
||||
* @method
|
||||
* @memberof LoggingActionDecorator
|
||||
* @memberof platform/core.LoggingActionDecorator#
|
||||
*/
|
||||
getActions: function () {
|
||||
return actionService.getActions.apply(
|
||||
actionService,
|
||||
arguments
|
||||
).map(addLogging);
|
||||
}
|
||||
};
|
||||
}
|
||||
return actionService.getActions.apply(
|
||||
actionService,
|
||||
arguments
|
||||
).map(addLogging);
|
||||
};
|
||||
|
||||
return LoggingActionDecorator;
|
||||
}
|
||||
|
@ -39,68 +39,59 @@ define(
|
||||
*
|
||||
* @memberof platform/core
|
||||
* @constructor
|
||||
* @implements {Capability}
|
||||
*/
|
||||
function CompositionCapability($injector, domainObject) {
|
||||
var objectService,
|
||||
lastPromise,
|
||||
lastModified;
|
||||
|
||||
// Get a reference to the object service from $injector
|
||||
function injectObjectService() {
|
||||
objectService = $injector.get("objectService");
|
||||
return objectService;
|
||||
}
|
||||
|
||||
// Get a reference to the object service (either cached or
|
||||
// from the injector)
|
||||
function getObjectService() {
|
||||
return objectService || injectObjectService();
|
||||
}
|
||||
|
||||
// Promise this domain object's composition (an array of domain
|
||||
// object instances corresponding to ids in its model.)
|
||||
function promiseComposition() {
|
||||
var model = domainObject.getModel(),
|
||||
ids;
|
||||
|
||||
// Then filter out non-existent objects,
|
||||
// and wrap others (such that they expose a
|
||||
// "context" capability)
|
||||
function contextualize(objects) {
|
||||
return ids.filter(function (id) {
|
||||
return objects[id];
|
||||
}).map(function (id) {
|
||||
return new ContextualDomainObject(
|
||||
objects[id],
|
||||
domainObject
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Make a new request if we haven't made one, or if the
|
||||
// object has been modified.
|
||||
if (!lastPromise || lastModified !== model.modified) {
|
||||
ids = model.composition || [];
|
||||
lastModified = model.modified;
|
||||
// Load from the underlying object service
|
||||
lastPromise = getObjectService().getObjects(ids)
|
||||
.then(contextualize);
|
||||
}
|
||||
|
||||
return lastPromise;
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Request the composition of this object.
|
||||
* @returns {Promise.<DomainObject[]>} a list of all domain
|
||||
* objects which compose this domain object.
|
||||
* @memberof platform/core.CompositionCapability#
|
||||
*/
|
||||
invoke: promiseComposition
|
||||
this.injectObjectService = function () {
|
||||
this.objectService = $injector.get("objectService");
|
||||
};
|
||||
|
||||
this.domainObject = domainObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the composition of this object.
|
||||
* @returns {Promise.<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
|
||||
* by a domain object based on its model. Checks for the presence of
|
||||
|
@ -38,79 +38,76 @@ define(
|
||||
*
|
||||
* @memberof platform/core
|
||||
* @constructor
|
||||
* @implements {Capability}
|
||||
*/
|
||||
function ContextCapability(parentObject, domainObject) {
|
||||
return {
|
||||
/**
|
||||
* Get the immediate parent of a domain object.
|
||||
*
|
||||
* A domain object may be contained in multiple places; its
|
||||
* parent (as exposed by this capability) is the domain
|
||||
* object from which this object was accessed, usually
|
||||
* by way of a `composition` capability.
|
||||
*
|
||||
* @returns {DomainObject} the immediate parent of this
|
||||
* domain object.
|
||||
* @memberof platform/core.ContextCapability#
|
||||
*/
|
||||
getParent: function () {
|
||||
return parentObject;
|
||||
},
|
||||
/**
|
||||
* Get an array containing the complete direct ancestry
|
||||
* of this domain object, including the domain object
|
||||
* itself.
|
||||
*
|
||||
* A domain object may be contained in multiple places; its
|
||||
* parent and all ancestors (as exposed by this capability)
|
||||
* serve as a record of how this specific domain object
|
||||
* instance was reached.
|
||||
*
|
||||
* The first element in the returned array is the deepest
|
||||
* ancestor; subsequent elements are progressively more
|
||||
* recent ancestors, with the domain object which exposed
|
||||
* the capability occupying the last element of the array.
|
||||
*
|
||||
* @returns {DomainObject[]} the full composition ancestry
|
||||
* of the domain object which exposed this
|
||||
* capability.
|
||||
* @memberof platform/core.ContextCapability#
|
||||
*/
|
||||
getPath: function () {
|
||||
var parentPath = [],
|
||||
parentContext;
|
||||
|
||||
if (parentObject) {
|
||||
parentContext = parentObject.getCapability("context");
|
||||
parentPath = parentContext ?
|
||||
parentContext.getPath() :
|
||||
[parentObject];
|
||||
}
|
||||
|
||||
return parentPath.concat([domainObject]);
|
||||
},
|
||||
/**
|
||||
* Get the deepest ancestor available for this domain object;
|
||||
* equivalent to `getPath()[0]`.
|
||||
*
|
||||
* See notes on `getPath()` for how ancestry is defined in
|
||||
* the context of this capability.
|
||||
*
|
||||
* @returns {DomainObject} the deepest ancestor of the domain
|
||||
* object which exposed this capability.
|
||||
* @memberof platform/core.ContextCapability#
|
||||
*/
|
||||
getRoot: function () {
|
||||
var parentContext = parentObject &&
|
||||
parentObject.getCapability('context');
|
||||
|
||||
return parentContext ?
|
||||
parentContext.getRoot() :
|
||||
(parentObject || domainObject);
|
||||
}
|
||||
};
|
||||
this.parentObject = parentObject;
|
||||
this.domainObject = domainObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the immediate parent of a domain object.
|
||||
*
|
||||
* A domain object may be contained in multiple places; its
|
||||
* parent (as exposed by this capability) is the domain
|
||||
* object from which this object was accessed, usually
|
||||
* by way of a `composition` capability.
|
||||
*
|
||||
* @returns {DomainObject} the immediate parent of this
|
||||
* domain object.
|
||||
*/
|
||||
ContextCapability.prototype.getParent = function () {
|
||||
return this.parentObject;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get an array containing the complete direct ancestry
|
||||
* of this domain object, including the domain object
|
||||
* itself.
|
||||
*
|
||||
* A domain object may be contained in multiple places; its
|
||||
* parent and all ancestors (as exposed by this capability)
|
||||
* serve as a record of how this specific domain object
|
||||
* instance was reached.
|
||||
*
|
||||
* The first element in the returned array is the deepest
|
||||
* ancestor; subsequent elements are progressively more
|
||||
* recent ancestors, with the domain object which exposed
|
||||
* the capability occupying the last element of the array.
|
||||
*
|
||||
* @returns {DomainObject[]} the full composition ancestry
|
||||
* of the domain object which exposed this
|
||||
* capability.
|
||||
*/
|
||||
ContextCapability.prototype.getPath = function () {
|
||||
var parentObject = this.parentObject,
|
||||
parentContext =
|
||||
parentObject && parentObject.getCapability('context'),
|
||||
parentPath = parentContext ?
|
||||
parentContext.getPath() : [ this.parentObject ];
|
||||
|
||||
return parentPath.concat([this.domainObject]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the deepest ancestor available for this domain object;
|
||||
* equivalent to `getPath()[0]`.
|
||||
*
|
||||
* See notes on `getPath()` for how ancestry is defined in
|
||||
* the context of this capability.
|
||||
*
|
||||
* @returns {DomainObject} the deepest ancestor of the domain
|
||||
* object which exposed this capability.
|
||||
*/
|
||||
ContextCapability.prototype.getRoot = function () {
|
||||
var parentContext = this.parentObject &&
|
||||
this.parentObject.getCapability('context');
|
||||
|
||||
return parentContext ?
|
||||
parentContext.getRoot() :
|
||||
(this.parentObject || this.domainObject);
|
||||
};
|
||||
|
||||
return ContextCapability;
|
||||
}
|
||||
);
|
||||
|
@ -44,6 +44,7 @@ define(
|
||||
*
|
||||
* @memberof platform/core
|
||||
* @constructor
|
||||
* @implements {DomainObject}
|
||||
*/
|
||||
function ContextualDomainObject(domainObject, parentObject) {
|
||||
// Prototypally inherit from the domain object, and
|
||||
|
@ -29,6 +29,19 @@ define(
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* A capability provides an interface with dealing with some
|
||||
* dynamic behavior associated with a domain object.
|
||||
* @interface Capability
|
||||
*/
|
||||
|
||||
/**
|
||||
* Optional; if present, will be used by `DomainObject#useCapability`
|
||||
* to simplify interaction with a specific capability. Parameters
|
||||
* and return values vary depending on capability type.
|
||||
* @method Capability#invoke
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides capabilities based on extension definitions,
|
||||
* matched to domain object models.
|
||||
|
@ -45,13 +45,40 @@ define(
|
||||
* in the type's definition, which contains an array of names of
|
||||
* capabilities to be delegated.
|
||||
*
|
||||
* @param domainObject
|
||||
* @param $q Angular's $q, for promises
|
||||
* @param {DomainObject} domainObject the delegating domain object
|
||||
* @memberof platform/core
|
||||
* @constructor
|
||||
* @implements {Capability}
|
||||
*/
|
||||
function DelegationCapability($q, domainObject) {
|
||||
var delegateCapabilities = {},
|
||||
type = domainObject.getCapability("type");
|
||||
var type = domainObject.getCapability("type"),
|
||||
self = this;
|
||||
|
||||
this.$q = $q;
|
||||
this.delegateCapabilities = {};
|
||||
this.domainObject = domainObject;
|
||||
|
||||
// Generate set for easy lookup of capability delegation
|
||||
if (type && type.getDefinition) {
|
||||
(type.getDefinition().delegates || []).forEach(function (key) {
|
||||
self.delegateCapabilities[key] = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the domain objects which are intended to be delegated
|
||||
* responsibility for some specific capability.
|
||||
*
|
||||
* @param {string} key the name of the delegated capability
|
||||
* @returns {DomainObject[]} the domain objects to which
|
||||
* responsibility for this capability is delegated.
|
||||
* @memberof platform/core.DelegationCapability#
|
||||
*/
|
||||
DelegationCapability.prototype.getDelegates = function (key) {
|
||||
var domainObject = this.domainObject;
|
||||
|
||||
function filterObjectsWithCapability(capability) {
|
||||
return function (objects) {
|
||||
@ -65,55 +92,40 @@ define(
|
||||
return domainObject.useCapability('composition');
|
||||
}
|
||||
|
||||
function doesDelegate(key) {
|
||||
return delegateCapabilities[key] || false;
|
||||
}
|
||||
return this.doesDelegateCapability(key) ?
|
||||
promiseChildren().then(
|
||||
filterObjectsWithCapability(key)
|
||||
) :
|
||||
this.$q.when([]);
|
||||
};
|
||||
|
||||
function getDelegates(capability) {
|
||||
return doesDelegate(capability) ?
|
||||
promiseChildren().then(
|
||||
filterObjectsWithCapability(capability)
|
||||
) :
|
||||
$q.when([]);
|
||||
}
|
||||
|
||||
// Generate set for easy lookup of capability delegation
|
||||
if (type && type.getDefinition) {
|
||||
(type.getDefinition().delegates || []).forEach(function (key) {
|
||||
delegateCapabilities[key] = true;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Invoke this capability; alias of `getDelegates`, used to
|
||||
* simplify usage, e.g.:
|
||||
*
|
||||
* `domainObject.useCapability("delegation", "telemetry")`
|
||||
*
|
||||
* ...will retrieve all members of a domain object's
|
||||
* composition which have a "telemetry" capability.
|
||||
*
|
||||
* @param {string} the name of the delegated capability
|
||||
* @returns {DomainObject[]} the domain objects to which
|
||||
* responsibility for this capability is delegated.
|
||||
* @memberof platform/core.DelegationCapability#
|
||||
*/
|
||||
invoke: getDelegates,
|
||||
/**
|
||||
* Get the domain objects which are intended to be delegated
|
||||
* responsibility for some specific capability.
|
||||
*
|
||||
* @param {string} the name of the delegated capability
|
||||
* @returns {DomainObject[]} the domain objects to which
|
||||
* responsibility for this capability is delegated.
|
||||
* @memberof platform/core.DelegationCapability#
|
||||
*/
|
||||
getDelegates: getDelegates,
|
||||
doesDelegateCapability: doesDelegate
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Check if the domain object which exposed this capability
|
||||
* wishes to delegate another capability.
|
||||
*
|
||||
* @param {string} key the capability to check for
|
||||
* @returns {boolean} true if the capability is delegated
|
||||
*/
|
||||
DelegationCapability.prototype.doesDelegateCapability = function (key) {
|
||||
return !!(this.delegateCapabilities[key]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Invoke this capability; alias of `getDelegates`, used to
|
||||
* simplify usage, e.g.:
|
||||
*
|
||||
* `domainObject.useCapability("delegation", "telemetry")`
|
||||
*
|
||||
* ...will retrieve all members of a domain object's
|
||||
* composition which have a "telemetry" capability.
|
||||
*
|
||||
* @param {string} the name of the delegated capability
|
||||
* @returns {DomainObject[]} the domain objects to which
|
||||
* responsibility for this capability is delegated.
|
||||
* @memberof platform/core.DelegationCapability#
|
||||
*/
|
||||
DelegationCapability.prototype.invoke =
|
||||
DelegationCapability.prototype.getDelegates;
|
||||
|
||||
return DelegationCapability;
|
||||
|
||||
|
@ -11,8 +11,6 @@ define(
|
||||
* @property {string} name the human-readable name of this property
|
||||
* @property {string} value the human-readable value of this property,
|
||||
* for this specific domain object
|
||||
* @constructor
|
||||
* @memberof platform/core
|
||||
*/
|
||||
|
||||
var TIME_FORMAT = "YYYY-MM-DD HH:mm:ss";
|
||||
@ -27,10 +25,23 @@ define(
|
||||
* `value` properties describing that domain object (suitable for
|
||||
* display.)
|
||||
*
|
||||
* @param {DomainObject} domainObject the domain object whose
|
||||
* metadata is to be exposed
|
||||
* @implements {Capability}
|
||||
* @constructor
|
||||
* @memberof platform/core
|
||||
*/
|
||||
function MetadataCapability(domainObject) {
|
||||
var model = domainObject.getModel();
|
||||
this.domainObject = domainObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get metadata about this object.
|
||||
* @returns {MetadataProperty[]} metadata about this object
|
||||
*/
|
||||
MetadataCapability.prototype.invoke = function () {
|
||||
var domainObject = this.domainObject,
|
||||
model = domainObject.getModel();
|
||||
|
||||
function hasDisplayableValue(metadataProperty) {
|
||||
var t = typeof metadataProperty.value;
|
||||
@ -39,8 +50,8 @@ define(
|
||||
|
||||
function formatTimestamp(timestamp) {
|
||||
return typeof timestamp === 'number' ?
|
||||
(moment.utc(timestamp).format(TIME_FORMAT) + " UTC") :
|
||||
undefined;
|
||||
(moment.utc(timestamp).format(TIME_FORMAT) + " UTC") :
|
||||
undefined;
|
||||
}
|
||||
|
||||
function getProperties() {
|
||||
@ -75,20 +86,9 @@ define(
|
||||
];
|
||||
}
|
||||
|
||||
function getMetadata() {
|
||||
return getProperties().concat(getCommonMetadata())
|
||||
.filter(hasDisplayableValue);
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get metadata about this object.
|
||||
* @returns {MetadataProperty[]} metadata about this object
|
||||
* @memberof platform/core.MetadataCapability#
|
||||
*/
|
||||
invoke: getMetadata
|
||||
};
|
||||
}
|
||||
return getProperties().concat(getCommonMetadata())
|
||||
.filter(hasDisplayableValue);
|
||||
};
|
||||
|
||||
return MetadataCapability;
|
||||
}
|
||||
|
@ -69,100 +69,103 @@ define(
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param {Function} topic a service for creating listeners
|
||||
* @param {Function} now a service to get the current time
|
||||
* @param {DomainObject} domainObject the domain object
|
||||
* which will expose this capability
|
||||
* @memberof platform/core
|
||||
* @constructor
|
||||
* @implements {Capability}
|
||||
*/
|
||||
function MutationCapability(topic, now, domainObject) {
|
||||
var t = topic(TOPIC_PREFIX + domainObject.getId());
|
||||
this.mutationTopic = topic(TOPIC_PREFIX + domainObject.getId());
|
||||
this.now = now;
|
||||
this.domainObject = domainObject;
|
||||
}
|
||||
|
||||
function mutate(mutator, timestamp) {
|
||||
// Get the object's model and clone it, so the
|
||||
// mutator function has a temporary copy to work with.
|
||||
var model = domainObject.getModel(),
|
||||
clone = JSON.parse(JSON.stringify(model)),
|
||||
useTimestamp = arguments.length > 1;
|
||||
/**
|
||||
* Modify the domain object's model, using a provided
|
||||
* function. This function will receive a copy of the
|
||||
* domain object's model as an argument; behavior
|
||||
* varies depending on that function's return value:
|
||||
*
|
||||
* * If no value (or undefined) is returned by the mutator,
|
||||
* the state of the model object delivered as the mutator's
|
||||
* argument will become the domain object's new model.
|
||||
* This is useful for writing code that modifies the model
|
||||
* directly.
|
||||
* * If a plain object is returned, that object will be used
|
||||
* as the domain object's new model.
|
||||
* * If boolean `false` is returned, the mutation will be
|
||||
* cancelled.
|
||||
* * If a promise is returned, its resolved value will be
|
||||
* handled as one of the above.
|
||||
*
|
||||
*
|
||||
* @param {Function} mutator the function which will make
|
||||
* changes to the domain object's model.
|
||||
* @param {number} [timestamp] timestamp to record for
|
||||
* this mutation (otherwise, system time will be
|
||||
* used)
|
||||
* @returns {Promise.<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 handleMutation(mutationResult) {
|
||||
// If mutation result was undefined, just use
|
||||
// the clone; this allows the mutator to omit return
|
||||
// values and just change the model directly.
|
||||
var result = mutationResult || clone;
|
||||
// Function to handle copying values to the actual
|
||||
function handleMutation(mutationResult) {
|
||||
// If mutation result was undefined, just use
|
||||
// the clone; this allows the mutator to omit return
|
||||
// values and just change the model directly.
|
||||
var result = mutationResult || clone;
|
||||
|
||||
// Allow mutators to change their mind by
|
||||
// returning false.
|
||||
if (mutationResult !== false) {
|
||||
// Copy values if result was a different object
|
||||
// (either our clone or some other new thing)
|
||||
if (model !== result) {
|
||||
copyValues(model, result);
|
||||
}
|
||||
model.modified = useTimestamp ? timestamp : now();
|
||||
t.notify(model);
|
||||
// Allow mutators to change their mind by
|
||||
// returning false.
|
||||
if (mutationResult !== false) {
|
||||
// Copy values if result was a different object
|
||||
// (either our clone or some other new thing)
|
||||
if (model !== result) {
|
||||
copyValues(model, result);
|
||||
}
|
||||
|
||||
// Report the result of the mutation
|
||||
return mutationResult !== false;
|
||||
model.modified = useTimestamp ? timestamp : now();
|
||||
t.notify(model);
|
||||
}
|
||||
|
||||
// Invoke the provided mutator, then make changes to
|
||||
// the underlying model (if applicable.)
|
||||
return fastPromise(mutator(clone)).then(handleMutation);
|
||||
// Report the result of the mutation
|
||||
return mutationResult !== false;
|
||||
}
|
||||
|
||||
function listen(listener) {
|
||||
return t.listen(listener);
|
||||
}
|
||||
// Invoke the provided mutator, then make changes to
|
||||
// the underlying model (if applicable.)
|
||||
return fastPromise(mutator(clone)).then(handleMutation);
|
||||
};
|
||||
|
||||
return {
|
||||
/**
|
||||
* Alias of `mutate`, used to support useCapability.
|
||||
* @memberof platform/core.MutationCapability#
|
||||
*/
|
||||
invoke: mutate,
|
||||
/**
|
||||
* Modify the domain object's model, using a provided
|
||||
* function. This function will receive a copy of the
|
||||
* domain object's model as an argument; behavior
|
||||
* varies depending on that function's return value:
|
||||
*
|
||||
* * If no value (or undefined) is returned by the mutator,
|
||||
* the state of the model object delivered as the mutator's
|
||||
* argument will become the domain object's new model.
|
||||
* This is useful for writing code that modifies the model
|
||||
* directly.
|
||||
* * If a plain object is returned, that object will be used
|
||||
* as the domain object's new model.
|
||||
* * If boolean `false` is returned, the mutation will be
|
||||
* cancelled.
|
||||
* * If a promise is returned, its resolved value will be
|
||||
* handled as one of the above.
|
||||
*
|
||||
*
|
||||
* @param {function} mutator the function which will make
|
||||
* changes to the domain object's model.
|
||||
* @param {number} [timestamp] timestamp to record for
|
||||
* this mutation (otherwise, system time will be
|
||||
* used)
|
||||
* @returns {Promise.<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
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Listen for mutations of this domain object's model.
|
||||
* The provided listener will be invoked with the domain
|
||||
* object's new model after any changes. To stop listening,
|
||||
* invoke the function returned by this method.
|
||||
* @param {Function} listener function to call on mutation
|
||||
* @returns {Function} a function to stop listening
|
||||
* @memberof platform/core.MutationCapability#
|
||||
*/
|
||||
MutationCapability.prototype.listen = function (listener) {
|
||||
return this.mutationTopic.listen(listener);
|
||||
};
|
||||
|
||||
/**
|
||||
* Alias of `mutate`, used to support useCapability.
|
||||
*/
|
||||
MutationCapability.prototype.invoke =
|
||||
MutationCapability.prototype.mutate;
|
||||
|
||||
return MutationCapability;
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ define(
|
||||
*
|
||||
* @param {PersistenceService} persistenceService the underlying
|
||||
* provider of persistence capabilities.
|
||||
* @param {string} SPACE the name of the persistence space to
|
||||
* @param {string} space the name of the persistence space to
|
||||
* use (this is an arbitrary string, useful in principle
|
||||
* for distinguishing different persistence stores from
|
||||
* one another.)
|
||||
@ -42,10 +42,60 @@ define(
|
||||
*
|
||||
* @memberof platform/core
|
||||
* @constructor
|
||||
* @implements {Capability}
|
||||
*/
|
||||
function PersistenceCapability(persistenceService, SPACE, domainObject) {
|
||||
function PersistenceCapability(persistenceService, space, domainObject) {
|
||||
// Cache modified timestamp
|
||||
var modified = domainObject.getModel().modified;
|
||||
this.modified = domainObject.getModel().modified;
|
||||
|
||||
this.domainObject = domainObject;
|
||||
this.space = space;
|
||||
this.persistenceService = persistenceService;
|
||||
}
|
||||
|
||||
// Utility function for creating promise-like objects which
|
||||
// resolve synchronously when possible
|
||||
function fastPromise(value) {
|
||||
return (value || {}).then ? value : {
|
||||
then: function (callback) {
|
||||
return fastPromise(callback(value));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist any changes which have been made to this
|
||||
* domain object's model.
|
||||
* @returns {Promise} a promise which will be resolved
|
||||
* if persistence is successful, and rejected
|
||||
* if not.
|
||||
*/
|
||||
PersistenceCapability.prototype.persist = function () {
|
||||
var domainObject = this.domainObject,
|
||||
modified = domainObject.getModel().modified;
|
||||
|
||||
// Update persistence timestamp...
|
||||
domainObject.useCapability("mutation", function (model) {
|
||||
model.persisted = modified;
|
||||
}, modified);
|
||||
|
||||
// ...and persist
|
||||
return this.persistenceService.updateObject(
|
||||
this.getSpace(),
|
||||
domainObject.getId(),
|
||||
domainObject.getModel()
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update this domain object to match the latest from
|
||||
* persistence.
|
||||
* @returns {Promise} a promise which will be resolved
|
||||
* when the update is complete
|
||||
*/
|
||||
PersistenceCapability.prototype.refresh = function () {
|
||||
var domainObject = this.domainObject,
|
||||
model = domainObject.getModel();
|
||||
|
||||
// Update a domain object's model upon refresh
|
||||
function updateModel(model) {
|
||||
@ -55,75 +105,28 @@ define(
|
||||
}, modified);
|
||||
}
|
||||
|
||||
// For refresh; update a domain object model, only if there
|
||||
// are no unsaved changes.
|
||||
function updatePersistenceTimestamp() {
|
||||
var modified = domainObject.getModel().modified;
|
||||
domainObject.useCapability("mutation", function (model) {
|
||||
model.persisted = modified;
|
||||
}, modified);
|
||||
}
|
||||
// Only update if we don't have unsaved changes
|
||||
return (model.modified === model.persisted) ?
|
||||
this.persistenceService.readObject(
|
||||
this.getSpace(),
|
||||
this.domainObject.getId()
|
||||
).then(updateModel) :
|
||||
fastPromise(false);
|
||||
};
|
||||
|
||||
// Utility function for creating promise-like objects which
|
||||
// resolve synchronously when possible
|
||||
function fastPromise(value) {
|
||||
return (value || {}).then ? value : {
|
||||
then: function (callback) {
|
||||
return fastPromise(callback(value));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Persist any changes which have been made to this
|
||||
* domain object's model.
|
||||
* @returns {Promise} a promise which will be resolved
|
||||
* if persistence is successful, and rejected
|
||||
* if not.
|
||||
* @memberof platform/core.PersistenceCapability#
|
||||
*/
|
||||
persist: function () {
|
||||
updatePersistenceTimestamp();
|
||||
return persistenceService.updateObject(
|
||||
SPACE,
|
||||
domainObject.getId(),
|
||||
domainObject.getModel()
|
||||
);
|
||||
},
|
||||
/**
|
||||
* Update this domain object to match the latest from
|
||||
* persistence.
|
||||
* @returns {Promise} a promise which will be resolved
|
||||
* when the update is complete
|
||||
* @memberof platform/core.PersistenceCapability#
|
||||
*/
|
||||
refresh: function () {
|
||||
var model = domainObject.getModel();
|
||||
// Only update if we don't have unsaved changes
|
||||
return (model.modified === model.persisted) ?
|
||||
persistenceService.readObject(
|
||||
SPACE,
|
||||
domainObject.getId()
|
||||
).then(updateModel) :
|
||||
fastPromise(false);
|
||||
},
|
||||
/**
|
||||
* Get the space in which this domain object is persisted;
|
||||
* this is useful when, for example, decided which space a
|
||||
* newly-created domain object should be persisted to (by
|
||||
* default, this should be the space of its containing
|
||||
* object.)
|
||||
*
|
||||
* @returns {string} the name of the space which should
|
||||
* be used to persist this object
|
||||
* @memberof platform/core.PersistenceCapability#
|
||||
*/
|
||||
getSpace: function () {
|
||||
return SPACE;
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Get the space in which this domain object is persisted;
|
||||
* this is useful when, for example, decided which space a
|
||||
* newly-created domain object should be persisted to (by
|
||||
* default, this should be the space of its containing
|
||||
* object.)
|
||||
*
|
||||
* @returns {string} the name of the space which should
|
||||
* be used to persist this object
|
||||
*/
|
||||
PersistenceCapability.prototype.getSpace = function () {
|
||||
return this.space;
|
||||
};
|
||||
|
||||
return PersistenceCapability;
|
||||
}
|
||||
|
@ -40,92 +40,80 @@ define(
|
||||
*
|
||||
* @memberof platform/core
|
||||
* @constructor
|
||||
* @implements {Capability}
|
||||
*/
|
||||
function RelationshipCapability($injector, domainObject) {
|
||||
var objectService,
|
||||
lastPromise = {},
|
||||
lastModified;
|
||||
|
||||
// Get a reference to the object service from $injector
|
||||
function injectObjectService() {
|
||||
objectService = $injector.get("objectService");
|
||||
return objectService;
|
||||
}
|
||||
|
||||
// Get a reference to the object service (either cached or
|
||||
// from the injector)
|
||||
function getObjectService() {
|
||||
return objectService || injectObjectService();
|
||||
}
|
||||
|
||||
// Promise this domain object's composition (an array of domain
|
||||
// object instances corresponding to ids in its model.)
|
||||
function promiseRelationships(key) {
|
||||
var model = domainObject.getModel(),
|
||||
ids;
|
||||
|
||||
// Package objects as an array
|
||||
function packageObject(objects) {
|
||||
return ids.map(function (id) {
|
||||
return objects[id];
|
||||
}).filter(function (obj) {
|
||||
return obj;
|
||||
});
|
||||
}
|
||||
|
||||
// Clear cached promises if modification has occurred
|
||||
if (lastModified !== model.modified) {
|
||||
lastPromise = {};
|
||||
lastModified = model.modified;
|
||||
}
|
||||
|
||||
// Make a new request if needed
|
||||
if (!lastPromise[key]) {
|
||||
ids = (model.relationships || {})[key] || [];
|
||||
lastModified = model.modified;
|
||||
// Load from the underlying object service
|
||||
lastPromise[key] = getObjectService().getObjects(ids)
|
||||
.then(packageObject);
|
||||
}
|
||||
|
||||
return lastPromise[key];
|
||||
}
|
||||
|
||||
// List types of relationships which this object has
|
||||
function listRelationships() {
|
||||
var relationships =
|
||||
(domainObject.getModel() || {}).relationships || {};
|
||||
|
||||
// Check if this key really does expose an array of ids
|
||||
// (to filter out malformed relationships)
|
||||
function isArray(key) {
|
||||
return Array.isArray(relationships[key]);
|
||||
}
|
||||
|
||||
return Object.keys(relationships).filter(isArray).sort();
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* List all types of relationships exposed by this
|
||||
* object.
|
||||
* @returns {string[]} a list of all relationship types
|
||||
* @memberof platform/core.RelationshipCapability#
|
||||
*/
|
||||
listRelationships: listRelationships,
|
||||
/**
|
||||
* Request related objects, with a given relationship type.
|
||||
* This will typically require asynchronous lookup, so this
|
||||
* returns a promise.
|
||||
* @param {string} key the type of relationship
|
||||
* @returns {Promise.<DomainObject[]>} a promise for related
|
||||
* domain objects
|
||||
* @memberof platform/core.RelationshipCapability#
|
||||
*/
|
||||
getRelatedObjects: promiseRelationships
|
||||
this.injectObjectService = function () {
|
||||
this.objectService = $injector.get("objectService");
|
||||
};
|
||||
|
||||
this.lastPromise = {};
|
||||
this.domainObject = domainObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* List all types of relationships exposed by this
|
||||
* object.
|
||||
* @returns {string[]} a list of all relationship types
|
||||
*/
|
||||
RelationshipCapability.prototype.listRelationships = function listRelationships() {
|
||||
var relationships =
|
||||
(this.domainObject.getModel() || {}).relationships || {};
|
||||
|
||||
// Check if this key really does expose an array of ids
|
||||
// (to filter out malformed relationships)
|
||||
function isArray(key) {
|
||||
return Array.isArray(relationships[key]);
|
||||
}
|
||||
|
||||
return Object.keys(relationships).filter(isArray).sort();
|
||||
};
|
||||
|
||||
/**
|
||||
* Request related objects, with a given relationship type.
|
||||
* This will typically require asynchronous lookup, so this
|
||||
* returns a promise.
|
||||
* @param {string} key the type of relationship
|
||||
* @returns {Promise.<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
|
||||
* by a domain object based on its model. Checks for the presence of
|
||||
|
@ -32,10 +32,30 @@ define(
|
||||
* object are not provided.
|
||||
* @memberof platform/core
|
||||
* @constructor
|
||||
* @param {ModelService} modelService this service to decorate
|
||||
* @implements {ModelService}
|
||||
*/
|
||||
function CachingModelDecorator(modelService) {
|
||||
var cache = {},
|
||||
cached = {};
|
||||
this.cache = {};
|
||||
this.cached = {};
|
||||
this.modelService = modelService;
|
||||
}
|
||||
|
||||
// Fast-resolving promise
|
||||
function fastPromise(value) {
|
||||
return (value || {}).then ? value : {
|
||||
then: function (callback) {
|
||||
return fastPromise(callback(value));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
CachingModelDecorator.prototype.getModels = function (ids) {
|
||||
var cache = this.cache,
|
||||
cached = this.cached,
|
||||
neededIds = ids.filter(function notCached(id) {
|
||||
return !cached[id];
|
||||
});
|
||||
|
||||
// Update the cached instance of a model to a new value.
|
||||
// We update in-place to ensure there is only ever one instance
|
||||
@ -68,30 +88,12 @@ define(
|
||||
return oldModel;
|
||||
}
|
||||
|
||||
// Fast-resolving promise
|
||||
function fastPromise(value) {
|
||||
return (value || {}).then ? value : {
|
||||
then: function (callback) {
|
||||
return fastPromise(callback(value));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Store this model in the cache
|
||||
function cacheModel(id, model) {
|
||||
cache[id] = cached[id] ? updateModel(id, model) : model;
|
||||
cached[id] = true;
|
||||
}
|
||||
|
||||
// Check if an id is not in cache, for lookup filtering
|
||||
function notCached(id) {
|
||||
return !cached[id];
|
||||
}
|
||||
|
||||
// Store the provided models in our cache
|
||||
function cacheAll(models) {
|
||||
Object.keys(models).forEach(function (id) {
|
||||
cacheModel(id, models[id]);
|
||||
cache[id] = cached[id] ?
|
||||
updateModel(id, models[id]) : models[id];
|
||||
cached[id] = true;
|
||||
});
|
||||
}
|
||||
|
||||
@ -100,38 +102,16 @@ define(
|
||||
return cache;
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get models for these specified string identifiers.
|
||||
* These will be given as an object containing keys
|
||||
* and values, where keys are object identifiers and
|
||||
* values are models.
|
||||
* This result may contain either a subset or a
|
||||
* superset of the total objects.
|
||||
*
|
||||
* @param {Array<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
|
||||
if (neededIds.length > 0) {
|
||||
return this.modelService.getModels(neededIds)
|
||||
.then(cacheAll)
|
||||
.then(giveCache);
|
||||
}
|
||||
|
||||
// Look up if we have unknown IDs
|
||||
if (neededIds.length > 0) {
|
||||
return modelService.getModels(neededIds)
|
||||
.then(cacheAll)
|
||||
.then(giveCache);
|
||||
}
|
||||
|
||||
// Otherwise, just expose the cache directly
|
||||
return fastPromise(cache);
|
||||
}
|
||||
};
|
||||
}
|
||||
// Otherwise, just expose the cache directly
|
||||
return fastPromise(cache);
|
||||
};
|
||||
|
||||
return CachingModelDecorator;
|
||||
}
|
||||
|
@ -29,33 +29,34 @@ define(
|
||||
/**
|
||||
* Adds placeholder domain object models for any models which
|
||||
* fail to load from the underlying model service.
|
||||
* @implements {ModelService}
|
||||
* @constructor
|
||||
* @memberof platform/core
|
||||
* @param {ModelService} modelService this service to decorate
|
||||
* @implements {ModelService}
|
||||
*/
|
||||
function MissingModelDecorator(modelService) {
|
||||
function missingModel(id) {
|
||||
return {
|
||||
type: "unknown",
|
||||
name: "Missing: " + id
|
||||
};
|
||||
}
|
||||
this.modelService = modelService;
|
||||
}
|
||||
|
||||
function missingModel(id) {
|
||||
return {
|
||||
getModels: function (ids) {
|
||||
function addMissingModels(models) {
|
||||
var result = {};
|
||||
ids.forEach(function (id) {
|
||||
result[id] = models[id] || missingModel(id);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
return modelService.getModels(ids).then(addMissingModels);
|
||||
}
|
||||
type: "unknown",
|
||||
name: "Missing: " + id
|
||||
};
|
||||
}
|
||||
|
||||
MissingModelDecorator.prototype.getModels = function (ids) {
|
||||
function addMissingModels(models) {
|
||||
var result = {};
|
||||
ids.forEach(function (id) {
|
||||
result[id] = models[id] || missingModel(id);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
return this.modelService.getModels(ids).then(addMissingModels);
|
||||
};
|
||||
|
||||
return MissingModelDecorator;
|
||||
}
|
||||
);
|
||||
|
@ -29,67 +29,71 @@ define(
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Allow domain object models to be looked up by their identifiers.
|
||||
*
|
||||
* @interface ModelService
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get domain object models.
|
||||
*
|
||||
* This may provide either a superset or a subset of the models
|
||||
* requested. Absence of a model means it does not exist within
|
||||
* this service instance.
|
||||
*
|
||||
* @method ModelService#getModels
|
||||
* @param {string[]} ids identifiers for models desired.
|
||||
* @returns {Promise.<Object>} a promise for an object mapping
|
||||
* string identifiers to domain object models.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Allows multiple services which provide models for domain objects
|
||||
* to be treated as one.
|
||||
*
|
||||
* @memberof platform/core
|
||||
* @constructor
|
||||
* @param {ModelProvider[]} providers the model providers to be
|
||||
* @implements {ModelService}
|
||||
* @param $q Angular's $q, for promises
|
||||
* @param {ModelService[]} providers the model providers to be
|
||||
* aggregated
|
||||
*/
|
||||
function ModelAggregator($q, providers) {
|
||||
|
||||
// Pick a domain object model to use, favoring the one
|
||||
// with the most recent timestamp
|
||||
function pick(a, b) {
|
||||
var aModified = (a || {}).modified || Number.NEGATIVE_INFINITY,
|
||||
bModified = (b || {}).modified || Number.NEGATIVE_INFINITY;
|
||||
return (aModified > bModified) ? a : (b || a);
|
||||
}
|
||||
|
||||
// Merge results from multiple providers into one
|
||||
// large result object.
|
||||
function mergeModels(provided, ids) {
|
||||
var result = {};
|
||||
ids.forEach(function (id) {
|
||||
provided.forEach(function (models) {
|
||||
if (models[id]) {
|
||||
result[id] = pick(result[id], models[id]);
|
||||
}
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get models with the specified identifiers.
|
||||
*
|
||||
* This will invoke the `getModels()` method of all providers
|
||||
* given at constructor-time, and aggregate the result into
|
||||
* one object.
|
||||
*
|
||||
* Note that the returned object may contain a subset or a
|
||||
* superset of the models requested.
|
||||
*
|
||||
* @param {string[]} ids an array of domain object identifiers
|
||||
* @returns {Promise.<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);
|
||||
});
|
||||
}
|
||||
};
|
||||
this.providers = providers;
|
||||
this.$q = $q;
|
||||
}
|
||||
|
||||
// Pick a domain object model to use, favoring the one
|
||||
// with the most recent timestamp
|
||||
function pick(a, b) {
|
||||
var aModified = (a || {}).modified || Number.NEGATIVE_INFINITY,
|
||||
bModified = (b || {}).modified || Number.NEGATIVE_INFINITY;
|
||||
return (aModified > bModified) ? a : (b || a);
|
||||
}
|
||||
|
||||
// Merge results from multiple providers into one
|
||||
// large result object.
|
||||
function mergeModels(provided, ids) {
|
||||
var result = {};
|
||||
ids.forEach(function (id) {
|
||||
provided.forEach(function (models) {
|
||||
if (models[id]) {
|
||||
result[id] = pick(result[id], models[id]);
|
||||
}
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
ModelAggregator.prototype.getModels = function (ids) {
|
||||
return this.$q.all(this.providers.map(function (provider) {
|
||||
return provider.getModels(ids);
|
||||
})).then(function (provided) {
|
||||
return mergeModels(provided, ids);
|
||||
});
|
||||
};
|
||||
|
||||
return ModelAggregator;
|
||||
}
|
||||
);
|
||||
|
@ -35,61 +35,47 @@ define(
|
||||
*
|
||||
* @memberof platform/core
|
||||
* @constructor
|
||||
* @implements {ModelService}
|
||||
* @param {PersistenceService} persistenceService the service in which
|
||||
* domain object models are persisted.
|
||||
* @param $q Angular's $q service, for working with promises
|
||||
* @param {string} SPACE the name of the persistence space from which
|
||||
* models should be retrieved.
|
||||
*/
|
||||
function PersistedModelProvider(persistenceService, $q, SPACE) {
|
||||
// Load a single object model from persistence
|
||||
function loadModel(id) {
|
||||
return persistenceService.readObject(SPACE, id);
|
||||
}
|
||||
|
||||
// Promise all persisted models (in id->model form)
|
||||
function promiseModels(ids) {
|
||||
// Package the result as id->model
|
||||
function packageResult(models) {
|
||||
var result = {};
|
||||
ids.forEach(function (id, index) {
|
||||
result[id] = models[index];
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// Filter out "namespaced" identifiers; these are
|
||||
// not expected to be found in database. See WTD-659.
|
||||
ids = ids.filter(function (id) {
|
||||
return id.indexOf(":") === -1;
|
||||
});
|
||||
|
||||
// Give a promise for all persistence lookups...
|
||||
return $q.all(ids.map(loadModel)).then(packageResult);
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get models with the specified identifiers.
|
||||
*
|
||||
* This will invoke the underlying persistence service to
|
||||
* retrieve object models which match the provided
|
||||
* identifiers.
|
||||
*
|
||||
* Note that the returned object may contain a subset or a
|
||||
* superset of the models requested.
|
||||
*
|
||||
* @param {string[]} ids an array of domain object identifiers
|
||||
* @returns {Promise.<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
|
||||
};
|
||||
function PersistedModelProvider(persistenceService, $q, space) {
|
||||
this.persistenceService = persistenceService;
|
||||
this.$q = $q;
|
||||
this.space = space;
|
||||
}
|
||||
|
||||
PersistedModelProvider.prototype.getModels = function (ids) {
|
||||
var persistenceService = this.persistenceService,
|
||||
$q = this.$q,
|
||||
space = this.space;
|
||||
|
||||
// Load a single object model from persistence
|
||||
function loadModel(id) {
|
||||
return persistenceService.readObject(space, id);
|
||||
}
|
||||
|
||||
// Package the result as id->model
|
||||
function packageResult(models) {
|
||||
var result = {};
|
||||
ids.forEach(function (id, index) {
|
||||
result[id] = models[index];
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// Filter out "namespaced" identifiers; these are
|
||||
// not expected to be found in database. See WTD-659.
|
||||
ids = ids.filter(function (id) {
|
||||
return id.indexOf(":") === -1;
|
||||
});
|
||||
|
||||
// Give a promise for all persistence lookups...
|
||||
return $q.all(ids.map(loadModel)).then(packageResult);
|
||||
};
|
||||
|
||||
return PersistedModelProvider;
|
||||
}
|
||||
|
@ -41,42 +41,31 @@ define(
|
||||
*
|
||||
* @memberof platform/core
|
||||
* @constructor
|
||||
* @implements {ModelService}
|
||||
* @param {Array} roots all `roots[]` extensions
|
||||
* @param $q Angular's $q, for promises
|
||||
* @param $log Anuglar's $log, for logging
|
||||
*/
|
||||
function RootModelProvider(roots, $q, $log) {
|
||||
// Pull out identifiers to used as ROOT's
|
||||
var ids = roots.map(function (root) { return root.id; }),
|
||||
baseProvider = new StaticModelProvider(roots, $q, $log);
|
||||
var ids = roots.map(function (root) { return root.id; });
|
||||
|
||||
function addRoot(models) {
|
||||
models.ROOT = {
|
||||
name: "The root object",
|
||||
type: "root",
|
||||
composition: ids
|
||||
};
|
||||
return models;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
/**
|
||||
* Get models with the specified identifiers.
|
||||
*
|
||||
* Note that the returned object may contain a subset or a
|
||||
* superset of the models requested.
|
||||
*
|
||||
* @param {string[]} ids an array of domain object identifiers
|
||||
* @returns {Promise.<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);
|
||||
}
|
||||
this.baseProvider = new StaticModelProvider(roots, $q, $log);
|
||||
this.rootModel = {
|
||||
name: "The root object",
|
||||
type: "root",
|
||||
composition: ids
|
||||
};
|
||||
}
|
||||
|
||||
RootModelProvider.prototype.getModels = function (ids) {
|
||||
var rootModel = this.rootModel;
|
||||
return this.baseProvider.getModels(ids).then(function (models) {
|
||||
models.ROOT = rootModel;
|
||||
return models;
|
||||
});
|
||||
};
|
||||
|
||||
return RootModelProvider;
|
||||
}
|
||||
);
|
||||
|
@ -41,7 +41,7 @@ define(
|
||||
// Skip models which don't look right
|
||||
if (typeof model !== 'object' ||
|
||||
typeof model.id !== 'string' ||
|
||||
typeof model.model !== 'object') {
|
||||
typeof model.model !== 'object') {
|
||||
$log.warn([
|
||||
"Skipping malformed domain object model exposed by ",
|
||||
((model || {}).bundle || {}).path
|
||||
@ -54,34 +54,19 @@ define(
|
||||
// Prepoulate maps with models to make subsequent lookup faster.
|
||||
models.forEach(addModelToMap);
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get models for these specified string identifiers.
|
||||
* These will be given as an object containing keys
|
||||
* and values, where keys are object identifiers and
|
||||
* values are models.
|
||||
* This result may contain either a subset or a
|
||||
* superset of the total objects.
|
||||
*
|
||||
* @param {Array<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);
|
||||
}
|
||||
};
|
||||
this.modelMap = modelMap;
|
||||
this.$q = $q;
|
||||
}
|
||||
|
||||
StaticModelProvider.prototype.getModels = function (ids) {
|
||||
var modelMap = this.modelMap,
|
||||
result = {};
|
||||
ids.forEach(function (id) {
|
||||
result[id] = modelMap[id];
|
||||
});
|
||||
return this.$q.when(result);
|
||||
};
|
||||
|
||||
return StaticModelProvider;
|
||||
}
|
||||
);
|
||||
|
@ -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;
|
||||
}
|
||||
);
|
143
platform/core/src/objects/DomainObjectImpl.js
Normal file
143
platform/core/src/objects/DomainObjectImpl.js
Normal 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;
|
||||
}
|
||||
);
|
@ -27,10 +27,31 @@
|
||||
* @namespace platform/core
|
||||
*/
|
||||
define(
|
||||
["./DomainObject"],
|
||||
function (DomainObject) {
|
||||
["./DomainObjectImpl"],
|
||||
function (DomainObjectImpl) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Provides instances of domain objects, as retrieved by their
|
||||
* identifiers.
|
||||
*
|
||||
* @interface ObjectService
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get a set of objects associated with a list of identifiers.
|
||||
* The provided result may contain a subset or a superset of
|
||||
* the total number of objects.
|
||||
*
|
||||
* @method ObjectService#getObjects
|
||||
* @param {string[]} ids the identifiers for domain objects
|
||||
* of interest.
|
||||
* @return {Promise<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.
|
||||
*
|
||||
@ -44,6 +65,16 @@ define(
|
||||
* @constructor
|
||||
*/
|
||||
function DomainObjectProvider(modelService, capabilityService, $q) {
|
||||
this.modelService = modelService;
|
||||
this.capabilityService = capabilityService;
|
||||
this.$q = $q;
|
||||
}
|
||||
|
||||
DomainObjectProvider.prototype.getObjects = function getObjects(ids) {
|
||||
var modelService = this.modelService,
|
||||
capabilityService = this.capabilityService,
|
||||
$q = this.$q;
|
||||
|
||||
// Given a models object (containing key-value id-model pairs)
|
||||
// create a function that will look up from the capability
|
||||
// service based on id; for handy mapping below.
|
||||
@ -51,8 +82,8 @@ define(
|
||||
return function (id) {
|
||||
var model = models[id];
|
||||
return model ?
|
||||
capabilityService.getCapabilities(model) :
|
||||
undefined;
|
||||
capabilityService.getCapabilities(model) :
|
||||
undefined;
|
||||
};
|
||||
}
|
||||
|
||||
@ -65,7 +96,7 @@ define(
|
||||
ids.forEach(function (id, index) {
|
||||
if (models[id]) {
|
||||
// Create the domain object
|
||||
result[id] = new DomainObject(
|
||||
result[id] = new DomainObjectImpl(
|
||||
id,
|
||||
models[id],
|
||||
capabilities[index]
|
||||
@ -75,36 +106,14 @@ define(
|
||||
return result;
|
||||
}
|
||||
|
||||
// Get object instances; this is the useful API exposed by the
|
||||
// domain object provider.
|
||||
function getObjects(ids) {
|
||||
return modelService.getModels(ids).then(function (models) {
|
||||
return $q.all(
|
||||
ids.map(capabilityResolver(models))
|
||||
).then(function (capabilities) {
|
||||
return modelService.getModels(ids).then(function (models) {
|
||||
return $q.all(
|
||||
ids.map(capabilityResolver(models))
|
||||
).then(function (capabilities) {
|
||||
return assembleResult(ids, models, capabilities);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get a set of objects associated with a list of identifiers.
|
||||
* The provided result may contain a subset or a superset of
|
||||
* the total number of objects.
|
||||
*
|
||||
* @param {Array<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;
|
||||
}
|
||||
|
@ -31,7 +31,6 @@ define(
|
||||
* `Date.now()` which can be injected to support testability.
|
||||
*
|
||||
* @returns {Function} a function which returns current system time
|
||||
* @constructor
|
||||
* @memberof platform/core
|
||||
*/
|
||||
function Now() {
|
||||
|
@ -42,7 +42,6 @@ define(
|
||||
* resolve to the returned value of `fn` whenever that is invoked.
|
||||
*
|
||||
* @returns {Function}
|
||||
* @constructor
|
||||
* @memberof platform/core
|
||||
*/
|
||||
function Throttle($timeout) {
|
||||
|
@ -44,7 +44,6 @@ define(
|
||||
* arguments) are private; each call returns a new instance.
|
||||
*
|
||||
* @returns {Function}
|
||||
* @constructor
|
||||
* @memberof platform/core
|
||||
*/
|
||||
function Topic() {
|
||||
|
@ -36,6 +36,8 @@ define(
|
||||
*
|
||||
* @memberof platform/core
|
||||
* @constructor
|
||||
* @augments {Type}
|
||||
* @implements {Capability}
|
||||
* @param {TypeService} typeService the service which
|
||||
* provides type information
|
||||
* @param {DomainObject} domainObject the domain object
|
||||
|
@ -26,10 +26,95 @@ define(
|
||||
function (TypeProperty) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Describes a type of domain object.
|
||||
*
|
||||
* @interface Type
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the string key which identifies this type.
|
||||
* This is the type's machine-readable name/identifier,
|
||||
* and will correspond to the "type" field of the models
|
||||
* of domain objects of this type.
|
||||
*
|
||||
* @returns {string} the key which identifies this type
|
||||
* @method Type#getKey
|
||||
*/
|
||||
/**
|
||||
* Get the human-readable name for this type, as should
|
||||
* be displayed in the user interface when referencing
|
||||
* this type.
|
||||
*
|
||||
* @returns {string} the human-readable name of this type
|
||||
* @method Type#getName
|
||||
*/
|
||||
/**
|
||||
* Get the human-readable description for this type, as should
|
||||
* be displayed in the user interface when describing
|
||||
* this type.
|
||||
*
|
||||
* @returns {string} the human-readable description of this type
|
||||
* @method Type#getDescription
|
||||
*/
|
||||
/**
|
||||
* Get the glyph associated with this type. Glyphs are
|
||||
* single-character strings which will appear as icons (when
|
||||
* displayed in an appropriate font) which visually
|
||||
* distinguish types from one another.
|
||||
*
|
||||
* @returns {string} the glyph to be displayed
|
||||
* @method Type#getGlyph
|
||||
*/
|
||||
/**
|
||||
* Get an array of properties associated with objects of
|
||||
* this type, as might be shown in a Create wizard or
|
||||
* an Edit Properties view.
|
||||
*
|
||||
* @return {Array<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
|
||||
* domain objects.
|
||||
*
|
||||
* @implements {Type}
|
||||
* @param {TypeDefinition} typeDef an object containing
|
||||
* key-value pairs describing a type and its
|
||||
* relationship to other types.
|
||||
@ -44,133 +129,62 @@ define(
|
||||
featureSet[feature] = true;
|
||||
});
|
||||
|
||||
return {
|
||||
/**
|
||||
* 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
|
||||
* @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;
|
||||
}
|
||||
};
|
||||
this.typeDef = typeDef;
|
||||
this.featureSet = featureSet;
|
||||
this.inheritList = inheritList;
|
||||
}
|
||||
|
||||
TypeImpl.prototype.getKey = function () {
|
||||
return this.typeDef.key;
|
||||
};
|
||||
|
||||
TypeImpl.prototype.getName = function () {
|
||||
return this.typeDef.name;
|
||||
};
|
||||
|
||||
TypeImpl.prototype.getDescription = function () {
|
||||
return this.typeDef.description;
|
||||
};
|
||||
|
||||
TypeImpl.prototype.getGlyph = function () {
|
||||
return this.typeDef.glyph;
|
||||
};
|
||||
|
||||
TypeImpl.prototype.getProperties = function () {
|
||||
return (this.typeDef.properties || []).map(function (propertyDef) {
|
||||
return new TypeProperty(propertyDef);
|
||||
});
|
||||
};
|
||||
|
||||
TypeImpl.prototype.getInitialModel = function () {
|
||||
return this.typeDef.model || {};
|
||||
};
|
||||
|
||||
TypeImpl.prototype.getDefinition = function () {
|
||||
return this.typeDef;
|
||||
};
|
||||
|
||||
TypeImpl.prototype.instanceOf = function instanceOf(key) {
|
||||
var typeDef = this.typeDef,
|
||||
inheritList = this.inheritList;
|
||||
|
||||
if (key === typeDef.key) {
|
||||
return true;
|
||||
} else if (inheritList.indexOf(key) > -1) {
|
||||
return true;
|
||||
} else if (!key) {
|
||||
return true;
|
||||
} else if (key !== null && typeof key === 'object') {
|
||||
return key.getKey ? this.instanceOf(key.getKey()) : false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
TypeImpl.prototype.hasFeature = function (feature) {
|
||||
return this.featureSet[feature] || false;
|
||||
};
|
||||
|
||||
return TypeImpl;
|
||||
}
|
||||
);
|
||||
|
@ -35,128 +35,130 @@ define(
|
||||
*/
|
||||
function TypeProperty(propertyDefinition) {
|
||||
// Load an appropriate conversion
|
||||
var conversion = new TypePropertyConversion(
|
||||
this.conversion = new TypePropertyConversion(
|
||||
propertyDefinition.conversion || "identity"
|
||||
);
|
||||
this.propertyDefinition = propertyDefinition;
|
||||
}
|
||||
|
||||
// Check if a value is defined; used to check if initial array
|
||||
// values have been populated.
|
||||
function isUnpopulatedArray(value) {
|
||||
var i;
|
||||
// Check if a value is defined; used to check if initial array
|
||||
// values have been populated.
|
||||
function isUnpopulatedArray(value) {
|
||||
var i;
|
||||
|
||||
if (!Array.isArray(value) || value.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (i = 0; i < value.length; i += 1) {
|
||||
if (value[i] !== undefined) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
if (!Array.isArray(value) || value.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Perform a lookup for a value from an object,
|
||||
// which may recursively look at contained objects
|
||||
// based on the path provided.
|
||||
function lookupValue(object, propertyPath) {
|
||||
var value;
|
||||
|
||||
// Can't look up from a non-object
|
||||
if (!object) {
|
||||
return undefined;
|
||||
for (i = 0; i < value.length; i += 1) {
|
||||
if (value[i] !== undefined) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If path is not an array, just look up the property
|
||||
if (!Array.isArray(propertyPath)) {
|
||||
return object[propertyPath];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, look up in the sequence defined in the array
|
||||
if (propertyPath.length > 0) {
|
||||
value = object[propertyPath[0]];
|
||||
return propertyPath.length > 1 ?
|
||||
lookupValue(value, propertyPath.slice(1)) :
|
||||
value;
|
||||
}
|
||||
// Specify a field deeply within an object
|
||||
function specifyValue(object, propertyPath, value) {
|
||||
// If path is not an array, just set the property
|
||||
if (!Array.isArray(propertyPath)) {
|
||||
object[propertyPath] = value;
|
||||
} else if (propertyPath.length > 1) {
|
||||
// Otherwise, look up in defined sequence
|
||||
object[propertyPath[0]] = object[propertyPath[0]] || {};
|
||||
specifyValue(
|
||||
object[propertyPath[0]],
|
||||
propertyPath.slice(1),
|
||||
value
|
||||
);
|
||||
} else if (propertyPath.length === 1) {
|
||||
object[propertyPath[0]] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback; property path was empty
|
||||
// Perform a lookup for a value from an object,
|
||||
// which may recursively look at contained objects
|
||||
// based on the path provided.
|
||||
function lookupValue(object, propertyPath) {
|
||||
var value;
|
||||
|
||||
// Can't look up from a non-object
|
||||
if (!object) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function specifyValue(object, propertyPath, value) {
|
||||
|
||||
// If path is not an array, just set the property
|
||||
if (!Array.isArray(propertyPath)) {
|
||||
object[propertyPath] = value;
|
||||
} else if (propertyPath.length > 1) {
|
||||
// Otherwise, look up in defined sequence
|
||||
object[propertyPath[0]] = object[propertyPath[0]] || {};
|
||||
specifyValue(
|
||||
object[propertyPath[0]],
|
||||
propertyPath.slice(1),
|
||||
value
|
||||
);
|
||||
} else if (propertyPath.length === 1) {
|
||||
object[propertyPath[0]] = value;
|
||||
}
|
||||
|
||||
// If path is not an array, just look up the property
|
||||
if (!Array.isArray(propertyPath)) {
|
||||
return object[propertyPath];
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Retrieve the value associated with this property
|
||||
* from a given model.
|
||||
* @memberof platform/core.TypeProperty#
|
||||
*/
|
||||
getValue: function (model) {
|
||||
var property = propertyDefinition.property ||
|
||||
propertyDefinition.key,
|
||||
initialValue =
|
||||
property && lookupValue(model, property);
|
||||
// Otherwise, look up in the sequence defined in the array
|
||||
if (propertyPath.length > 0) {
|
||||
value = object[propertyPath[0]];
|
||||
return propertyPath.length > 1 ?
|
||||
lookupValue(value, propertyPath.slice(1)) :
|
||||
value;
|
||||
}
|
||||
|
||||
// Provide an empty array if this is a multi-item
|
||||
// property.
|
||||
if (Array.isArray(propertyDefinition.items)) {
|
||||
initialValue = initialValue ||
|
||||
new Array(propertyDefinition.items.length);
|
||||
}
|
||||
|
||||
return conversion.toFormValue(initialValue);
|
||||
},
|
||||
/**
|
||||
* Set a value associated with this property in
|
||||
* an object's model.
|
||||
* @memberof platform/core.TypeProperty#
|
||||
*/
|
||||
setValue: function setValue(model, value) {
|
||||
var property = propertyDefinition.property ||
|
||||
propertyDefinition.key;
|
||||
|
||||
// If an array contains all undefined values, treat it
|
||||
// as undefined, to filter back out arrays for input
|
||||
// that never got entered.
|
||||
value = isUnpopulatedArray(value) ? undefined : value;
|
||||
|
||||
// Convert to a value suitable for storage in the
|
||||
// domain object's model
|
||||
value = conversion.toModelValue(value);
|
||||
|
||||
return property ?
|
||||
specifyValue(model, property, value) :
|
||||
undefined;
|
||||
},
|
||||
/**
|
||||
* Get the raw definition for this property.
|
||||
* @memberof platform/core.TypeProperty#
|
||||
*/
|
||||
getDefinition: function () {
|
||||
return propertyDefinition;
|
||||
}
|
||||
};
|
||||
// Fallback; property path was empty
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the value associated with this property
|
||||
* from a given model.
|
||||
* @param {object} model a domain object model to read from
|
||||
* @returns {*} the value for this property, as read from the model
|
||||
*/
|
||||
TypeProperty.prototype.getValue = function (model) {
|
||||
var property = this.propertyDefinition.property ||
|
||||
this.propertyDefinition.key,
|
||||
initialValue =
|
||||
property && lookupValue(model, property);
|
||||
|
||||
// Provide an empty array if this is a multi-item
|
||||
// property.
|
||||
if (Array.isArray(this.propertyDefinition.items)) {
|
||||
initialValue = initialValue ||
|
||||
new Array(this.propertyDefinition.items.length);
|
||||
}
|
||||
|
||||
return this.conversion.toFormValue(initialValue);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set a value associated with this property in
|
||||
* an object's model.
|
||||
* @param {object} model a domain object model to update
|
||||
* @param {*} value the new value to set for this property
|
||||
*/
|
||||
TypeProperty.prototype.setValue = function (model, value) {
|
||||
var property = this.propertyDefinition.property ||
|
||||
this.propertyDefinition.key;
|
||||
|
||||
// If an array contains all undefined values, treat it
|
||||
// as undefined, to filter back out arrays for input
|
||||
// that never got entered.
|
||||
value = isUnpopulatedArray(value) ? undefined : value;
|
||||
|
||||
// Convert to a value suitable for storage in the
|
||||
// domain object's model
|
||||
value = this.conversion.toModelValue(value);
|
||||
|
||||
return property ?
|
||||
specifyValue(model, property, value) :
|
||||
undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the raw definition for this property.
|
||||
* @returns {TypePropertyDefinition}
|
||||
*/
|
||||
TypeProperty.prototype.getDefinition = function () {
|
||||
return this.propertyDefinition;
|
||||
};
|
||||
|
||||
return TypeProperty;
|
||||
}
|
||||
);
|
||||
|
@ -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;
|
||||
}
|
||||
);
|
||||
|
@ -26,6 +26,27 @@ define(
|
||||
function (TypeImpl, mergeModels) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Provides domain object types that are available/recognized within
|
||||
* the system.
|
||||
*
|
||||
* @interface TypeService
|
||||
*/
|
||||
/**
|
||||
* Get a specific type by name.
|
||||
*
|
||||
* @method TypeService#getType
|
||||
* @param {string} key the key (machine-readable identifier)
|
||||
* for the type of interest
|
||||
* @returns {Type} the type identified by this key
|
||||
*/
|
||||
/**
|
||||
* List all known types.
|
||||
*
|
||||
* @method TypeService#listTypes
|
||||
* @returns {Type[]} all known types
|
||||
*/
|
||||
|
||||
var TO_CONCAT = ['inherits', 'capabilities', 'properties', 'features'],
|
||||
TO_MERGE = ['model'];
|
||||
|
||||
@ -49,11 +70,44 @@ define(
|
||||
}) : array;
|
||||
}
|
||||
|
||||
// Reduce an array of type definitions to a single type definiton,
|
||||
// which has merged all properties in order.
|
||||
function collapse(typeDefs) {
|
||||
var collapsed = typeDefs.reduce(function (a, b) {
|
||||
var result = {};
|
||||
copyKeys(result, a);
|
||||
copyKeys(result, b);
|
||||
|
||||
// Special case: Do a merge, e.g. on "model"
|
||||
TO_MERGE.forEach(function (k) {
|
||||
if (a[k] && b[k]) {
|
||||
result[k] = mergeModels(a[k], b[k]);
|
||||
}
|
||||
});
|
||||
|
||||
// Special case: Concatenate certain arrays
|
||||
TO_CONCAT.forEach(function (k) {
|
||||
if (a[k] || b[k]) {
|
||||
result[k] = (a[k] || []).concat(b[k] || []);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}, {});
|
||||
|
||||
// Remove any duplicates from the collapsed array
|
||||
TO_CONCAT.forEach(function (k) {
|
||||
if (collapsed[k]) {
|
||||
collapsed[k] = removeDuplicates(collapsed[k]);
|
||||
}
|
||||
});
|
||||
return collapsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* A type provider provides information about types of domain objects
|
||||
* within the running Open MCT Web instance.
|
||||
*
|
||||
* @param {Array<TypeDefinition>} options.definitions the raw type
|
||||
* @param {Array<TypeDefinition>} types the raw type
|
||||
* definitions for this type.
|
||||
* @memberof platform/core
|
||||
* @constructor
|
||||
@ -69,46 +123,34 @@ define(
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}(rawTypeDefinitions)),
|
||||
typeMap = {},
|
||||
undefinedType;
|
||||
}(rawTypeDefinitions));
|
||||
|
||||
// Reduce an array of type definitions to a single type definiton,
|
||||
// which has merged all properties in order.
|
||||
function collapse(typeDefs) {
|
||||
var collapsed = typeDefs.reduce(function (a, b) {
|
||||
var result = {};
|
||||
copyKeys(result, a);
|
||||
copyKeys(result, b);
|
||||
|
||||
// Special case: Do a merge, e.g. on "model"
|
||||
TO_MERGE.forEach(function (k) {
|
||||
if (a[k] && b[k]) {
|
||||
result[k] = mergeModels(a[k], b[k]);
|
||||
}
|
||||
});
|
||||
this.typeMap = {};
|
||||
this.typeDefinitions = typeDefinitions;
|
||||
this.rawTypeDefinitions = types;
|
||||
}
|
||||
|
||||
// Special case: Concatenate certain arrays
|
||||
TO_CONCAT.forEach(function (k) {
|
||||
if (a[k] || b[k]) {
|
||||
result[k] = (a[k] || []).concat(b[k] || []);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}, {});
|
||||
TypeProvider.prototype.listTypes = function () {
|
||||
var self = this;
|
||||
return removeDuplicates(
|
||||
this.rawTypeDefinitions.filter(function (def) {
|
||||
return def.key;
|
||||
}).map(function (def) {
|
||||
return def.key;
|
||||
}).map(function (key) {
|
||||
return self.getType(key);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
// Remove any duplicates from the collapsed array
|
||||
TO_CONCAT.forEach(function (k) {
|
||||
if (collapsed[k]) {
|
||||
collapsed[k] = removeDuplicates(collapsed[k]);
|
||||
}
|
||||
});
|
||||
return collapsed;
|
||||
}
|
||||
TypeProvider.prototype.getType = function (key) {
|
||||
var typeDefinitions = this.typeDefinitions,
|
||||
self = this;
|
||||
|
||||
function getUndefinedType() {
|
||||
return (undefinedType = undefinedType || collapse(
|
||||
rawTypeDefinitions.filter(function (typeDef) {
|
||||
return (self.undefinedType = self.undefinedType || collapse(
|
||||
self.rawTypeDefinitions.filter(function (typeDef) {
|
||||
return !typeDef.key;
|
||||
})
|
||||
));
|
||||
@ -118,79 +160,36 @@ define(
|
||||
return Array.isArray(value) ? value : [value];
|
||||
}
|
||||
|
||||
function lookupTypeDef(typeKey) {
|
||||
function buildTypeDef(typeKey) {
|
||||
var typeDefs = typeDefinitions[typeKey] || [],
|
||||
inherits = typeDefs.map(function (typeDef) {
|
||||
return asArray(typeDef.inherits || []);
|
||||
}).reduce(function (a, b) {
|
||||
return a.concat(b);
|
||||
}, []),
|
||||
def = collapse(
|
||||
[getUndefinedType()].concat(
|
||||
inherits.map(lookupTypeDef)
|
||||
).concat(typeDefs)
|
||||
);
|
||||
|
||||
// Always provide a default name
|
||||
def.model = def.model || {};
|
||||
def.model.name = def.model.name || (
|
||||
"Unnamed " + (def.name || "Object")
|
||||
function buildTypeDef(typeKey) {
|
||||
var typeDefs = typeDefinitions[typeKey] || [],
|
||||
inherits = typeDefs.map(function (typeDef) {
|
||||
return asArray(typeDef.inherits || []);
|
||||
}).reduce(function (a, b) {
|
||||
return a.concat(b);
|
||||
}, []),
|
||||
def = collapse(
|
||||
[getUndefinedType()].concat(
|
||||
inherits.map(lookupTypeDef)
|
||||
).concat(typeDefs)
|
||||
);
|
||||
|
||||
return def;
|
||||
// Always provide a default name
|
||||
def.model = def.model || {};
|
||||
def.model.name = def.model.name ||
|
||||
("Unnamed " + (def.name || "Object"));
|
||||
|
||||
}
|
||||
|
||||
return (typeMap[typeKey] = typeMap[typeKey] || buildTypeDef(typeKey));
|
||||
return def;
|
||||
}
|
||||
|
||||
function lookupTypeDef(typeKey) {
|
||||
return (self.typeMap[typeKey] =
|
||||
self.typeMap[typeKey] || buildTypeDef(typeKey));
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get a list of all types defined by this service.
|
||||
*
|
||||
* @returns {Promise<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 new TypeImpl(lookupTypeDef(key));
|
||||
};
|
||||
|
||||
return TypeProvider;
|
||||
|
||||
|
||||
}
|
||||
|
||||
);
|
||||
|
@ -36,23 +36,25 @@ define(
|
||||
* object.
|
||||
*
|
||||
* @memberof platform/core
|
||||
* @implements {Capability}
|
||||
* @constructor
|
||||
*/
|
||||
function ViewCapability(viewService, domainObject) {
|
||||
return {
|
||||
/**
|
||||
* Get all view definitions which are applicable to
|
||||
* this object.
|
||||
* @returns {View[]} an array of view definitions
|
||||
* which are applicable to this object.
|
||||
* @memberof platform/core.ViewCapability#
|
||||
*/
|
||||
invoke: function () {
|
||||
return viewService.getViews(domainObject);
|
||||
}
|
||||
};
|
||||
this.viewService = viewService;
|
||||
this.domainObject = domainObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all view definitions which are applicable to
|
||||
* this object.
|
||||
* @returns {View[]} an array of view definitions
|
||||
* which are applicable to this object.
|
||||
* @memberof platform/core.ViewCapability#
|
||||
*/
|
||||
ViewCapability.prototype.invoke = function () {
|
||||
return this.viewService.getViews(this.domainObject);
|
||||
};
|
||||
|
||||
return ViewCapability;
|
||||
}
|
||||
);
|
||||
|
@ -29,6 +29,22 @@ define(
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Provides definitions for views that are available for specific
|
||||
* domain objects.
|
||||
*
|
||||
* @interface ViewService
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get all views which are applicable to this domain object.
|
||||
*
|
||||
* @method ViewService#getViews
|
||||
* @param {DomainObject} domainObject the domain object to view
|
||||
* @returns {View[]} all views which can be used to visualize
|
||||
* this domain object.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A view provider allows view definitions (defined as extensions)
|
||||
* to be read, and takes responsibility for filtering these down
|
||||
@ -58,6 +74,8 @@ define(
|
||||
* @memberof platform/core
|
||||
* @constructor
|
||||
* @param {View[]} an array of view definitions
|
||||
* @param $log Angular's logging service
|
||||
* @implements {ViewService}
|
||||
*/
|
||||
function ViewProvider(views, $log) {
|
||||
|
||||
@ -79,6 +97,13 @@ define(
|
||||
return key;
|
||||
}
|
||||
|
||||
// Filter out any key-less views
|
||||
this.views = views.filter(validate);
|
||||
}
|
||||
|
||||
ViewProvider.prototype.getViews = function (domainObject) {
|
||||
var type = domainObject.useCapability("type");
|
||||
|
||||
// Check if an object has all capabilities designated as `needs`
|
||||
// for a view. Exposing a capability via delegation is taken to
|
||||
// satisfy this filter if `allowDelegation` is true.
|
||||
@ -122,35 +147,16 @@ define(
|
||||
return matches;
|
||||
}
|
||||
|
||||
function getViews(domainObject) {
|
||||
var type = domainObject.useCapability("type");
|
||||
|
||||
// First, filter views by type (matched to domain object type.)
|
||||
// Second, filter by matching capabilities.
|
||||
return views.filter(function (view) {
|
||||
return viewMatchesType(view, type) && capabilitiesMatch(
|
||||
// First, filter views by type (matched to domain object type.)
|
||||
// Second, filter by matching capabilities.
|
||||
return this.views.filter(function (view) {
|
||||
return viewMatchesType(view, type) && capabilitiesMatch(
|
||||
domainObject,
|
||||
view.needs || [],
|
||||
view.delegation || false
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Filter out any key-less views
|
||||
views = views.filter(validate);
|
||||
|
||||
return {
|
||||
/**
|
||||
* Get all views which are applicable to this domain object.
|
||||
*
|
||||
* @param {DomainObject} domainObject the domain object to view
|
||||
* @returns {View[]} all views which can be used to visualize
|
||||
* this domain object.
|
||||
* @memberof platform/core.ViewProvider#
|
||||
*/
|
||||
getViews: getViews
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return ViewProvider;
|
||||
}
|
||||
|
@ -25,7 +25,7 @@
|
||||
* DomainObjectSpec. Created by vwoeltje on 11/6/14.
|
||||
*/
|
||||
define(
|
||||
["../../src/objects/DomainObject"],
|
||||
["../../src/objects/DomainObjectImpl"],
|
||||
function (DomainObject) {
|
||||
"use strict";
|
||||
|
||||
|
@ -23,7 +23,7 @@
|
||||
|
||||
define(
|
||||
['../../src/types/TypeImpl'],
|
||||
function (typeImpl) {
|
||||
function (TypeImpl) {
|
||||
"use strict";
|
||||
|
||||
describe("Type definition wrapper", function () {
|
||||
@ -41,7 +41,7 @@ define(
|
||||
properties: [ {} ],
|
||||
model: {someKey: "some value"}
|
||||
};
|
||||
type = typeImpl(testTypeDef);
|
||||
type = new TypeImpl(testTypeDef);
|
||||
});
|
||||
|
||||
it("exposes key from definition", function () {
|
||||
|
@ -128,7 +128,7 @@ define(
|
||||
});
|
||||
|
||||
it("includes capabilities from undefined type in all types", function () {
|
||||
captured.type = TypeProvider.instantiate(
|
||||
captured.type = new TypeProvider(
|
||||
testTypeDefinitions.concat([
|
||||
{ capabilities: ['a', 'b', 'c'] },
|
||||
{ capabilities: ['x', 'y', 'z'] }
|
||||
|
Loading…
x
Reference in New Issue
Block a user