[Core] Bring in core bundle from sandbox

Bring in bundle platform/core from the sandbox
branch, in preparation for clean up, tests, and
integration. WTD-573
This commit is contained in:
Victor Woeltjen 2014-11-20 12:58:21 -08:00
parent c50ca2e92b
commit 0fdce798f7
38 changed files with 2914 additions and 0 deletions

147
platform/core/bundle.json Normal file
View File

@ -0,0 +1,147 @@
{
"name": "Open MCT Web Core",
"description": "Defines core concepts of Open MCT Web.",
"sources": "src",
"extensions": {
"components": [
{
"provides": "objectService",
"type": "provider",
"implementation": "objects/DomainObjectProvider.js",
"depends": [ "modelService", "capabilityService" ]
},
{
"provides": "capabilityService",
"type": "provider",
"implementation": "capabilities/CoreCapabilityProvider.js",
"depends": [ "capabilities[]" ]
},
{
"provides": "modelService",
"type": "provider",
"implementation": "models/StaticModelProvider.js",
"depends": [ "models[]", "$log" ]
},
{
"provides": "modelService",
"type": "provider",
"implementation": "models/RootModelProvider.js",
"depends": [ "roots[]", "$log" ]
},
{
"provides": "modelService",
"type": "aggregator",
"implementation": "models/ModelAggregator.js"
},
{
"provides": "modelService",
"type": "provider",
"implementation": "models/PersistedModelProvider.js",
"depends": [ "persistenceService", "$q", "PERSISTENCE_SPACE" ]
},
{
"provides": "typeService",
"type": "provider",
"implementation": "types/TypeProvider.js",
"depends": [ "types[]" ]
},
{
"provides": "actionService",
"type": "provider",
"implementation": "actions/ActionProvider.js",
"depends": [ "actions[]" ]
},
{
"provides": "actionService",
"type": "aggregator",
"implementation": "actions/ActionAggregator.js"
},
{
"provides": "actionService",
"type": "decorator",
"implementation": "actions/LoggingActionDecorator.js",
"depends": [ "$log" ]
},
{
"provides": "viewService",
"type": "provider",
"implementation": "views/ViewProvider.js",
"depends": [ "views[]" ]
}
],
"types": [
{
"properties": [
{
"control": "_textfield",
"label": "Title",
"key": "name",
"property": "name",
"pattern": "\\S+",
"required": true
},
{
"control": "_checkbox",
"label": "Display title by default",
"key": "displayTitle",
"property": [ "display", "title" ]
}
]
},
{
"key": "folder",
"name": "Folder",
"glyph": "F",
"features": "creation",
"description": "A folder, useful for storing and organizing domain objects.",
"model": { "composition": [] }
}
],
"capabilities": [
{
"key": "composition",
"implementation": "capabilities/CompositionCapability.js",
"depends": [ "$injector" ]
},
{
"key": "type",
"implementation": "types/TypeCapability.js",
"depends": [ "typeService" ]
},
{
"key": "action",
"implementation": "actions/ActionCapability.js",
"depends": [ "actionService" ]
},
{
"key": "view",
"implementation": "views/ViewCapability.js",
"depends": [ "viewService" ]
},
{
"key": "persistence",
"implementation": "capabilities/PersistenceCapability.js",
"depends": [ "persistenceService", "PERSISTENCE_SPACE" ]
},
{
"key": "mutation",
"implementation": "capabilities/MutationCapability.js",
"depends": [ "$q" ]
},
{
"key": "delegation",
"implementation": "capabilities/DelegationCapability.js"
}
],
"roots": [
{
"id": "mine",
"model": {
"name": "My Items",
"type": "folder",
"composition": []
}
}
]
}
}

View File

