mirror of
https://github.com/nasa/openmct.git
synced 2024-12-19 05:07:52 +00:00
More hacks
This commit is contained in:
parent
7c6fa305ad
commit
a2bcc828eb
93
api/composition-api/CompositionAPI.js
Normal file
93
api/composition-api/CompositionAPI.js
Normal file
@ -0,0 +1,93 @@
|
||||
define([
|
||||
|
||||
], function (
|
||||
|
||||
) {
|
||||
|
||||
|
||||
var PROVIDER_REGISTRY = [];
|
||||
|
||||
function getProvider (object) {
|
||||
return PROVIDER_REGISTRY.filter(function (p) {
|
||||
return p.appliesTo(object);
|
||||
})[0];
|
||||
};
|
||||
|
||||
function composition(object) {
|
||||
var provider = getProvider(object);
|
||||
|
||||
if (!provider) {
|
||||
return;
|
||||
}
|
||||
|
||||
return new CompositionCollection(object, provider);
|
||||
};
|
||||
|
||||
composition.addProvider = function (provider) {
|
||||
PROVIDER_REGISTRY.unshift(provider);
|
||||
};
|
||||
|
||||
window.MCT = window.MCT || {};
|
||||
window.MCT.composition = composition;
|
||||
|
||||
function CompositionCollection(domainObject, provider) {
|
||||
this.domainObject = domainObject;
|
||||
this.provider = provider;
|
||||
};
|
||||
|
||||
CompositionCollection.prototype.add = function (child, skipMutate) {
|
||||
if (!this._children) {
|
||||
throw new Error("Must load composition before you can add!");
|
||||
}
|
||||
// we probably should not add until we have loaded.
|
||||
// todo: should we modify parent?
|
||||
if (!skipMutate) {
|
||||
this.provider.add(this.domainObject, child);
|
||||
}
|
||||
this.children.push(child);
|
||||
this.emit('add', 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');
|
||||
// Todo: set up listener for changes via provider?
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
CompositionCollection.prototype.remove = function (child) {
|
||||
var index = this.children.indexOf(child);
|
||||
if (index === -1) {
|
||||
throw new Error("Unable to remove child: not found in composition");
|
||||
}
|
||||
this.provider.remove(this.domainObject, child);
|
||||
this.children.splice(index, 1);
|
||||
this.emit('remove', index, child);
|
||||
};
|
||||
|
||||
var DefaultCompositionProvider = {
|
||||
appliesTo: function (domainObject) {
|
||||
return !!domainObject.composition;
|
||||
},
|
||||
load: function (domainObject) {
|
||||
return Promise.all(domainObject.composition.map(MCT.objects.get));
|
||||
},
|
||||
add: function (domainObject, child) {
|
||||
domainObject.composition.push(child.key);
|
||||
}
|
||||
};
|
||||
|
||||
composition.addProvider(DefaultCompositionProvider);
|
||||
|
||||
function Injector() {
|
||||
console.log('composition api injected!');
|
||||
}
|
||||
|
||||
return Injector;
|
||||
|
||||
});
|
35
api/composition-api/README.md
Normal file
35
api/composition-api/README.md
Normal file
@ -0,0 +1,35 @@
|
||||
# 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
|
||||
|
||||
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)`
|
47
api/composition-api/bundle.js
Normal file
47
api/composition-api/bundle.js
Normal file
@ -0,0 +1,47 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
define([
|
||||
'./CompositionAPI',
|
||||
'legacyRegistry'
|
||||
], function (
|
||||
CompositionAPI,
|
||||
legacyRegistry
|
||||
) {
|
||||
legacyRegistry.register('api/composition-api', {
|
||||
name: 'Composition API',
|
||||
description: 'The public Composition API',
|
||||
extensions: {
|
||||
runs: [
|
||||
{
|
||||
key: "CompositionAPI",
|
||||
priority: "mandatory",
|
||||
implementation: CompositionAPI,
|
||||
depends: [
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
});
|
@ -16,17 +16,22 @@ define([
|
||||
console.log(composition)
|
||||
})
|
||||
});
|
||||
|
||||
*/
|
||||
|
||||
var Objects = {},
|
||||
ROOT_REGISTRY = [],
|
||||
PROVIDER_REGISTRY = [];
|
||||
PROVIDER_REGISTRY = {},
|
||||
FALLBACK_PROVIDER;
|
||||
|
||||
window.MCT = window.MCT || {};
|
||||
window.MCT.objects = Objects;
|
||||
|
||||
function parseKey(key) {
|
||||
// take a key string and turn it into a key object
|
||||
// 'scratch:root' ==> {namespace: 'scratch', identifier: 'root'}
|
||||
function parseKeyString(key) {
|
||||
if (typeof key === 'object') {
|
||||
return key;
|
||||
}
|
||||
var namespace = '',
|
||||
identifier = key;
|
||||
for (var i = 0, escaped = false, len=key.length; i < len; i++) {
|
||||
@ -42,20 +47,40 @@ define([
|
||||
};
|
||||
};
|
||||
|
||||
function makeKey(namespace, identifier) {
|
||||
if (arguments.length === 1) {
|
||||
identifier = namespace.identifier;
|
||||
namespace = namespace.namespace;
|
||||
// take a key and turn it into a key string
|
||||
// {namespace: 'scratch', identifier: 'root'} ==> 'scratch:root'
|
||||
function makeKeyString(key) {
|
||||
if (typeof key === 'string') {
|
||||
return key;
|
||||
}
|
||||
if (!namespace) {
|
||||
return identifier
|
||||
if (!key.namespace) {
|
||||
return key.identifier;
|
||||
}
|
||||
return [
|
||||
namespace.replace(':', '\\:'),
|
||||
identifier.replace(':', '\\:')
|
||||
key.namespace.replace(':', '\\:'),
|
||||
key.identifier.replace(':', '\\:')
|
||||
].join(':');
|
||||
};
|
||||
|
||||
// Converts composition to use key strings instead of keys
|
||||
function toOldFormat(model) {
|
||||
delete model.key;
|
||||
if (model.composition) {
|
||||
model.composition = model.composition.map(makeKeyString);
|
||||
}
|
||||
return model;
|
||||
};
|
||||
|
||||
// converts composition to use keys instead of key strings
|
||||
function toNewFormat(model, key) {
|
||||
model.key = key;
|
||||
if (model.composition) {
|
||||
model.composition = model.composition.map(parseKeyString);
|
||||
}
|
||||
return model;
|
||||
};
|
||||
|
||||
// Root provider is hardcoded in; can't be skipped.
|
||||
var RootProvider = {
|
||||
'get': function () {
|
||||
return Promise.resolve({
|
||||
@ -66,30 +91,26 @@ define([
|
||||
}
|
||||
};
|
||||
|
||||
// Retrieve the provider for a given key.
|
||||
function getProvider(key) {
|
||||
if (key.identifier === 'ROOT') {
|
||||
return RootProvider;
|
||||
}
|
||||
return PROVIDER_REGISTRY.filter(function (p) {
|
||||
return p.appliesTo(key);
|
||||
})[0];
|
||||
return PROVIDER_REGISTRY[key.namespace] || FALLBACK_PROVIDER;
|
||||
};
|
||||
|
||||
Objects.addProvider = function (provider) {
|
||||
PROVIDER_REGISTRY.push(provider);
|
||||
Objects.addProvider = function (namespace, provider) {
|
||||
PROVIDER_REGISTRY[namespace] = provider;
|
||||
};
|
||||
|
||||
[
|
||||
'create',
|
||||
'save',
|
||||
'delete',
|
||||
'get'
|
||||
].forEach(function (method) {
|
||||
Objects[method] = function () {
|
||||
var key = arguments[0],
|
||||
keyParts = parseKey(key),
|
||||
provider = getProvider(keyParts),
|
||||
args = [keyParts].concat([].slice.call(arguments, 1));
|
||||
provider = getProvider(key);
|
||||
|
||||
if (!provider) {
|
||||
throw new Error('No Provider Matched');
|
||||
@ -99,26 +120,20 @@ define([
|
||||
throw new Error('Provider does not support [' + method + '].');
|
||||
}
|
||||
|
||||
return provider[method].apply(provider, args);
|
||||
return provider[method].apply(provider, arguments);
|
||||
};
|
||||
});
|
||||
|
||||
Objects.getComposition = function (object) {
|
||||
if (!object.composition) {
|
||||
throw new Error('object has no composition!');
|
||||
}
|
||||
return Promise.all(object.composition.map(Objects.get, Objects));
|
||||
Objects.addRoot = function (key) {
|
||||
ROOT_REGISTRY.unshift(key);
|
||||
};
|
||||
|
||||
Objects.addRoot = function (keyParts) {
|
||||
var key = makeKey(keyParts);
|
||||
ROOT_REGISTRY.push(key);
|
||||
};
|
||||
|
||||
Objects.removeRoot = function (keyParts) {
|
||||
var key = makeKey(keyParts);
|
||||
Objects.removeRoot = function (key) {
|
||||
ROOT_REGISTRY = ROOT_REGISTRY.filter(function (k) {
|
||||
return k !== key;
|
||||
return (
|
||||
k.identifier !== key.identifier ||
|
||||
k.namespace !== key.namespace
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@ -127,60 +142,42 @@ define([
|
||||
this.instantiate = instantiate;
|
||||
}
|
||||
|
||||
ObjectServiceProvider.prototype.appliesTo = function (keyParts) {
|
||||
return true;
|
||||
};
|
||||
|
||||
ObjectServiceProvider.prototype.create = function (keyParts, object) {
|
||||
var key = makeKey(keyParts),
|
||||
object = this.instantiate(object, key);
|
||||
ObjectServiceProvider.prototype.save = function (object) {
|
||||
var key = object.key,
|
||||
keyString = makeKeyString(key),
|
||||
newObject = this.instantiate(toOldFormat(object), keyString);
|
||||
|
||||
return object.getCapability('persistence')
|
||||
.persist()
|
||||
.then(function () {
|
||||
return object;
|
||||
return toNewFormat(object, key);
|
||||
});
|
||||
};
|
||||
|
||||
ObjectServiceProvider.prototype.save = function (keyParts, object) {
|
||||
var key = makeKey(keyParts);
|
||||
return this.objectService.getObjects([key])
|
||||
.then(function (results) {
|
||||
var obj = results[key];
|
||||
obj.getCapability('mutation').mutate(function (model) {
|
||||
_.extend(model, object);
|
||||
});
|
||||
return obj.getCapability('persistence')
|
||||
.persist()
|
||||
.then(function () {
|
||||
return object;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
ObjectServiceProvider.prototype.delete = function (keyParts, object) {
|
||||
ObjectServiceProvider.prototype.delete = function (object) {
|
||||
// TODO!
|
||||
};
|
||||
|
||||
ObjectServiceProvider.prototype.get = function (keyParts) {
|
||||
var key = makeKey(keyParts);
|
||||
return this.objectService.getObjects([key])
|
||||
ObjectServiceProvider.prototype.get = function (key) {
|
||||
var keyString = makeKeyString(key);
|
||||
return this.objectService.getObjects([keyString])
|
||||
.then(function (results) {
|
||||
var model = results[key].getModel();
|
||||
if (model.composition) {
|
||||
model.composition = model.composition.map(parseKey);
|
||||
}
|
||||
return model;
|
||||
var model = JSON.parse(JSON.stringify(results[keyString].getModel()));
|
||||
return toNewFormat(model, key);
|
||||
});
|
||||
};
|
||||
|
||||
function ObjectAPI(ROOTS, instantiate, objectService) {
|
||||
// Injects new object API as a decorator so that it hijacks all requests.
|
||||
// Object providers implemented on new API should just work, old API should just work, many things may break.
|
||||
function ObjectAPIInjector(ROOTS, instantiate, objectService) {
|
||||
this.getObjects = function (keys) {
|
||||
var results = {},
|
||||
promises = keys.map(function (key) {
|
||||
promises = keys.map(function (keyString) {
|
||||
var key = parseKeyString(keyString);
|
||||
return Objects.get(key)
|
||||
.then(function (object) {
|
||||
results[key] = instantiate(object, key);
|
||||
object = toOldFormat(object)
|
||||
results[keyString] = instantiate(object, keyString);
|
||||
});
|
||||
});
|
||||
|
||||
@ -190,14 +187,14 @@ define([
|
||||
});
|
||||
};
|
||||
|
||||
PROVIDER_REGISTRY.push(new ObjectServiceProvider(objectService, instantiate));
|
||||
FALLBACK_PROVIDER = new ObjectServiceProvider(objectService, instantiate);
|
||||
|
||||
ROOTS.forEach(function (r) {
|
||||
ROOT_REGISTRY.push(r.id);
|
||||
ROOT_REGISTRY.push(parseKeyString(r.id));
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
return ObjectAPI;
|
||||
return ObjectAPIInjector;
|
||||
});
|
||||
|
@ -2,23 +2,100 @@
|
||||
|
||||
The object API provides methods for fetching domain objects.
|
||||
|
||||
# Keys
|
||||
Keys are a composite identifier that is used to create and persist objects. Ex:
|
||||
```javascript
|
||||
{
|
||||
namespace: 'elastic',
|
||||
identifier: 'myIdentifier'
|
||||
}
|
||||
```
|
||||
|
||||
### MCT.objects.get(namespace, identifier)
|
||||
In old MCT days, we called this an "id", and we encoded it in a single string.
|
||||
The above key would encode into the identifier, `elastic:myIdentifier`.
|
||||
|
||||
namespace, name, id (unique combination of namespace + name)
|
||||
When interacting with the API you will be dealing with key objects.
|
||||
|
||||
### MCT.objects.Provider
|
||||
# Configuring the Object API
|
||||
|
||||
provider.matchNamespace(namespace)
|
||||
provider.get(id)
|
||||
provider.save(domainObject)
|
||||
provider.delete(domainObject)
|
||||
The following methods should be used before calling run. They allow you to
|
||||
configure the persistence space of MCT.
|
||||
|
||||
* `MCT.objects.addRoot(key)` -- add a "ROOT" to Open MCT by specifying it's
|
||||
key.
|
||||
* `MCT.objects.removeRoot(key)` -- Remove a "ROOT" from Open MCT by key.
|
||||
* `MCT.objects.addProvider(namespace, provider)` -- register an object provider
|
||||
for a specific namespace. See below for documentation on the provider
|
||||
interface.
|
||||
|
||||
# Using the object API
|
||||
|
||||
The object API provides methods for getting, saving, and deleting objects.
|
||||
|
||||
* MCT.objects.get(key) -> returns promise for an object
|
||||
* MCT.objects.save(object) -> returns promise that is resolved when object
|
||||
has been saved
|
||||
* MCT.objects.delete(object) -> returns promise that is resolved when object has
|
||||
been deleted
|
||||
|
||||
## Configuration Example: Adding a groot
|
||||
|
||||
The following example adds a new root object for groot and populates it with
|
||||
some pieces of groot.
|
||||
|
||||
```javascript
|
||||
|
||||
var ROOT_KEY = {
|
||||
namespace: 'groot',
|
||||
identifier: 'groot'
|
||||
};
|
||||
|
||||
var GROOT_ROOT = {
|
||||
name: 'I am groot',
|
||||
type: 'folder',
|
||||
composition: [
|
||||
{
|
||||
namespace: 'groot',
|
||||
identifier: 'arms'
|
||||
},
|
||||
{
|
||||
namespace: 'groot',
|
||||
identifier: 'legs'
|
||||
},
|
||||
{
|
||||
namespace: 'groot',
|
||||
identifier: 'torso'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var GrootProvider = {
|
||||
get: function (key) {
|
||||
if (key.identifier === 'groot') {
|
||||
return Promise.resolve(GROOT_ROOT);
|
||||
}
|
||||
return Promise.resolve({
|
||||
name: 'Groot\'s ' + key.identifier
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
MCT.objects.addRoot(ROOT_KEY);
|
||||
|
||||
MCT.objects.addProvider('groot', GrootProvider);
|
||||
|
||||
MCT.run();
|
||||
```
|
||||
|
||||
### Making a custom provider:
|
||||
|
||||
All methods on the provider interface are optional, so you do not need
|
||||
to modify them.
|
||||
|
||||
* `provider.get(key)` -> promise for a domain object.
|
||||
* `provider.save(domainObject)` -> returns promise that is fulfilled when object
|
||||
has been saved.
|
||||
* `provider.delete(domainObject)` -> returns promise that is fulfilled when
|
||||
object has been deleted.
|
||||
|
||||
|
||||
### MCT.objects.save(domainObject)
|
||||
Create, or
|
||||
|
||||
### MCT.objects.delete(id)
|
||||
Delete an object by specifying id
|
||||
### MCT.objects.delete(domainObject)
|
||||
Delete an objects by specifying domain object
|
@ -43,5 +43,4 @@ define([
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
51
main.js
51
main.js
@ -94,6 +94,7 @@ define([
|
||||
'./platform/commonUI/regions/bundle',
|
||||
'./api/telemetry-api/bundle',
|
||||
'./api/object-api/bundle',
|
||||
'./api/composition-api/bundle',
|
||||
'./example/scratchpad/bundle',
|
||||
|
||||
'./example/imagery/bundle',
|
||||
@ -102,56 +103,6 @@ define([
|
||||
], function (Main, legacyRegistry) {
|
||||
'use strict';
|
||||
|
||||
MCT.objects.addRoot({
|
||||
namespace: 'magic',
|
||||
identifier: 'groot'
|
||||
});
|
||||
|
||||
var GROOT_BITS = [
|
||||
{
|
||||
key: {
|
||||
namespace: 'magic',
|
||||
identifier: 'groot-one'
|
||||
},
|
||||
name: 'groot!'
|
||||
},
|
||||
{
|
||||
key: {
|
||||
namespace: 'magic',
|
||||
identifier: 'groot-two'
|
||||
},
|
||||
name: 'groot?'
|
||||
},
|
||||
{
|
||||
key: {
|
||||
namespace: 'magic',
|
||||
identifier: 'groot-three'
|
||||
},
|
||||
name: 'groot groot groot!'
|
||||
}
|
||||
];
|
||||
|
||||
MCT.objects.addProvider({
|
||||
appliesTo: function (key) {
|
||||
return key.namespace === 'magic';
|
||||
},
|
||||
get: function (key) {
|
||||
if (key.identifier === 'groot') {
|
||||
return Promise.resolve({
|
||||
'name': 'grootman!',
|
||||
'type': 'folder',
|
||||
'composition': GROOT_BITS.map(function (gb) {
|
||||
return gb.key
|
||||
})
|
||||
});
|
||||
}
|
||||
return GROOT_BITS.filter(function (gb) {
|
||||
return (gb.key.identifier === key.identifier);
|
||||
})[0];
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return {
|
||||
legacyRegistry: legacyRegistry,
|
||||
run: function () {
|
||||
|
Loading…
Reference in New Issue
Block a user