More hacks

This commit is contained in:
Pete Richards 2016-06-17 14:32:44 -07:00
parent 7c6fa305ad
commit a2bcc828eb
7 changed files with 337 additions and 138 deletions

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

View 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)`

View 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: [
]
}
]
}
});
});

View File

@ -16,17 +16,22 @@ define([
console.log(composition) console.log(composition)
}) })
}); });
*/ */
var Objects = {}, var Objects = {},
ROOT_REGISTRY = [], ROOT_REGISTRY = [],
PROVIDER_REGISTRY = []; PROVIDER_REGISTRY = {},
FALLBACK_PROVIDER;
window.MCT = window.MCT || {}; window.MCT = window.MCT || {};
window.MCT.objects = Objects; 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 = '', var namespace = '',
identifier = key; identifier = key;
for (var i = 0, escaped = false, len=key.length; i < len; i++) { for (var i = 0, escaped = false, len=key.length; i < len; i++) {
@ -42,20 +47,40 @@ define([
}; };
}; };
function makeKey(namespace, identifier) { // take a key and turn it into a key string
if (arguments.length === 1) { // {namespace: 'scratch', identifier: 'root'} ==> 'scratch:root'
identifier = namespace.identifier; function makeKeyString(key) {
namespace = namespace.namespace; if (typeof key === 'string') {
return key;
} }
if (!namespace) { if (!key.namespace) {
return identifier return key.identifier;
} }
return [ return [
namespace.replace(':', '\\:'), key.namespace.replace(':', '\\:'),
identifier.replace(':', '\\:') key.identifier.replace(':', '\\:')
].join(':'); ].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 = { var RootProvider = {
'get': function () { 'get': function () {
return Promise.resolve({ return Promise.resolve({
@ -66,30 +91,26 @@ define([
} }
}; };
// Retrieve the provider for a given key.
function getProvider(key) { function getProvider(key) {
if (key.identifier === 'ROOT') { if (key.identifier === 'ROOT') {
return RootProvider; return RootProvider;
} }
return PROVIDER_REGISTRY.filter(function (p) { return PROVIDER_REGISTRY[key.namespace] || FALLBACK_PROVIDER;
return p.appliesTo(key);
})[0];
}; };
Objects.addProvider = function (provider) { Objects.addProvider = function (namespace, provider) {
PROVIDER_REGISTRY.push(provider); PROVIDER_REGISTRY[namespace] = provider;
}; };
[ [
'create',
'save', 'save',
'delete', 'delete',
'get' 'get'
].forEach(function (method) { ].forEach(function (method) {
Objects[method] = function () { Objects[method] = function () {
var key = arguments[0], var key = arguments[0],
keyParts = parseKey(key), provider = getProvider(key);
provider = getProvider(keyParts),
args = [keyParts].concat([].slice.call(arguments, 1));
if (!provider) { if (!provider) {
throw new Error('No Provider Matched'); throw new Error('No Provider Matched');
@ -99,26 +120,20 @@ define([
throw new Error('Provider does not support [' + method + '].'); throw new Error('Provider does not support [' + method + '].');
} }
return provider[method].apply(provider, args); return provider[method].apply(provider, arguments);
}; };
}); });
Objects.getComposition = function (object) { Objects.addRoot = function (key) {
if (!object.composition) { ROOT_REGISTRY.unshift(key);
throw new Error('object has no composition!');
}
return Promise.all(object.composition.map(Objects.get, Objects));
}; };
Objects.addRoot = function (keyParts) { Objects.removeRoot = function (key) {
var key = makeKey(keyParts);
ROOT_REGISTRY.push(key);
};
Objects.removeRoot = function (keyParts) {
var key = makeKey(keyParts);
ROOT_REGISTRY = ROOT_REGISTRY.filter(function (k) { 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; this.instantiate = instantiate;
} }
ObjectServiceProvider.prototype.appliesTo = function (keyParts) { ObjectServiceProvider.prototype.save = function (object) {
return true; var key = object.key,
}; keyString = makeKeyString(key),
newObject = this.instantiate(toOldFormat(object), keyString);
ObjectServiceProvider.prototype.create = function (keyParts, object) {
var key = makeKey(keyParts),
object = this.instantiate(object, key);
return object.getCapability('persistence') return object.getCapability('persistence')
.persist() .persist()
.then(function () { .then(function () {
return object; return toNewFormat(object, key);
}); });
}; };
ObjectServiceProvider.prototype.save = function (keyParts, object) { ObjectServiceProvider.prototype.delete = function (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) {
// TODO! // TODO!
}; };
ObjectServiceProvider.prototype.get = function (keyParts) { ObjectServiceProvider.prototype.get = function (key) {
var key = makeKey(keyParts); var keyString = makeKeyString(key);
return this.objectService.getObjects([key]) return this.objectService.getObjects([keyString])
.then(function (results) { .then(function (results) {
var model = results[key].getModel(); var model = JSON.parse(JSON.stringify(results[keyString].getModel()));
if (model.composition) { return toNewFormat(model, key);
model.composition = model.composition.map(parseKey);
}
return model;
}); });
}; };
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) { this.getObjects = function (keys) {
var results = {}, var results = {},
promises = keys.map(function (key) { promises = keys.map(function (keyString) {
var key = parseKeyString(keyString);
return Objects.get(key) return Objects.get(key)
.then(function (object) { .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) { ROOTS.forEach(function (r) {
ROOT_REGISTRY.push(r.id); ROOT_REGISTRY.push(parseKeyString(r.id));
}); });
return this; return this;
} }
return ObjectAPI; return ObjectAPIInjector;
}); });

View File

@ -2,23 +2,100 @@
The object API provides methods for fetching domain objects. 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) The following methods should be used before calling run. They allow you to
provider.get(id) configure the persistence space of MCT.
provider.save(domainObject)
provider.delete(domainObject)
* `MCT.objects.addRoot(key)` -- add a "ROOT" to Open MCT by specifying it's
### MCT.objects.save(domainObject) key.
Create, or * `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.
### MCT.objects.delete(id) # Using the object API
Delete an object by specifying id
### MCT.objects.delete(domainObject) The object API provides methods for getting, saving, and deleting objects.
Delete an objects by specifying domain object
* 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.

View File

@ -43,5 +43,4 @@ define([
] ]
} }
}); });
}); });

51
main.js
View File

@ -94,6 +94,7 @@ define([
'./platform/commonUI/regions/bundle', './platform/commonUI/regions/bundle',
'./api/telemetry-api/bundle', './api/telemetry-api/bundle',
'./api/object-api/bundle', './api/object-api/bundle',
'./api/composition-api/bundle',
'./example/scratchpad/bundle', './example/scratchpad/bundle',
'./example/imagery/bundle', './example/imagery/bundle',
@ -102,56 +103,6 @@ define([
], function (Main, legacyRegistry) { ], function (Main, legacyRegistry) {
'use strict'; '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 { return {
legacyRegistry: legacyRegistry, legacyRegistry: legacyRegistry,
run: function () { run: function () {