@ -0,0 +1,30 @@
/*global define,Promise*/
define(
function () {
"use strict";
/**
* An action service factory which aggregates
* multiple other 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 {
getActions: getActions
};
}
return ActionAggregator;
}
);

View File

@ -0,0 +1,53 @@
/*global define,Promise*/
/**
* Module defining ActionCapability. Created by vwoeltje on 11/10/14.
*/
define(
[],
function () {
"use strict";
/**
*
* @constructor
*/
function ActionCapability(actionService, domainObject) {
var self;
function getActions(context) {
var baseContext = typeof context === 'string' ?
{ key: context } :
(context || {}),
actionContext = Object.create(baseContext);
actionContext.domainObject = domainObject;
return actionService.getActions(actionContext);
}
function performAction(context) {
var actions = getActions(context);
return Promise.resolve(
(actions && actions.length > 0) ?
actions[0].perform() :
undefined
);
}
self = {
invoke: function () {
return self;
},
perform: performAction,
getActions: getActions
};
return self;
}
return ActionCapability;
}
);

View File

@ -0,0 +1,88 @@
/*global define,Promise*/
/**
* Module defining ActionProvider. Created by vwoeltje on 11/7/14.
*/
define(
[],
function () {
"use strict";
/**
*
* @constructor
*/
function ActionProvider(actions) {
var actionsByKey = {},
actionsByCategory = {};
function instantiateAction(Action, context) {
var action = new Action(context),
metadata;
// Provide a getMetadata method that echos
// declarative bindings, as well as context,
// unless the action has defined its own.
if (!action.getMetadata) {
metadata = Object.create(Action.definition);
metadata.context = context;
action.getMetadata = function () {
return metadata;
};
}
return action;
}
function createIfApplicable(actions, context) {
return (actions || []).filter(function (Action) {
return Action.appliesTo ?
Action.appliesTo(context) : true;
}).map(function (Action) {
return instantiateAction(Action, context);
});
}
function getActions(actionContext) {
var context = (actionContext || {}),
category = context.category,
key = context.key,
candidates;
candidates = actions;
if (key) {
candidates = actionsByKey[key];
if (category) {
candidates = candidates.filter(function (Action) {
return Action.category === category;
});
}
} else if (category) {
candidates = actionsByCategory[category];
}
return createIfApplicable(candidates, context);
}
// Build up look-up tables
actions.forEach(function (Action) {
if (Action.category) {
actionsByCategory[Action.category] =
actionsByCategory[Action.category] || [];
actionsByCategory[Action.category].push(Action);
}
if (Action.key) {
actionsByKey[Action.key] =
actionsByKey[Action.key] || [];
actionsByKey[Action.key].push(Action);
}
});
return {
getActions: getActions
};
}
return ActionProvider;
}
);

View File

@ -0,0 +1,73 @@
/*global define*/
/**
* Defines the CreateWizard, used by the CreateAction to
* populate the form shown in dialog based on the created type.
*
* @module core/action/create-wizard
*/
define(
function () {
'use strict';
/**
* Construct a new CreateWizard.
*
* @param {TypeImpl} type the type of domain object to be created
* @param {DomainObject} parent the domain object to serve as
* the initial parent for the created object, in the dialog
* @constructor
* @memberof module:core/action/create-wizard
*/
function CreateWizard(type, parent) {
var model = type.getInitialModel(),
properties = type.getProperties();
return {
getSections: function () {
var parentRow = Object.create(parent),
sections = [];
sections.push({
label: "Properties",
rows: properties.map(function (property) {
// Property definition is same as form row definition
var row = Object.create(property.getDefinition());
// But pull an initial value from the model
row.value = property.getValue(model);
return row;
})
});
// Ensure there is always a "save in" section
parentRow.label = "Save In";
parentRow.cssclass = "selector-list";
parentRow.control = "_locator";
parentRow.key = "createParent";
sections.push({ label: 'Location', rows: [parentRow]});
return sections;
},
createModel: function (formValue) {
// Clone
var newModel = JSON.parse(JSON.stringify(model));
// Always use the type from the type definition
newModel.type = type.getKey();
// Update all properties
properties.forEach(function (property) {
var value = formValue[property.getDefinition().key];
property.setValue(newModel, value);
});
return newModel;
}
};
}
return CreateWizard;
}
);

View File

@ -0,0 +1,46 @@
/*global define,Promise*/
/**
* Module defining LoggingActionDecorator. Created by vwoeltje on 11/17/14.
*/
define(
[],
function () {
"use strict";
/**
*
* @constructor
*/
function LoggingActionDecorator($log, actionService) {
function addLogging(action) {
var logAction = Object.create(action),
domainObject =
action.getMetadata().context.domainObject;
logAction.perform = function () {
$log.info([
"Performing action ",
action.getMetadata().key,
" upon ",
domainObject && domainObject.getId()
].join(""));
return action.perform.apply(action, arguments);
};
return logAction;
}
return {
getActions: function () {
return actionService.getActions.apply(
actionService,
arguments
).map(addLogging);
}
};
}
return LoggingActionDecorator;
}
);

View File

@ -0,0 +1,138 @@
/*global define*/
/**
* Implements the Create action, which users may utilize to
* create new domain objects.
*
* @module core/action/create-action
*/
define(
['core/promises', 'core/action/create-wizard', 'uuid'],
function (promises, CreateWizard, uuid) {
"use strict";
// Handles issuing Navigate actions in order to
// display a newly-created object immediately.
function navigateTo(id, parent) {
// Look up child objects...
promises.decorate(
parent.getCapability('composition'),
function (c) { return c.list(); }
).then(
function (composition) {
// ...and find one with a matching id...
composition.forEach(function (child) {
if (child.getId() === id) {
// ...and navigate to it.
child.getCapability('action').then(
function (action) {
action.performAction('navigate', {});
}
);
}
});
}
);
}
/**
* Instantiate a new Create action for a specified type.
*
* @param {ActionContext} context the context in which the action would occur
* @param {module:core/type/type-impl.Type} type the type of domain object
* to be createdxs
* @param {DialogService} dialogService the service used to display the
* Create dialog
* @param {PersistenceService} persistenceService the service used to
* persist the model of the newly-created domain object
* @param {string} spaceName the name of the space in which to store
* the created object; used when communicating with the
* persistence service.
* @constructor CreateAction
* @memberof module:core/action/create-action
*/
return function CreateAction(context, type, dialogService, persistenceService, spaceName) {
// Invoked when the Create Action is performed
function perform() {
var id = uuid(), // get a unique id for the new object
parent = context.selection[0].object,
wizard = new CreateWizard(type, parent);
// Pop up the create dialog
dialogService.getUserInput(
{sections: wizard.getSections()},
"Create a New " + type.getName()
).then(function (userInput) {
// userInput will be undefined if cancelled
if (userInput) {
userInput.type = userInput.type || type.getKey();
// Create and persist the model for the new object.
// Note that we rely upon the persistence service
// being wired such that this model will be available
// via a model service.
persistenceService.createObject(
spaceName,
id,
wizard.createModel(userInput)
).then(function (result) {
var model,
destination = userInput.createParent || parent;
if (result) {
// Mutate the containing object: Add the newly
// created object as part of its composition.
destination.getCapability('mutation').then(
function (mutation) {
return mutation.mutate(function (model) {
model.composition = model.composition || [];
model.composition.push(id);
return model;
});
}
).then(function (mutated) {
// Persist immediately (persistence upon mutation
// is not automatic, to permit "working copy"
// changes to objects, as in edit mode)
if (mutated) {
destination.getCapability('persistence').then(
function (persistence) {
promises.as(
persistence.persist()
).then(function () {
navigateTo(id, destination);
});
}
);
}
});
}
});
}
});
}
function metadata() {
return {
id: 'create-' + type.getKey(),
name: type.getName(),
category: 'create',
glyph: type.getGlyph(),
context: context,
description: type.getDescription()
};
}
return {
perform: perform,
metadata: metadata
};
};
}
);

View File

@ -0,0 +1,235 @@
/*global define,Promise*/
/**
* Module defining duplicate action. Created by vwoeltje on 11/5/14.
*/
define(
['core/promises', 'core/action/duplicate-wizard', 'uuid'],
function (promises, DuplicateWizard, uuid) {
"use strict";
/**
* Deep-copy a domain object and its composition.
* @constructor
*/
function DuplicateAction(context, dialogService, persistenceService, spaceName) {
var object = context.selection[0].object;
function makeWizard(parameters) {
return new DuplicateWizard(
object.getModel(),
parameters.type,
parameters.parent
);
}
function getParent() {
return object.getCapability('context').then(function (c) {
return c.getParent();
});
}
function showDialog(wizard) {
return dialogService.getUserInput(
{ sections: wizard.getSections() },
"Duplicate " + object.getModel().name
);
}
function getComposition(object) {
return object.getCapability('composition').then(
function (c) { return c.list(); },
function () { return []; }
);
}
function getModels() {
var models = {};
function populateModelsFor(object) {
var id = object.getId();
// Already stored this object, don't keep going
if (models[id]) {
return models;
}
// Clone to new map
models[id] = JSON.parse(JSON.stringify(object.getModel()));
return getComposition(object).then(function (objs) {
return promises.merge(objs.map(populateModelsFor));
});
}
return populateModelsFor(object).then(function () {
return models;
});
}
function buildIdMap(ids) {
var idMap = {};
ids.forEach(function (id) {
idMap[id] = uuid();
});
return idMap;
}
function rewriteComposition(models, idMap) {
Object.keys(models).forEach(function (id) {
if (models[id].composition) {
models[id].composition = models[id].composition.map(function (childId) {
return idMap[childId] || childId;
});
}
});
return models;
}
function shouldRewrite(state, idMap) {
var keys;
function isId(key) {
return Object.prototype.hasOwnProperty.apply(idMap, [key]);
}
function and(a, b) {
return a && b;
}
keys = Object.keys(state);
return keys.length > 0 && keys.map(isId).reduce(and, true);
}
function rewriteIdentifierKeys(state, idMap) {
if (typeof state !== 'object' || state === null) {
return state;
}
if (shouldRewrite(state, idMap)) {
// Rewrite the keys of a JavaScript object
Object.keys(state).forEach(function (id) {
var newId = idMap[id] || id,
oldState = state[id];
delete state[id];
state[newId] = oldState;
});
}
// Recursively search for model contents which
// look like id maps
Object.keys(state).forEach(function (k) {
state[k] = rewriteIdentifierKeys(state[k], idMap);
});
return state;
}
function rewriteIdentifiers(models, idMap) {
var newModels = {};
Object.keys(models).forEach(function (id) {
var newId = idMap[id] || id;
newModels[newId] = models[id];
});
return newModels;
}
function doPersist(models) {
var ids = Object.keys(models);
return promises.merge(ids.map(function (id) {
return persistenceService.createObject(
spaceName,
id,
models[id]
);
}));
}
function doDuplicate(newModel) {
var idMap;
if (!newModel) {
return undefined;
}
return getModels().then(function (models) {
// Add in the model from user input
models[object.getId()] = newModel;
idMap = buildIdMap(Object.keys(models));
rewriteComposition(models, idMap);
models = rewriteIdentifiers(models, idMap);
return rewriteIdentifierKeys(models, idMap);
}).then(doPersist).then(function () {
// Return the new identifier for the object
return idMap[object.getId()];
});
}
function addToComposition(destination, id) {
function mutator(model) {
if (model.composition) {
model.composition.push(id);
}
}
return destination.getCapability('mutation').then(
function (m) { return m.mutate(mutator); }
).then(function () {
return destination.getCapability('persistence');
}).then(function (p) {
return p.persist();
});
}
function perform() {
var destination, wizard;
// Pop up the create dialog
promises.merge({
type: object.getCapability('type'),
parent: getParent()
}).then(function (params) {
// Record parent, to add to composition later
destination = params.parent;
return params;
}).then(function (params) {
wizard = makeWizard(params);
return wizard;
}).then(showDialog).then(function (formValue) {
// If user picked a different destination, use that
if (formValue && formValue.createParent) {
destination = formValue.createParent;
}
return formValue && wizard.createModel(formValue);
}).then(doDuplicate).then(function (newId) {
return addToComposition(destination, newId);
});
}
function metadata() {
return {
id: 'duplicate-' + object.getId(),
name: "Duplicate...",
category: 'contextual',
glyph: "+",
context: context,
description: "Make a copy of this object, and its contained objects."
};
}
return {
perform: perform,
metadata: metadata
};
}
return DuplicateAction;
}
);

View File

@ -0,0 +1,64 @@
/*global define,Promise*/
/**
* Module defining duplicate-wizard. Created by vwoeltje on 11/5/14.
*/
define(
[],
function () {
"use strict";
/**
*
* @constructor
*/
function DuplicateWizard(model, type, parent) {
var properties = type.getProperties();
return {
getSections: function () {
var parentRow = Object.create(parent),
sections = [];
sections.push({
label: "Properties",
rows: properties.map(function (property) {
// Property definition is same as form row definition
var row = Object.create(property.getDefinition());
// But pull an initial value from the model
row.value = property.getValue(model);
return row;
})
});
// Ensure there is always a "save in" section
parentRow.label = "Save In";
parentRow.cssclass = "selector-list";
parentRow.control = "_locator";
parentRow.key = "createParent";
sections.push({ label: 'Location', rows: [parentRow]});
return sections;
},
createModel: function (formValue) {
// Clone
var newModel = JSON.parse(JSON.stringify(model));
// Always use the type from the type definition
newModel.type = type.getKey();
// Update all properties
properties.forEach(function (property) {
var value = formValue[property.getDefinition().key];
property.setValue(newModel, value);
});
return newModel;
}
};
}
return DuplicateWizard;
}
);

View File

@ -0,0 +1,94 @@
/*global define,Promise*/
/**
* Module defining CompositionCapability. Created by vwoeltje on 11/7/14.
*/
define(
["./ContextualDomainObject"],
function (ContextualDomainObject) {
"use strict";
/**
* Composition capability. A domain object's composition is the set of
* domain objects it contains. This is available as an array of
* identifiers in the model; the composition capability makes this
* available as an array of domain object instances, which may
* require consulting the object service (e.g. to trigger a database
* query to retrieve the nested object models.)
*
* @constructor
*/
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.
*/
invoke: promiseComposition
};
}
/**
* Test to determine whether or not this capability should be exposed
* by a domain object based on its model. Checks for the presence of
* a composition field, that must be an array.
* @param model the domain object model
* @returns {boolean} true if this object has a composition
*/
CompositionCapability.appliesTo = function (model) {
return Array.isArray((model || {}).composition);
};
return CompositionCapability;
}
);

