mirror of
https://github.com/nasa/openmct.git
synced 2024-12-19 21:27:52 +00:00
[API] Draft Composition API (#1068)
* [Objects] util for equality checking Add a method for checking object equality, useful for other services. * [Composition] Draft Composition API Draft composition API. Composition collections provide an observable for watching and mutating the composition of an object. Composition providers implement the loading and modification of composition. The default composition provider uses the composition attribute of domain objects, while allowing other providers to implement their own loading and mutation behavior. * add todo about event listener bindings * [Type] Add form property for defining form fields * [tutorial] Add Composition tutorial * provider doesn't have to implement events, load returns array of children * use new composition in old api * correct key name * Override instantiate to provide model ids Override instantiate in public API adapter to prevent making changes to platform code. Instantiate now passes the id of the domain object with the model so that capabilities can convert to a new-style domain object and use that to detect functionality. * Implement mutation capability with decorator Implementation mutation capability override with decorator to adapter code outside of platform. Capability override ensures that models are kept in sync even though they are no longer shared objects. * override composition cleanly Override composition capability without making changes inside platform. * cleanup after temporary collections * remove unused try/catch
This commit is contained in:
parent
7890fcae69
commit
d5aa998b4c
1
API.md
1
API.md
@ -33,6 +33,7 @@ Returns a `typeInstance`. `options` is an object supporting the following prope
|
||||
* `description`: `string`, a human readible description of the object and what it is for.
|
||||
* `initialize`: `function` which initializes new instances of this type. it is called with an object, should add any default properties to that object.
|
||||
* `creatable`: `boolean`, if true, this object will be visible in the create menu.
|
||||
* `form`: `Array` an array of form fields, as defined... somewhere! Generates a property sheet that is visible while editing this object.
|
||||
|
||||
### `MCT.type(typeKey, typeInstance)`
|
||||
Status: First Draft
|
||||
|
65
composition-test.html
Normal file
65
composition-test.html
Normal file
@ -0,0 +1,65 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||
<title>Implementing a composition provider</title>
|
||||
<script src="dist/main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
|
||||
var widgetParts = ['foo', 'bar', 'baz', 'bing', 'frobnak']
|
||||
|
||||
function fabricateName() {
|
||||
return [
|
||||
widgetParts[Math.floor(Math.random() * widgetParts.length)],
|
||||
widgetParts[Math.floor(Math.random() * widgetParts.length)],
|
||||
Math.floor(Math.random() * 1000)
|
||||
].join('_');
|
||||
}
|
||||
|
||||
MCT.type('example.widget-factory', new MCT.Type({
|
||||
metadata: {
|
||||
label: "Widget Factory",
|
||||
glyph: "s",
|
||||
description: "A factory for making widgets"
|
||||
},
|
||||
initialize: function (object) {
|
||||
object.widgetCount = 5;
|
||||
object.composition = [];
|
||||
},
|
||||
creatable: true,
|
||||
form: [
|
||||
{
|
||||
name: "Widget Count",
|
||||
control: "textfield",
|
||||
key: "widgetCount",
|
||||
property: "widgetCount",
|
||||
required: true
|
||||
}
|
||||
]
|
||||
}));
|
||||
|
||||
MCT.Composition.addProvider({
|
||||
appliesTo: function (domainObject) {
|
||||
return domainObject.type === 'example.widget-factory';
|
||||
},
|
||||
load: function (domainObject) {
|
||||
var widgets = [];
|
||||
while (widgets.length < domainObject.widgetCount) {
|
||||
widgets.push({
|
||||
name: fabricateName(),
|
||||
key: {
|
||||
namespace: 'widget-factory',
|
||||
identifier: '' + widgets.length
|
||||
}
|
||||
});
|
||||
}
|
||||
return Promise.resolve(widgets);
|
||||
}
|
||||
});
|
||||
|
||||
MCT.run();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -167,10 +167,7 @@ define(
|
||||
* @memberof platform/core.MutationCapability#
|
||||
*/
|
||||
MutationCapability.prototype.listen = function (listener) {
|
||||
return this.specificMutationTopic.listen(function (newModel) {
|
||||
this.domainObject.model = JSON.parse(JSON.stringify(newModel));
|
||||
listener(newModel);
|
||||
}.bind(this));
|
||||
return this.specificMutationTopic.listen(listener);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1,7 +1,14 @@
|
||||
define([
|
||||
'legacyRegistry',
|
||||
'./directives/MCTView'
|
||||
], function (legacyRegistry, MCTView) {
|
||||
'./directives/MCTView',
|
||||
'./services/Instantiate',
|
||||
'./capabilities/APICapabilityDecorator'
|
||||
], function (
|
||||
legacyRegistry,
|
||||
MCTView,
|
||||
Instantiate,
|
||||
APICapabilityDecorator
|
||||
) {
|
||||
legacyRegistry.register('src/adapter', {
|
||||
"extensions": {
|
||||
"directives": [
|
||||
@ -13,6 +20,28 @@ define([
|
||||
"PublicAPI"
|
||||
]
|
||||
}
|
||||
],
|
||||
services: [
|
||||
{
|
||||
key: "instantiate",
|
||||
priority: "mandatory",
|
||||
implementation: Instantiate,
|
||||
depends: [
|
||||
"capabilityService",
|
||||
"identifierService",
|
||||
"cacheService"
|
||||
]
|
||||
}
|
||||
],
|
||||
components: [
|
||||
{
|
||||
type: "decorator",
|
||||
provides: "capabilityService",
|
||||
implementation: APICapabilityDecorator,
|
||||
depends: [
|
||||
"$injector"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
37
src/adapter/capabilities/APICapabilityDecorator.js
Normal file
37
src/adapter/capabilities/APICapabilityDecorator.js
Normal file
@ -0,0 +1,37 @@
|
||||
define([
|
||||
'./synchronizeMutationCapability',
|
||||
'./AlternateCompositionCapability'
|
||||
], function (
|
||||
synchronizeMutationCapability,
|
||||
AlternateCompositionCapability
|
||||
) {
|
||||
|
||||
/**
|
||||
* Overrides certain capabilities to keep consistency between old API
|
||||
* and new API.
|
||||
*/
|
||||
function APICapabilityDecorator($injector, capabilityService) {
|
||||
this.$injector = $injector;
|
||||
this.capabilityService = capabilityService;
|
||||
}
|
||||
|
||||
APICapabilityDecorator.prototype.getCapabilities = function (
|
||||
model
|
||||
) {
|
||||
var capabilities = this.capabilityService.getCapabilities(model);
|
||||
if (capabilities.mutation) {
|
||||
capabilities.mutation =
|
||||
synchronizeMutationCapability(capabilities.mutation);
|
||||
}
|
||||
if (AlternateCompositionCapability.appliesTo(model)) {
|
||||
capabilities.composition = function (domainObject) {
|
||||
return new AlternateCompositionCapability(this.$injector, domainObject)
|
||||
}.bind(this);
|
||||
}
|
||||
|
||||
return capabilities;
|
||||
};
|
||||
|
||||
return APICapabilityDecorator;
|
||||
|
||||
});
|
102
src/adapter/capabilities/AlternateCompositionCapability.js
Normal file
102
src/adapter/capabilities/AlternateCompositionCapability.js
Normal file
@ -0,0 +1,102 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Module defining AlternateCompositionCapability. Created by vwoeltje on 11/7/14.
|
||||
*/
|
||||
define([
|
||||
'../../api/objects/object-utils',
|
||||
'../../api/composition/CompositionAPI'
|
||||
], function (objectUtils, CompositionAPI) {
|
||||
|
||||
function AlternateCompositionCapability($injector, domainObject) {
|
||||
this.domainObject = domainObject;
|
||||
|
||||
this.getDependencies = function () {
|
||||
this.instantiate = $injector.get("instantiate");
|
||||
this.contextualize = $injector.get("contextualize");
|
||||
this.getDependencies = undefined;
|
||||
}.bind(this);
|
||||
}
|
||||
|
||||
AlternateCompositionCapability.prototype.add = function (child, index) {
|
||||
if (typeof index !== 'undefined') {
|
||||
// At first glance I don't see a location in the existing
|
||||
// codebase where add is called with an index. Won't support.
|
||||
throw new Error(
|
||||
'Composition Capability does not support adding at index'
|
||||
);
|
||||
}
|
||||
|
||||
function addChildToComposition(model) {
|
||||
var existingIndex = model.composition.indexOf(child.getId());
|
||||
if (existingIndex === -1) {
|
||||
model.composition.push(child.getId())
|
||||
}
|
||||
}
|
||||
|
||||
return this.domainObject.useCapability(
|
||||
'mutation',
|
||||
addChildToComposition
|
||||
)
|
||||
.then(this.invoke.bind(this))
|
||||
.then(function (children) {
|
||||
return children.filter(function (c) {
|
||||
return c.getId() === child.getId();
|
||||
})[0];
|
||||
});
|
||||
};
|
||||
|
||||
AlternateCompositionCapability.prototype.contextualizeChild = function (
|
||||
child
|
||||
) {
|
||||
if (this.getDependencies) {
|
||||
this.getDependencies();
|
||||
}
|
||||
|
||||
var keyString = objectUtils.makeKeyString(child.key);
|
||||
var oldModel = objectUtils.toOldFormat(child);
|
||||
var newDO = this.instantiate(oldModel, keyString);
|
||||
return this.contextualize(newDO, this.domainObject);
|
||||
|
||||
};
|
||||
|
||||
AlternateCompositionCapability.prototype.invoke = function () {
|
||||
var newFormatDO = objectUtils.toNewFormat(
|
||||
this.domainObject.getModel(),
|
||||
this.domainObject.getId()
|
||||
);
|
||||
var collection = CompositionAPI(newFormatDO);
|
||||
return collection.load()
|
||||
.then(function (children) {
|
||||
collection.destroy();
|
||||
return children.map(this.contextualizeChild, this);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
AlternateCompositionCapability.appliesTo = function (model) {
|
||||
return !!CompositionAPI(objectUtils.toNewFormat(model, model.id));
|
||||
};
|
||||
|
||||
return AlternateCompositionCapability;
|
||||
}
|
||||
);
|
27
src/adapter/capabilities/synchronizeMutationCapability.js
Normal file
27
src/adapter/capabilities/synchronizeMutationCapability.js
Normal file
@ -0,0 +1,27 @@
|
||||
define([
|
||||
|
||||
], function (
|
||||
|
||||
) {
|
||||
|
||||
/**
|
||||
* Wraps the mutation capability and synchronizes the mutation
|
||||
*/
|
||||
function synchronizeMutationCapability(mutationConstructor) {
|
||||
|
||||
return function makeCapability(domainObject) {
|
||||
var capability = mutationConstructor(domainObject);
|
||||
var oldListen = capability.listen.bind(capability);
|
||||
capability.listen = function (listener) {
|
||||
return oldListen(function (newModel) {
|
||||
capability.domainObject.model =
|
||||
JSON.parse(JSON.stringify(newModel));
|
||||
listener(newModel);
|
||||
});
|
||||
};
|
||||
return capability;
|
||||
}
|
||||
};
|
||||
|
||||
return synchronizeMutationCapability;
|
||||
});
|
49
src/adapter/services/Instantiate.js
Normal file
49
src/adapter/services/Instantiate.js
Normal file
@ -0,0 +1,49 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
['../../../platform/core/src/objects/DomainObjectImpl'],
|
||||
function (DomainObjectImpl) {
|
||||
|
||||
/**
|
||||
* Overrides platform version of instantiate, passes Id with model such
|
||||
* that capability detection can utilize new format domain objects.
|
||||
*/
|
||||
function Instantiate(
|
||||
capabilityService,
|
||||
identifierService,
|
||||
cacheService
|
||||
) {
|
||||
return function (model, id) {
|
||||
id = id || identifierService.generate();
|
||||
var old_id = model.id;
|
||||
model.id = id;
|
||||
var capabilities = capabilityService.getCapabilities(model);
|
||||
model.id = old_id;
|
||||
cacheService.put(id, model);
|
||||
return new DomainObjectImpl(id, model, capabilities);
|
||||
};
|
||||
}
|
||||
|
||||
return Instantiate;
|
||||
}
|
||||
);
|
@ -37,6 +37,7 @@ define(function () {
|
||||
def.name = this.definition.metadata.label;
|
||||
def.glyph = this.definition.metadata.glyph;
|
||||
def.description = this.definition.metadata.description;
|
||||
def.properties = this.definition.form;
|
||||
|
||||
if (this.definition.initialize) {
|
||||
def.model = {};
|
||||
|
@ -2,17 +2,20 @@ define([
|
||||
'./Type',
|
||||
'./TimeConductor',
|
||||
'./View',
|
||||
'./objects/ObjectAPI'
|
||||
'./objects/ObjectAPI',
|
||||
'./composition/CompositionAPI'
|
||||
], function (
|
||||
Type,
|
||||
TimeConductor,
|
||||
View,
|
||||
ObjectAPI
|
||||
ObjectAPI,
|
||||
CompositionAPI
|
||||
) {
|
||||
return {
|
||||
Type: Type,
|
||||
TimeConductor: new TimeConductor(),
|
||||
View: View,
|
||||
Objects: ObjectAPI
|
||||
Objects: ObjectAPI,
|
||||
Composition: CompositionAPI
|
||||
};
|
||||
});
|
||||
|
39
src/api/composition/CompositionAPI.js
Normal file
39
src/api/composition/CompositionAPI.js
Normal file
@ -0,0 +1,39 @@
|
||||
define([
|
||||
'lodash',
|
||||
'EventEmitter',
|
||||
'./DefaultCompositionProvider',
|
||||
'./CompositionCollection'
|
||||
], function (
|
||||
_,
|
||||
EventEmitter,
|
||||
DefaultCompositionProvider,
|
||||
CompositionCollection
|
||||
) {
|
||||
|
||||
var PROVIDER_REGISTRY = [];
|
||||
|
||||
function getProvider (object) {
|
||||
return _.find(PROVIDER_REGISTRY, function (p) {
|
||||
return p.appliesTo(object);
|
||||
});
|
||||
};
|
||||
|
||||
function composition(object) {
|
||||
var provider = getProvider(object);
|
||||
|
||||
if (!provider) {
|
||||
return;
|
||||
}
|
||||
|
||||
return new CompositionCollection(object, provider);
|
||||
};
|
||||
|
||||
composition.addProvider = function (provider) {
|
||||
PROVIDER_REGISTRY.unshift(provider);
|
||||
};
|
||||
|
||||
composition.addProvider(new DefaultCompositionProvider());
|
||||
|
||||
return composition;
|
||||
|
||||
});
|
115
src/api/composition/CompositionCollection.js
Normal file
115
src/api/composition/CompositionCollection.js
Normal file
@ -0,0 +1,115 @@
|
||||
define([
|
||||
'EventEmitter',
|
||||
'lodash',
|
||||
'../objects/object-utils'
|
||||
], function (
|
||||
EventEmitter,
|
||||
_,
|
||||
objectUtils
|
||||
) {
|
||||
|
||||
function CompositionCollection(domainObject, provider) {
|
||||
EventEmitter.call(this);
|
||||
this.domainObject = domainObject;
|
||||
this.provider = provider;
|
||||
if (this.provider.on) {
|
||||
this.provider.on(
|
||||
this.domainObject,
|
||||
'add',
|
||||
this.onProviderAdd,
|
||||
this
|
||||
);
|
||||
this.provider.on(
|
||||
this.domainObject,
|
||||
'remove',
|
||||
this.onProviderRemove,
|
||||
this
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
CompositionCollection.prototype = Object.create(EventEmitter.prototype);
|
||||
|
||||
CompositionCollection.prototype.onProviderAdd = function (child) {
|
||||
this.add(child, true);
|
||||
};
|
||||
|
||||
CompositionCollection.prototype.onProviderRemove = function (child) {
|
||||
this.remove(child, true);
|
||||
};
|
||||
|
||||
CompositionCollection.prototype.indexOf = function (child) {
|
||||
return _.findIndex(this._children, function (other) {
|
||||
return objectUtils.equals(child, other);
|
||||
});
|
||||
};
|
||||
|
||||
CompositionCollection.prototype.contains = function (child) {
|
||||
return this.indexOf(child) !== -1;
|
||||
};
|
||||
|
||||
CompositionCollection.prototype.add = function (child, skipMutate) {
|
||||
if (!this._children) {
|
||||
throw new Error("Must load composition before you can add!");
|
||||
}
|
||||
if (this.contains(child)) {
|
||||
if (skipMutate) {
|
||||
return; // don't add twice, don't error.
|
||||
}
|
||||
throw new Error("Unable to add child: already in composition");
|
||||
}
|
||||
this._children.push(child);
|
||||
this.emit('add', child);
|
||||
if (!skipMutate) {
|
||||
// add after we have added.
|
||||
this.provider.add(this.domainObject, child);
|
||||
}
|
||||
};
|
||||
|
||||
CompositionCollection.prototype.load = function () {
|
||||
return this.provider.load(this.domainObject)
|
||||
.then(function (children) {
|
||||
this._children = [];
|
||||
children.map(function (c) {
|
||||
this.add(c, true);
|
||||
}, this);
|
||||
this.emit('load');
|
||||
return this._children.slice();
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
CompositionCollection.prototype.remove = function (child, skipMutate) {
|
||||
if (!this.contains(child)) {
|
||||
if (skipMutate) {
|
||||
return;
|
||||
}
|
||||
throw new Error("Unable to remove child: not found in composition");
|
||||
}
|
||||
var index = this.indexOf(child);
|
||||
var removed = this._children.splice(index, 1)[0];
|
||||
this.emit('remove', index, child);
|
||||
if (!skipMutate) {
|
||||
// trigger removal after we have internally removed it.
|
||||
this.provider.remove(this.domainObject, removed);
|
||||
}
|
||||
};
|
||||
|
||||
CompositionCollection.prototype.destroy = function () {
|
||||
if (this.provider.off) {
|
||||
this.provider.off(
|
||||
this.domainObject,
|
||||
'add',
|
||||
this.onProviderAdd,
|
||||
this
|
||||
);
|
||||
this.provider.off(
|
||||
this.domainObject,
|
||||
'remove',
|
||||
this.onProviderRemove,
|
||||
this
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return CompositionCollection
|
||||
});
|
76
src/api/composition/DefaultCompositionProvider.js
Normal file
76
src/api/composition/DefaultCompositionProvider.js
Normal file
@ -0,0 +1,76 @@
|
||||
define([
|
||||
'lodash',
|
||||
'EventEmitter',
|
||||
'../objects/ObjectAPI',
|
||||
'../objects/object-utils'
|
||||
], function (
|
||||
_,
|
||||
EventEmitter,
|
||||
ObjectAPI,
|
||||
objectUtils
|
||||
) {
|
||||
|
||||
function makeEventName(domainObject, event) {
|
||||
return event + ':' + objectUtils.makeKeyString(domainObject.key);
|
||||
}
|
||||
|
||||
function DefaultCompositionProvider() {
|
||||
EventEmitter.call(this);
|
||||
}
|
||||
|
||||
DefaultCompositionProvider.prototype =
|
||||
Object.create(EventEmitter.prototype);
|
||||
|
||||
DefaultCompositionProvider.prototype.appliesTo = function (domainObject) {
|
||||
return !!domainObject.composition;
|
||||
};
|
||||
|
||||
DefaultCompositionProvider.prototype.load = function (domainObject) {
|
||||
return Promise.all(domainObject.composition.map(ObjectAPI.get));
|
||||
};
|
||||
|
||||
DefaultCompositionProvider.prototype.on = function (
|
||||
domainObject,
|
||||
event,
|
||||
listener,
|
||||
context
|
||||
) {
|
||||
// these can likely be passed through to the mutation service instead
|
||||
// of using an eventemitter.
|
||||
this.addListener(
|
||||
makeEventName(domainObject, event),
|
||||
listener,
|
||||
context
|
||||
);
|
||||
};
|
||||
|
||||
DefaultCompositionProvider.prototype.off = function (
|
||||
domainObject,
|
||||
event,
|
||||
listener,
|
||||
context
|
||||
) {
|
||||
// these can likely be passed through to the mutation service instead
|
||||
// of using an eventemitter.
|
||||
this.removeListener(
|
||||
makeEventName(domainObject, event),
|
||||
listener,
|
||||
context
|
||||
);
|
||||
};
|
||||
|
||||
DefaultCompositionProvider.prototype.remove = function (domainObject, child) {
|
||||
// TODO: this needs to be synchronized via mutation
|
||||
var index = domainObject.composition.indexOf(child);
|
||||
domainObject.composition.splice(index, 1);
|
||||
this.emit(makeEventName(domainObject, 'remove'), child);
|
||||
};
|
||||
|
||||
DefaultCompositionProvider.prototype.add = function (domainObject, child) {
|
||||
// TODO: this needs to be synchronized via mutation
|
||||
domainObject.composition.push(child.key);
|
||||
this.emit(makeEventName(domainObject, 'add'), child);
|
||||
};
|
||||
|
||||
return DefaultCompositionProvider;
|
||||
});
|
37
src/api/composition/README.md
Normal file
37
src/api/composition/README.md
Normal file
@ -0,0 +1,37 @@
|
||||
# Composition API - Overview
|
||||
|
||||
The composition API is straightforward:
|
||||
|
||||
MCT.composition(object) -- returns a `CompositionCollection` if the object has
|
||||
composition, returns undefined if it doesn't.
|
||||
|
||||
## CompositionCollection
|
||||
|
||||
Has three events:
|
||||
* `load`: when the collection has completed loading.
|
||||
* `add`: when a new object has been added to the collection.
|
||||
* `remove` when an object has been removed from the collection.
|
||||
|
||||
Has three methods:
|
||||
|
||||
`Collection.load()` -- returns a promise that is fulfilled when the composition
|
||||
has loaded.
|
||||
`Collection.add(object)` -- add a domain object to the composition.
|
||||
`Collection.remove(object)` -- remove the object from the composition.
|
||||
|
||||
## Composition providers
|
||||
composition providers are anything that meets the following interface:
|
||||
|
||||
* `provider.appliesTo(domainObject)` -> return true if this provider can provide
|
||||
composition for a given domain object.
|
||||
* `provider.add(domainObject, childObject)` -> adds object
|
||||
* `provider.remove(domainObject, childObject)` -> immediately removes objects
|
||||
* `provider.load(domainObject)` -> returns promise for array of children
|
||||
|
||||
TODO: need to figure out how to make the provider event listeners invisible to the provider.
|
||||
|
||||
There is a default composition provider which handles loading composition for
|
||||
any object with a `composition` property. If you want specialized composition
|
||||
loading behavior, implement your own composition provider and register it with
|
||||
|
||||
`MCT.composition.addProvider(myProvider)`
|
@ -74,10 +74,15 @@ define([
|
||||
return model;
|
||||
};
|
||||
|
||||
var equals = function (a, b) {
|
||||
return makeKeyString(a.key) === makeKeyString(b.key);
|
||||
};
|
||||
|
||||
return {
|
||||
toOldFormat: toOldFormat,
|
||||
toNewFormat: toNewFormat,
|
||||
makeKeyString: makeKeyString,
|
||||
parseKeyString: parseKeyString
|
||||
parseKeyString: parseKeyString,
|
||||
equals: equals
|
||||
};
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user