[Code Style] Use prototypes in platform

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

View File

@ -108,41 +108,19 @@ define(
* of action services
*/
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;
}
);

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

View File

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

View File

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

View File

@ -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.

View File

@ -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;

View File

@ -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;
}

View File

@ -69,100 +69,103 @@ define(
* });
* ```
*
* @param {Function} topic a service for creating listeners
* @param {Function} now a service to get the current time
* @param {DomainObject} domainObject the domain object
* 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;
}

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

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

View File

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

View File

@ -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;
}

View File

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

View File

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

View File

@ -1,134 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,Promise*/
/**
* Module defining DomainObject. Created by vwoeltje on 11/7/14.
*/
define(
[],
function () {
"use strict";
/**
* Construct a new domain object with the specified
* identifier, model, and capabilities.
*
* @param {string} id the object's unique identifier
* @param {object} model the "JSONifiable" state of the object
* @param {Object.<string, Capability>|function} capabilities all
* capabilities to be exposed by this object
* @memberof platform/core
* @constructor
*/
function DomainObject(id, model, capabilities) {
return {
/**
* Get the unique identifier for this domain object.
* @return {string} the domain object's unique identifier
* @memberof DomainObject#
* @memberof platform/core.DomainObject#
*/
getId: function () {
return id;
},
/**
* Get the domain object's model. This is useful to
* directly look up known properties of an object, but
* direct modification of a returned model is generally
* discouraged and may result in errors. Instead, an
* object's "mutation" capability should be used.
*
* @return {object} the domain object's persistent state
* @memberof DomainObject#
* @memberof platform/core.DomainObject#
*/
getModel: function () {
return model;
},
/**
* Get a capability associated with this object.
* Capabilities are looked up by string identifiers;
* prior knowledge of a capability's interface is
* necessary.
*
* @return {Capability} the named capability, or undefined
* if not present.
* @memberof DomainObject#
* @memberof platform/core.DomainObject#
*/
getCapability: function (name) {
var capability = capabilities[name];
return typeof capability === 'function' ?
capability(this) : capability;
},
/**g
* Check if this domain object supports a capability
* with the provided name.
*
* @param {string} name the name of the capability to
* check for
* @returns {boolean} true if provided
* @memberof platform/core.DomainObject#
*/
hasCapability: function hasCapability(name) {
return this.getCapability(name) !== undefined;
},
/**
* Use a capability of an object; this is a shorthand
* for:
*
* ```
* hasCapability(name) ?
* getCapability(name).invoke(args...) :
* undefined
* ```
*
* That is, it handles both the check-for-existence and
* invocation of the capability, and checks for existence
* before invoking the capability.
*
* @param {string} name the name of the capability to invoke
* @param {...*} [arguments] to pass to the invocation
* @returns {*}
* @memberof DomainObject#
* @memberof platform/core.DomainObject#
*/
useCapability: function (name) {
// Get tail of args to pass to invoke
var args = Array.prototype.slice.apply(arguments, [1]),
capability = this.getCapability(name);
return (capability && capability.invoke) ?
capability.invoke.apply(capability, args) :
capability;
}
};
}
return DomainObject;
}
);

View File

@ -0,0 +1,143 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,Promise*/
/**
* Module defining DomainObject. Created by vwoeltje on 11/7/14.
*/
define(
[],
function () {
"use strict";
/**
* A domain object is an entity of interest to the user.
*
* @interface DomainObject
*/
/**
* Get the unique identifier for this domain object.
*
* @method DomainObject#getId
* @return {string} the domain object's unique identifier
*/
/**
* Get the domain object's model. This is useful to
* directly look up known properties of an object, but
* direct modification of a returned model is generally
* discouraged and may result in errors. Instead, an
* object's `mutation` capability should be used.
*
* @method DomainObject#getModel
* @return {object} the domain object's persistent state
*/
/**
* Get a capability associated with this object.
* Capabilities are looked up by string identifiers;
* prior knowledge of a capability's interface is
* necessary.
*
* @method DomainObject#getCapability
* @param {string} key the identifier for the capability
* @return {Capability} the named capability, or undefined
* if not present.
*/
/**
* Check if this domain object supports a capability
* with the provided name.
*
* @method DomainObject#hasCapability
* @param {string} key the identifier for the capability
* @return {boolean} true if this domain object has this capability
*/
/**
* Use a capability of an object; the behavior of this method
* depends on the interface of the capability, and whether
* or not it is present.
*
* * If the capability is not present for this object,
* no operation occurs.
* * If the capability is present and has an `invoke` method,
* that method is called with any additional arguments
* provided, and its return value is returned.
* * If the capability is present but has no `invoke` method,
* this capability itself is returned.
*
* @method DomainObject#useCapability
* @param {string} name the name of the capability to invoke
* @param {...*} [arguments] to pass to the invocation
* @returns {*|Capability} the result of invocation (see description)
*/
/**
* Construct a new domain object with the specified
* identifier, model, and capabilities.
*
* @param {string} id the object's unique identifier
* @param {object} model the "JSONifiable" state of the object
* @param {Object.<string, Capability>|function} capabilities all
* capabilities to be exposed by this object
* @memberof platform/core
* @constructor
*/
function DomainObjectImpl(id, model, capabilities) {
this.id = id;
this.model = model;
this.capabilities = capabilities;
}
DomainObjectImpl.prototype.getId = function () {
return this.id;
};
DomainObjectImpl.prototype.getModel = function () {
return this.model;
};
DomainObjectImpl.prototype.getCapability = function (name) {
var capability = this.capabilities[name];
return typeof capability === 'function' ?
capability(this) : capability;
};
DomainObjectImpl.prototype.hasCapability = function (name) {
return this.getCapability(name) !== undefined;
};
DomainObjectImpl.prototype.useCapability = function (name) {
// Get tail of args to pass to invoke
var args = Array.prototype.slice.apply(arguments, [1]),
capability = this.getCapability(name);
return (capability && capability.invoke) ?
capability.invoke.apply(capability, args) :
capability;
};
return DomainObjectImpl;
}
);

View File

@ -27,10 +27,31 @@
* @namespace platform/core
*/
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;
}

View File

@ -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() {

View File

@ -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) {

View File

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

View File

@ -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

View File

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

View File

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

View File

@ -76,6 +76,23 @@ define(
}
}
/**
* Convert a value from its format as read from a form, to a
* format appropriate to store in a model.
* @method platform/core.TypePropertyConversion#toModelValue
* @param {*} formValue value as read from a form
* @returns {*} value to store in a model
*/
/**
* Convert a value from its format as stored in a model, to a
* format appropriate to display in a form.
* @method platform/core.TypePropertyConversion#toFormValue
* @param {*} modelValue value as stored in a model
* @returns {*} value to display within a form
*/
return TypePropertyConversion;
}
);

View File

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

View File

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

View File

@ -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;
}

View File

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

View File

@ -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 () {

View File

@ -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'] }