View File

@ -0,0 +1,50 @@
/*global define,Promise*/
/**
* Module defining ContextCapability. Created by vwoeltje on 11/17/14.
*/
define(
[],
function () {
"use strict";
/**
*
* @constructor
*/
function ContextCapability(parentObject, domainObject) {
var self,
parentObject;
self = {
getParent: function () {
return parentObject;
},
getPath: function () {
var parentPath = [],
parentContext;
if (parentObject) {
parentContext = parentObject.getCapability("context");
parentPath = parentContext ?
parentContext.getPath() :
[parentObject];
}
return parentPath.concat([domainObject]);
},
getRoot: function () {
return self.getPath()[0];
}
};
return self;
}
ContextCapability.appliesTo = function () {
return true;
};
return ContextCapability;
}
);

View File

@ -0,0 +1,31 @@
/*global define,Promise*/
/**
* Module defining ContextualDomainObject. Created by vwoeltje on 11/18/14.
*/
define(
["./ContextCapability"],
function (ContextCapability) {
"use strict";
/**
*
* @constructor
*/
function ContextualDomainObject(domainObject, parentObject) {
var contextualObject = Object.create(domainObject),
contextCapability =
new ContextCapability(parentObject, domainObject);
contextualObject.getCapability = function (name) {
return name === "context" ?
contextCapability :
domainObject.getCapability.apply(this, arguments);
};
return contextualObject;
}
return ContextualDomainObject;
}
);

