mirror of
https://github.com/nasa/openmct.git
synced 2025-01-30 16:13:53 +00:00
[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:
parent
c50ca2e92b
commit
0fdce798f7
147
platform/core/bundle.json
Normal file
147
platform/core/bundle.json
Normal 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": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
30
platform/core/src/actions/ActionAggregator.js
Normal file
30
platform/core/src/actions/ActionAggregator.js
Normal 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;
|
||||||
|
}
|
||||||
|
);
|
53
platform/core/src/actions/ActionCapability.js
Normal file
53
platform/core/src/actions/ActionCapability.js
Normal 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;
|
||||||
|
}
|
||||||
|
);
|
88
platform/core/src/actions/ActionProvider.js
Normal file
88
platform/core/src/actions/ActionProvider.js
Normal 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;
|
||||||
|
}
|
||||||
|
);
|
73
platform/core/src/actions/CreateWizard.js
Normal file
73
platform/core/src/actions/CreateWizard.js
Normal 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;
|
||||||
|
}
|
||||||
|
);
|
46
platform/core/src/actions/LoggingActionDecorator.js
Normal file
46
platform/core/src/actions/LoggingActionDecorator.js
Normal 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;
|
||||||
|
}
|
||||||
|
);
|
138
platform/core/src/actions/create-action.js
Normal file
138
platform/core/src/actions/create-action.js
Normal 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
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
235
platform/core/src/actions/duplicate-action.js
Normal file
235
platform/core/src/actions/duplicate-action.js
Normal 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;
|
||||||
|
}
|
||||||
|
);
|
64
platform/core/src/actions/duplicate-wizard.js
Normal file
64
platform/core/src/actions/duplicate-wizard.js
Normal 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;
|
||||||
|
}
|
||||||
|
);
|
94
platform/core/src/capabilities/CompositionCapability.js
Normal file
94
platform/core/src/capabilities/CompositionCapability.js
Normal 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;
|
||||||
|
}
|
||||||
|
);
|
50
platform/core/src/capabilities/ContextCapability.js
Normal file
50
platform/core/src/capabilities/ContextCapability.js
Normal 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;
|
||||||
|
}
|
||||||
|
);
|
31
platform/core/src/capabilities/ContextualDomainObject.js
Normal file
31
platform/core/src/capabilities/ContextualDomainObject.js
Normal 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;
|
||||||
|
}
|
||||||
|
);
|
63
platform/core/src/capabilities/CoreCapabilityProvider.js
Normal file
63
platform/core/src/capabilities/CoreCapabilityProvider.js
Normal 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;
|
||||||
|
}
|
||||||
|
);
|
57
platform/core/src/capabilities/DelegationCapability.js
Normal file
57
platform/core/src/capabilities/DelegationCapability.js
Normal 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;
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
63
platform/core/src/capabilities/MutationCapability.js
Normal file
63
platform/core/src/capabilities/MutationCapability.js
Normal 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;
|
||||||
|
}
|
||||||
|
);
|
38
platform/core/src/capabilities/PersistenceCapability.js
Normal file
38
platform/core/src/capabilities/PersistenceCapability.js
Normal 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;
|
||||||
|
}
|
||||||
|
);
|
41
platform/core/src/models/ModelAggregator.js
Normal file
41
platform/core/src/models/ModelAggregator.js
Normal 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;
|
||||||
|
}
|
||||||
|
);
|
36
platform/core/src/models/PersistedModelProvider.js
Normal file
36
platform/core/src/models/PersistedModelProvider.js
Normal 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;
|
||||||
|
}
|
||||||
|
);
|
41
platform/core/src/models/RootModelProvider.js
Normal file
41
platform/core/src/models/RootModelProvider.js
Normal 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;
|
||||||
|
}
|
||||||
|
);
|
64
platform/core/src/models/StaticModelProvider.js
Normal file
64
platform/core/src/models/StaticModelProvider.js
Normal 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;
|
||||||
|
}
|
||||||
|
);
|
107
platform/core/src/objects/DomainObject.js
Normal file
107
platform/core/src/objects/DomainObject.js
Normal 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;
|
||||||
|
}
|
||||||
|
);
|
85
platform/core/src/objects/DomainObjectProvider.js
Normal file
85
platform/core/src/objects/DomainObjectProvider.js
Normal 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;
|
||||||
|
}
|
||||||
|
);
|
50
platform/core/src/types/MergeModels.js
Normal file
50
platform/core/src/types/MergeModels.js
Normal 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;
|
||||||
|
}
|
||||||
|
);
|
27
platform/core/src/types/TypeCapability.js
Normal file
27
platform/core/src/types/TypeCapability.js
Normal 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;
|
||||||
|
}
|
||||||
|
);
|
113
platform/core/src/types/TypeCapabilityProvider.js
Normal file
113
platform/core/src/types/TypeCapabilityProvider.js
Normal 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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
153
platform/core/src/types/TypeImpl.js
Normal file
153
platform/core/src/types/TypeImpl.js
Normal 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;
|
||||||
|
}
|
||||||
|
);
|
117
platform/core/src/types/TypeProperty.js
Normal file
117
platform/core/src/types/TypeProperty.js
Normal 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;
|
||||||
|
}
|
||||||
|
);
|
64
platform/core/src/types/TypePropertyConversion.js
Normal file
64
platform/core/src/types/TypePropertyConversion.js
Normal 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;
|
||||||
|
}
|
||||||
|
);
|
185
platform/core/src/types/TypeProvider.js
Normal file
185
platform/core/src/types/TypeProvider.js
Normal 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;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
);
|
70
platform/core/src/types/TypeWizard.js
Normal file
70
platform/core/src/types/TypeWizard.js
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
25
platform/core/src/views/ViewCapability.js
Normal file
25
platform/core/src/views/ViewCapability.js
Normal 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;
|
||||||
|
}
|
||||||
|
);
|
55
platform/core/src/views/ViewProvider.js
Normal file
55
platform/core/src/views/ViewProvider.js
Normal 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;
|
||||||
|
}
|
||||||
|
);
|
6
platform/core/test/suite.json
Normal file
6
platform/core/test/suite.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[
|
||||||
|
"types/TypeImpl",
|
||||||
|
"types/TypeProperty",
|
||||||
|
"types/TypePropertyConversion",
|
||||||
|
"types/TypeProvider"
|
||||||
|
]
|
13
platform/core/test/types/TypeCapabilityProviderSpec.js
Normal file
13
platform/core/test/types/TypeCapabilityProviderSpec.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/*global define,describe,it,beforeEach,expect*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
['../../src/types/TypeCapabilityProvider'],
|
||||||
|
function () {
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
describe("Type capability provider", function () {
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
72
platform/core/test/types/TypeImplSpec.js
Normal file
72
platform/core/test/types/TypeImplSpec.js
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
45
platform/core/test/types/TypePropertyConversionSpec.js
Normal file
45
platform/core/test/types/TypePropertyConversionSpec.js
Normal 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();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
61
platform/core/test/types/TypePropertySpec.js
Normal file
61
platform/core/test/types/TypePropertySpec.js
Normal 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");
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
214
platform/core/test/types/TypeProviderSpec.js
Normal file
214
platform/core/test/types/TypeProviderSpec.js
Normal 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
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
Loading…
x
Reference in New Issue
Block a user