View File

@ -0,0 +1,63 @@
/*global define,Promise*/
/**
* Module defining CoreCapabilityProvider. Created by vwoeltje on 11/7/14.
*/
define(
[],
function () {
"use strict";
/**
* Provides capabilities based on extension definitions.
* @constructor
*/
function CoreCapabilityProvider(capabilities) {
// Filter by invoking the capability's appliesTo method
function filterCapabilities(model) {
return capabilities.filter(function (capability) {
return capability.appliesTo ?
capability.appliesTo(model) :
true;
});
}
// Package capabilities as key-value pairs
function packageCapabilities(capabilities) {
var result = {};
capabilities.forEach(function (capability) {
result[capability.key] = capability;
});
return result;
}
function getCapabilities(model) {
return packageCapabilities(filterCapabilities(model));
}
return {
/**
* Get all capabilities associated with a given domain
* object.
*
* This returns a promise for an object containing key-value
* pairs, where keys are capability names and values are
* either:
*
* * Capability instances
* * Capability constructors (which take a domain object
* as their argument.)
*
*
* @param {*} model the object model
* @returns {Object.<string,function|Capability>} all
* capabilities known to be valid for this model, as
* key-value pairs
*/
getCapabilities: getCapabilities
};
}
return CoreCapabilityProvider;
}
);

View File

@ -0,0 +1,57 @@
/*global define,Promise*/
/**
* Module defining DelegationCapability. Created by vwoeltje on 11/18/14.
*/
define(
[],
function () {
'use strict';
function DelegationCapability(domainObject) {
var delegateCapabilities = {},
type = domainObject.getCapability("type");
function filterObjectsWithCapability(capability) {
return function (objects) {
return objects.filter(function (obj) {
return obj.hasCapability(capability);
});
};
}
function promiseChildren() {
return domainObject.useCapability('composition');
}
function doesDelegate(key) {
return delegateCapabilities[key] || false;
}
function getDelegates(capability) {
return doesDelegate(capability) ?
promiseChildren().then(
filterObjectsWithCapability(capability)
) :
[];
}
// Generate set for easy lookup of capability delegation
if (type && type.getDefinition) {
(type.getDefinition().delegates || []).forEach(function (key) {
delegateCapabilities[key] = true;
});
}
return {
invoke: getDelegates,
getDelegates: getDelegates,
doesDelegateCapability: doesDelegate
};
}
return DelegationCapability;
}
);

View File

@ -0,0 +1,63 @@
/*global define,Promise*/
/**
* Module defining MutationCapability. Created by vwoeltje on 11/12/14.
*/
define(
[],
function () {
"use strict";
// Utility function to overwrite a destination object
// with the contents of a source object.
function copyValues(destination, source) {
// First, remove all previously-existing keys
Object.keys(destination).forEach(function (k) {
delete destination[k];
});
// Second, write all new keys
Object.keys(source).forEach(function (k) {
destination[k] = source[k];
});
}
function MutationCapability($q, domainObject) {
function mutate(mutator) {
// 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));
// 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) {
copyValues(model, result);
model.modified = Date.now();
}
// Report the result of the mutation
return mutationResult !== false;
}
// Invoke the provided mutator, then make changes to
// the underlying model (if applicable.)
return $q.when(mutator(clone))
.then(handleMutation);
}
return {
invoke: mutate
};
}
return MutationCapability;
}
);

View File

@ -0,0 +1,38 @@
/*global define*/
/**
* Defines the "persistence" capability, used to indicate
* that changes to an object should be written to some
* underlying store.
*
* Current implementation is a stub that simply triggers
* a refresh on modified views, which is a necessary
* side effect of persisting the object.
*/
define(
function () {
'use strict';
function PersistenceCapability(persistenceService, SPACE, domainObject) {
var self = {
persist: function () {
return persistenceService.updateObject(
SPACE,
domainObject.getId(),
domainObject.getModel()
);
},
getSpace: function () {
return SPACE;
},
invoke: function () {
return self;
}
};
return self;
}
return PersistenceCapability;
}
);

View File

@ -0,0 +1,41 @@
/*global define,Promise*/
/**
* Module defining ModelAggregator. Created by vwoeltje on 11/7/14.
*/
define(
[],
function () {
"use strict";
/**
*
* @constructor
*/
function ModelAggregator(providers) {
function mergeModels(provided, ids) {
var result = {};
ids.forEach(function (id) {
provided.forEach(function (models) {
if (models[id]) {
result[id] = models[id];
}
});
});
return result;
}
return {
getModels: function (ids) {
return Promise.all(providers.map(function (provider) {
return provider.getModels(ids);
})).then(function (provided) {
return mergeModels(provided, ids);
});
}
};
}
return ModelAggregator;
}
);

View File

@ -0,0 +1,36 @@
/*global define,Promise*/
/**
* Module defining PersistedModelProvider. Created by vwoeltje on 11/12/14.
*/
define(
[],
function () {
"use strict";
/**
*
* @constructor
*/
function PersistedModelProvider(persistenceService, $q, SPACE) {
function promiseModels(ids) {
return $q.all(ids.map(function (id) {
return persistenceService.readObject(SPACE, id);
})).then(function (models) {
var result = {};
ids.forEach(function (id, index) {
result[id] = models[index];
});
return result;
});
}
return {
getModels: promiseModels
};
}
return PersistedModelProvider;
}
);

View File

@ -0,0 +1,41 @@
/*global define,Promise*/
/**
* Module defining RootModelProvider. Created by vwoeltje on 11/7/14.
*/
define(
['./StaticModelProvider.js'],
function (StaticModelProvider) {
"use strict";
/**
* The root model provider works as the static model provider,
* except that it aggregates roots[] instead of models[], and
* exposes them all as composition of the root object ROOT.
*
* @constructor
*/
function RootModelProvider(roots, $log) {
var ids = roots.map(function (root) {
return root.id;
}),
baseProvider = new StaticModelProvider(roots, $log);
function addRoot(models) {
models.ROOT = {
name: "The root object",
composition: ids
};
return models;
}
return {
getModels: function(ids) {
return baseProvider.getModels(ids).then(addRoot);
}
};
}
return RootModelProvider;
}
);

View File

@ -0,0 +1,64 @@
/*global define,Promise*/
/**
* Module defining StaticModelProvider. Created by vwoeltje on 11/7/14.
*/
define(
[],
function () {
"use strict";
/**
* Loads static models, provided as declared extensions of bundles.
* @constructor
*/
function StaticModelProvider(models, $log) {
var modelMap = {};
function addModelToMap(model) {
// Skip models which don't look right
if (typeof model !== 'object' ||
typeof model.id !== 'string' ||
typeof model.model !== 'object') {
$log.warn([
"Skipping malformed domain object model exposed by ",
((model || {}).bundle || {}).path
].join(""));
} else {
modelMap[model.id] = model.model;
}
}
// 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#
*/
getModels: function (ids) {
var result = {};
ids.forEach(function (id) {
result[id] = modelMap[id];
});
return Promise.resolve(result);
}
};
}
return StaticModelProvider;
}
);

View File

@ -0,0 +1,107 @@
/*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
* @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#
*/
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#
*/
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#
*/
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
*/
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#
*/
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,85 @@
/*global define,Promise*/
/**
* Module defining DomainObjectProvider. Created by vwoeltje on 11/7/14.
*/
define(
["./DomainObject"],
function (DomainObject) {
"use strict";
/**
* Construct a new provider for domain objects.
*
* @param {ModelService} modelService the service which shall
* provide models (persistent state) for domain objects
* @param {CapabilityService} capabilityService the service
* which provides capabilities (dynamic behavior)
* for domain objects.
* @constructor
*/
function DomainObjectProvider(modelService, capabilityService) {
// 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.
function capabilityResolver(models) {
return function (id) {
var model = models[id];
return model ?
capabilityService.getCapabilities(model) :
undefined;
};
}
// Assemble the results from the model service and the
// capability service into one value, suitable to return
// from this service. Note that ids are matched to capabilities
// by index.
function assembleResult(ids, models, capabilities) {
var result = {};
ids.forEach(function (id, index) {
if (models[id]) {
// Create the domain object
result[id] = new DomainObject(
id,
models[id],
capabilities[index]
);
}
});
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 Promise.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#
*/
getObjects: getObjects
};
}
return DomainObjectProvider;
}
);

View File

@ -0,0 +1,50 @@
/*global define*/
/**
* Defines MergedModel, which allows a deep merge of domain object
* models, or JSONifiable JavaScript objects generally.
*
* @module core/model/merged-model
*/
define(
function () {
'use strict';
function mergeModels(a, b, merger) {
var mergeFunction;
function mergeArrays(a, b) {
return a.concat(b);
}
function mergeObjects(a, b) {
var result = {};
Object.keys(a).forEach(function (k) {
result[k] = b.hasOwnProperty(k) ?
mergeModels(a[k], b[k], (merger || {})[k]) :
a[k];
});
Object.keys(b).forEach(function (k) {
// Copy any properties not already merged
if (!a.hasOwnProperty(k)) {
result[k] = b[k];
}
});
return result;
}
function mergeOther(a, b) {
return b;
}
mergeFunction = merger && Function.isFunction(merger) ? merger :
(Array.isArray(a) && Array.isArray(b)) ? mergeArrays :
(a instanceof Object && b instanceof Object) ? mergeObjects :
mergeOther;
return mergeFunction(a, b);
}
return mergeModels;
}
);

View File

@ -0,0 +1,27 @@
/*global define,Promise*/
/**
* Module defining TypeCapability. Created by vwoeltje on 11/10/14.
*/
define(
[],
function () {
"use strict";
/**
*
* @constructor
*/
function TypeCapability(typeService, domainObject) {
var typeKey = domainObject.getModel().type,
type = typeService.getType(typeKey),
self = Object.create(type);
self.invoke = function () { return self; };
return self;
}
return TypeCapability;
}
);

View File

@ -0,0 +1,113 @@
/*global define,Promise*/
/**
* Type capability provider. Provides capabilities for domain objects based
* on properties defined as part of their type, where type is inferred from
* a string with key "type" in the model.
*
* These capabilities include the "type" capability, which providers
* information about an object's type not stored in the model.
*
* @module core/type/type-capability-provider
*/
define(
[],
function () {
'use strict';
var promises = {
merge: Promise.all,
decorate: function (promise, callback) {
return promise.then(callback);
},
as: function (value) {
return Promise.resolve(value);
}
};
/**
* Construct a new type capability provider. The provided type
* service will be used to obtain type information; the provided
* module service will be used to load modules which define
* factories for capabilities associated with types.
*
* @param {TypeService} typeService the service which will
* provide type information/definitions
* @param {ModuleService} moduleService the service which
* shall allow other JavaScript modules to be loaded.
* @constructor
* @memberof module:core/type/type-capability-provider
*/
function TypeCapabilityProvider(typeService, moduleService) {
var typeFactories = {};
function buildTypeFactory(type) {
var capabilities = type.getDefinition().capabilities || [];
return promises.decorate(
promises.merge(capabilities.map(moduleService.get)),
function (modules) {
var factory = {};
modules.filter(function (mod) {
return mod;
}).forEach(function (mod) {
var capFactories = mod.capabilities || {};
Object.keys(capFactories).forEach(function (k) {
factory[k] = capFactories[k];
});
});
// And the "type" capability
factory.type = function () {
return type;
};
return factory;
}
);
}
/**
* Description (TODO)
* @param {string} typeKey Description
* @returns {string} Description TODO
*/
function promiseTypeFactory(typeKey) {
return (typeFactories[typeKey] = typeFactories[typeKey] || promises.decorate(
typeService.getType(typeKey),
buildTypeFactory
));
}
return {
/**
* Get a capability which should be expressed by a domain
* object. This will return a promise for the capability instance;
* if the named capaability is not provided for that domain
* object by this service, the result of the fulfilled promise
* will be undefined.
*
* @param {module:core/api/domain-object.DomainObject} domainObject the
* domain object which may or may not express this capability
* @param {string} name the name of the capability requested
* @memberof module:core/type/type-capability-provider.TypeCapabilityProvider#
*/
getCapability: function (domainObject, name) {
var typeKey = domainObject.getModel().type;
return promises.decorate(
promiseTypeFactory(typeKey),
function (typeFactory) {
return typeFactory[name] ?
typeFactory[name](domainObject) :
undefined;
}
);
}
};
}
return {
instantiate: TypeCapabilityProvider
};
}
);

View File

@ -0,0 +1,153 @@
/*global define*/
/**
* Type implementation. Defines a type object which wraps a
* type definition and exposes useful methods for inspecting
* that type and understanding its relationship to other
* types.
*
* @module core/type/type-impl
*/
define(
['./TypeProperty'],
function (TypeProperty) {
"use strict";
/**
* Construct a new type. Types describe categories of
* domain objects.
*
* @param {TypeDefinition} typeDef an object containing
* key-value pairs describing a type and its
* relationship to other types.
* @memberof module:core/type/type-impl
*/
function TypeImpl(typeDef) {
var inheritList = typeDef.inherits || [],
featureSet = {};
(typeDef.features || []).forEach(function (feature) {
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#
*/
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#
*/
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#
*/
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#
*/
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
*/
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
*/
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#
*/
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#
*/
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
*/
hasFeature: function (feature) {
return featureSet[feature] || false;
}
};
}
return TypeImpl;
}
);

View File

@ -0,0 +1,117 @@
/*global define*/
/**
* Type property. Defines a mutable or displayable property
* associated with objects of a given type.
*
* @module core/type/type-property
*/
define(
['./TypePropertyConversion'],
function (TypePropertyConversion) {
'use strict';
/**
* Instantiate a property associated with domain objects of a
* given type. This provides an interface by which
*
* @constructor
* @memberof module:core/type/type-property
*/
function TypeProperty(propertyDefinition) {
// Load an appropriate conversion
var conversion = new TypePropertyConversion(
propertyDefinition.conversion || "identity"
);
// 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;
}
// If path is not an array, just look up the property
if (!Array.isArray(propertyPath)) {
return object[propertyPath];
}
// 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;
} else {
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;
}
}
return {
/**
* Retrieve the value associated with this property
* from a given model.
*/
getValue: function (model) {
var property = propertyDefinition.property ||
propertyDefinition.key;
if (property) {
return conversion.toFormValue(
lookupValue(model, property)
);
} else {
return undefined;
}
},
/**
* Set a value associated with this property in
* an object's model.
*/
setValue: function setValue(model, value) {
var property = propertyDefinition.property ||
propertyDefinition.key;
value = conversion.toModelValue(value);
if (property) {
return specifyValue(model, property, value);
} else {
return undefined;
}
},
/**
* Get the raw definition for this property.
*/
getDefinition: function () {
return propertyDefinition;
}
};
}
return TypeProperty;
}
);

View File

@ -0,0 +1,64 @@
/*global define*/
/**
* Defines type property conversions, used to convert values from
* a domain object model to values displayable in a form, and
* vice versa.
* @module core/type/type-property-conversion
*/
define(
function () {
'use strict';
var conversions = {
number: {
toModelValue: parseFloat,
toFormValue: function (modelValue) {
return (typeof modelValue === 'number') ?
modelValue.toString(10) : undefined;
}
},
identity: {
toModelValue: function (v) { return v; },
toFormValue: function (v) { return v; }
}
},
ARRAY_SUFFIX = '[]';
// Utility function to handle arrays of conversiions
function ArrayConversion(conversion) {
return {
toModelValue: function (formValue) {
return formValue && formValue.map(conversion.toModelValue);
},
toFormValue: function (modelValue) {
return modelValue && modelValue.map(conversion.toFormValue);
}
};
}
/**
* Look up an appropriate conversion between form values and model
* values, e.g. to numeric values.
*/
function TypePropertyConversion(name) {
if (name &&
name.length > ARRAY_SUFFIX.length &&
name.indexOf(ARRAY_SUFFIX, name.length - ARRAY_SUFFIX.length) !== -1) {
return new ArrayConversion(
new TypePropertyConversion(
name.substring(0, name.length - ARRAY_SUFFIX.length)
)
);
} else {
if (!conversions[name]) {
throw new Error("Unknown conversion type: " + name);
}
return conversions[name];
}
}
return TypePropertyConversion;
}
);

View File

@ -0,0 +1,185 @@
/*global define, Promise*/
/**
* Provides information about types of domain objects within the running
* Open MCT Web instance.
*
* @module core/type/type-provider
*/
define(
['./TypeImpl', './MergeModels'],
function (TypeImpl, mergeModels) {
'use strict';
var promises = {
merge: Promise.all,
decorate: function (promise, callback) {
return promise.then(callback);
},
as: function (value) {return Promise.resolve(value); }
},
TO_CONCAT = ['inherits', 'capabilities', 'properties', 'features'],
TO_MERGE = ['model'];
function copyKeys(a, b) {
Object.keys(b).forEach(function (k) {
a[k] = b[k];
});
}
function removeDuplicates(array) {
var set = {};
return array ? array.filter(function (element) {
// Don't filter objects (e.g. property definitions)
if (element instanceof Object && !(element instanceof String)) {
return true;
}
return set[element] ?
false :
(set[element] = true);
}) : array;
}
/**
* Instantiate a new type provider.
*
* @param {Array<TypeDefinition>} options.definitions the raw type
* definitions for this type.
* @constructor
* @memberof module:core/type/type-provider
*/
function TypeProvider(types) {
var rawTypeDefinitions = types,
typeDefinitions = (function (typeDefArray) {
var result = {};
typeDefArray.forEach(function (typeDef) {
var k = typeDef.key;
if (k) {
result[k] = (result[k] || []).concat(typeDef);
}
});
return result;
}(rawTypeDefinitions)),
typeMap = {},
undefinedType;
// Reduce an array of type definitions to a single type definiton,
// which has merged all properties in order.
function collapse(typeDefs) {
var collapsed = typeDefs.reduce(function (a, b) {
var result = {};
copyKeys(result, a);
copyKeys(result, b);
// Special case: Do a merge, e.g. on "model"
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;
}
function getUndefinedType() {
return (undefinedType = undefinedType || collapse(
rawTypeDefinitions.filter(function (typeDef) {
return !typeDef.key;
})
));
}
function asArray(value) {
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")
);
return def;
}
return (typeMap[typeKey] = 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#
*/
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#
*/
getType: function (key) {
return new TypeImpl(lookupTypeDef(key));
}
};
}
// Services framework is designed to expect factories
TypeProvider.instantiate = TypeProvider;
return TypeProvider;
}
);

View File

@ -0,0 +1,70 @@
/*global define*/
/**
* Default type wizard. Type wizards provide both a declarative
* description of the form which should be presented to a user
* when a new domain object of a given type is instantiatd, as
* well as the necessary functionality to convert this user input
* to a model for a domain object.
*
* This wizard is intended to be both a general-purpose default
* and a useful supertype; custom wizards for types which
* require additional information should use this as a base
* and append to the results of getSections(..) or add
* properties to the result of createModel(..) as appropriate
* to the more specific type.
*
* @module core/type/type-wizard
*/
define(
{
/**
* Get all sections appropriate to the display of this type.
*
* @returns {Array<FormSection>}
* @method
*/
getSections: function (typeKey) {
'use strict';
return [
{
label: "Title Options",
rows: [
{
control: '_textfield',
label: "Title",
key: "name"
},
{
control: '_checkbox',
label: "Display title by default",
key: "displayTitle"
}
]
}
];
},
/**
* Create a model for a domain object based on user input.
*
* @param {object} formValue an object containing key-value
* pairs, where keys are properties indicated as part
* of a form's definition, and values are the result
* of user input.
* @return {object} the new model for a domain object
*/
createModel: function (formValue) {
'use strict';
var model = {
type: formValue.type,
name: formValue.name,
display: { title: formValue.displayTitle }
};
return model;
}
}
);

View File

@ -0,0 +1,25 @@
/*global define,Promise*/
/**
* Module defining ViewCapability. Created by vwoeltje on 11/10/14.
*/
define(
[],
function () {
"use strict";
/**
*
* @constructor
*/
function ViewCapability(viewService, domainObject) {
return {
invoke: function () {
return viewService.getViews(domainObject);
}
};
}
return ViewCapability;
}
);

View File

@ -0,0 +1,55 @@
/*global define,Promise*/
/**
* Module defining ViewProvider. Created by vwoeltje on 11/10/14.
*/
define(
[],
function () {
"use strict";
/**
*
* @constructor
*/
function ViewProvider(views) {
function capabilitiesMatch(domainObject, capabilities, allowDelegation) {
var delegation = domainObject.getCapability("delegation");
allowDelegation = allowDelegation && (delegation !== undefined);
function hasCapability(c) {
return domainObject.hasCapability(c) ||
(allowDelegation && delegation.doesDelegateCapability(c));
}
function and(a, b) {
return a && b;
}
return capabilities.map(hasCapability).reduce(and, true);
}
function getViews(domainObject) {
var type = domainObject.useCapability("type");
return views.filter(function (view) {
return (!view.type) || type.instanceOf(view.type);
}).filter(function (view) {
return capabilitiesMatch(
domainObject,
view.needs || [],
view.delegation || false
);
});
}
return {
getViews: getViews
};
}
return ViewProvider;
}
);

View File

@ -0,0 +1,6 @@
[
"types/TypeImpl",
"types/TypeProperty",
"types/TypePropertyConversion",
"types/TypeProvider"
]

View File

@ -0,0 +1,13 @@
/*global define,describe,it,beforeEach,expect*/
define(
['../../src/types/TypeCapabilityProvider'],
function () {
'use strict';
describe("Type capability provider", function () {
});
}
);

View File

@ -0,0 +1,72 @@
/*global define,describe,it,expect,beforeEach*/
define(
['../../src/types/TypeImpl'],
function (typeImpl) {
"use strict";
describe("Type definition wrapper", function () {
var testTypeDef,
type;
beforeEach(function () {
testTypeDef = {
key: 'test-type',
name: 'Test Type',
description: 'A type, for testing',
glyph: 't',
inherits: ['test-parent-1', 'test-parent-2']
};
type = typeImpl(testTypeDef);
});
it("exposes key from definition", function () {
expect(type.getKey()).toEqual('test-type');
});
it("exposes name from definition", function () {
expect(type.getName()).toEqual('Test Type');
});
it("exposes description from definition", function () {
expect(type.getDescription()).toEqual('A type, for testing');
});
it("exposes glyph from definition", function () {
expect(type.getGlyph()).toEqual('t');
});
it("exposes its underlying type definition", function () {
expect(type.getDefinition()).toEqual(testTypeDef);
});
it("supports instance-of checks by type key", function () {
expect(type.instanceOf('test-parent-1')).toBeTruthy();
expect(type.instanceOf('test-parent-2')).toBeTruthy();
expect(type.instanceOf('some-other-type')).toBeFalsy();
});
it("supports instance-of checks by specific type key", function () {
expect(type.instanceOf('test-type')).toBeTruthy();
});
it("supports instance-of checks by type object", function () {
expect(type.instanceOf({
getKey: function () { return 'test-parent-1'; }
})).toBeTruthy();
expect(type.instanceOf({
getKey: function () { return 'some-other-type'; }
})).toBeFalsy();
});
it("correctly recognizes instance-of checks upon itself", function () {
expect(type.instanceOf(type)).toBeTruthy();
});
it("recognizes that all types are instances of the undefined type", function () {
expect(type.instanceOf()).toBeTruthy();
expect(type.instanceOf({ getKey: function () {} })).toBeTruthy();
});
});
}
);

View File

@ -0,0 +1,45 @@
/*global define,describe,it,xit,expect,beforeEach*/
define(
['../../src/types/TypePropertyConversion'],
function (TypePropertyConversion) {
"use strict";
describe("Type property conversion", function () {
it("allows non-conversion when parameter is 'identity'", function () {
var conversion = new TypePropertyConversion("identity");
[ 42, "42", { a: 42 } ].forEach(function (v) {
expect(conversion.toFormValue(v)).toBe(v);
expect(conversion.toModelValue(v)).toBe(v);
});
});
it("allows numeric conversion", function () {
var conversion = new TypePropertyConversion("number");
expect(conversion.toFormValue(42)).toBe("42");
expect(conversion.toModelValue("42")).toBe(42);
});
it("supports array conversions", function () {
var conversion = new TypePropertyConversion("number[]");
expect(conversion.toFormValue([42, 44]).length).toEqual(2);
expect(conversion.toFormValue([42, 44])[0]).toBe("42");
expect(conversion.toModelValue(["11", "42"])[1]).toBe(42);
});
it("throws exceptions on unrecognized conversions", function () {
var caught = false, tmp;
try {
tmp = new TypePropertyConversion("some-unknown-conversion");
} catch (e) {
caught = true;
}
expect(caught).toBeTruthy();
});
});
}
);

View File

@ -0,0 +1,61 @@
/*global define,describe,it,xit,expect,beforeEach*/
define(
['../../src/types/TypeProperty'],
function (TypeProperty) {
"use strict";
describe("Type property", function () {
it("allows retrieval of its definition", function () {
var definition = { key: "hello", someOtherKey: "hm?" };
expect(
new TypeProperty(definition).getDefinition()
).toEqual(definition);
});
it("sets properties in object models", function () {
var definition = {
key: "someKey",
property: "someProperty"
},
model = {},
property = new TypeProperty(definition);
property.setValue(model, "some value");
expect(model.someProperty).toEqual("some value");
});
it("gets properties from object models", function () {
var definition = {
key: "someKey",
property: "someProperty"
},
model = { someProperty: "some value"},
property = new TypeProperty(definition);
expect(property.getValue(model)).toEqual("some value");
});
it("sets properties by path", function () {
var definition = {
key: "someKey",
property: [ "some", "property" ]
},
model = {},
property = new TypeProperty(definition);
property.setValue(model, "some value");
expect(model.some.property).toEqual("some value");
});
it("gets properties by path", function () {
var definition = {
key: "someKey",
property: [ "some", "property" ]
},
model = { some: { property: "some value" } },
property = new TypeProperty(definition);
expect(property.getValue(model)).toEqual("some value");
});
});
}
);

View File

@ -0,0 +1,214 @@
/*global define,describe,it,expect,beforeEach, waitsFor, runs*/
define(
['../../src/types/TypeProvider'],
function (typeProviderModule) {
"use strict";
describe("Type provider", function () {
var captured = {},
capture = function (name) {
return function (value) {
captured[name] = value;
};
},
testTypeDefinitions = [
{
key: 'basic',
glyph: "X",
name: "Basic Type"
},
{
key: 'multi1',
glyph: "Z",
description: "Multi1 Description",
capabilities: ['a1', 'b1']
},
{
key: 'multi2',
glyph: "Y",
capabilities: ['a2', 'b2', 'c2']
},
{
key: 'single-subtype',
inherits: 'basic',
name: "Basic Subtype",
description: "A test subtype"
},
{
key: 'multi-subtype',
inherits: ['multi1', 'multi2'],
name: "Multi-parent Subtype",
capabilities: ['a3']
},
{
name: "Default"
}
],
provider;
beforeEach(function () {
captured = {};
provider = typeProviderModule.instantiate({
definitions: testTypeDefinitions
});
});
it("can be instantiated from a factory method", function () {
expect(provider).toBeTruthy();
});
it("looks up non-inherited types by name", function () {
provider.getType('basic').then(capture('type'));
waitsFor(
function () {
return captured.type !== undefined;
},
"promise resolution",
250
);
runs(function () {
expect(captured.type.getGlyph()).toEqual("X");
expect(captured.type.getName()).toEqual("Basic Type");
expect(captured.type.getDescription()).toBeUndefined();
});
});
it("supports single inheritance", function () {
provider.getType('single-subtype').then(capture('type'));
waitsFor(
function () {
return captured.type !== undefined;
},
"promise resolution",
250
);
runs(function () {
expect(captured.type.getGlyph()).toEqual("X");
expect(captured.type.getName()).toEqual("Basic Subtype");
expect(captured.type.getDescription()).toEqual("A test subtype");
});
});
it("supports multiple inheritance", function () {
provider.getType('multi-subtype').then(capture('type'));
waitsFor(
function () {
return captured.type !== undefined;
},
"promise resolution",
250
);
runs(function () {
expect(captured.type.getGlyph()).toEqual("Y");
expect(captured.type.getName()).toEqual("Multi-parent Subtype");
expect(captured.type.getDescription()).toEqual("Multi1 Description");
});
});
it("concatenates capabilities in order", function () {
provider.getType('multi-subtype').then(capture('type'));
waitsFor(
function () {
return captured.type !== undefined;
},
"promise resolution",
250
);
runs(function () {
expect(captured.type.getDefinition().capabilities).toEqual(
['a1', 'b1', 'a2', 'b2', 'c2', 'a3']
);
});
});
it("allows lookup of the undefined type", function () {
provider.getType(undefined).then(capture('type'));
waitsFor(
function () {
return captured.type !== undefined;
},
"promise resolution",
250
);
runs(function () {
expect(captured.type.getName()).toEqual("Default");
});
});
it("concatenates capabilities of all undefined types", function () {
typeProviderModule.instantiate({
definitions: testTypeDefinitions.concat([
{
capabilities: ['a', 'b', 'c']
},
{
capabilities: ['x', 'y', 'z']
}
])
}).getType(undefined).then(capture('type'));
waitsFor(
function () {
return captured.type !== undefined;
},
"promise resolution",
250
);
runs(function () {
expect(captured.type.getDefinition().capabilities).toEqual(
['a', 'b', 'c', 'x', 'y', 'z']
);
});
});
it("includes capabilities from undefined type in all types", function () {
typeProviderModule.instantiate({
definitions: testTypeDefinitions.concat([
{
capabilities: ['a', 'b', 'c']
},
{
capabilities: ['x', 'y', 'z']
}
])
}).getType('multi-subtype').then(capture('type'));
waitsFor(
function () {
return captured.type !== undefined;
},
"promise resolution",
250
);
runs(function () {
expect(captured.type.getDefinition().capabilities).toEqual(
['a', 'b', 'c', 'x', 'y', 'z', 'a1', 'b1', 'a2', 'b2', 'c2', 'a3']
);
});
});
it("allows types to be listed", function () {
provider.listTypes().then(capture('types'));
waitsFor(
function () {
return captured.types !== undefined;
},
"promise resolution",
250
);
runs(function () {
expect(captured.types.length).toEqual(
testTypeDefinitions.filter(function (t) {
return t.key;
}).length
);
});
});
});
}
);