Merge pull request #1030 from nasa/api-tutorial/objects

Api tutorial/objects
This commit is contained in:
Victor Woeltjen 2016-07-01 10:03:42 -07:00 committed by GitHub
commit d51e6bfd92
11 changed files with 443 additions and 9 deletions

View File

@ -19,6 +19,7 @@
"comma-separated-values": "^3.6.4", "comma-separated-values": "^3.6.4",
"FileSaver.js": "^0.0.2", "FileSaver.js": "^0.0.2",
"zepto": "^1.1.6", "zepto": "^1.1.6",
"eventemitter3": "^1.2.0" "eventemitter3": "^1.2.0",
"lodash": "3.10.1"
} }
} }

View File

@ -31,12 +31,14 @@
<script type="text/javascript"> <script type="text/javascript">
require(['main'], function (mct) { require(['main'], function (mct) {
require([ require([
'./tutorials/grootprovider/groots',
'./tutorials/todo/todo', './tutorials/todo/todo',
'./tutorials/todo/bundle', './tutorials/todo/bundle',
'./example/imagery/bundle', './example/imagery/bundle',
'./example/eventGenerator/bundle', './example/eventGenerator/bundle',
'./example/generator/bundle' './example/generator/bundle',
], function (todoPlugin) { ], function (grootify, todoPlugin) {
grootify(mct);
todoPlugin(mct); todoPlugin(mct);
mct.start(); mct.start();
}) })

View File

@ -35,7 +35,8 @@ requirejs.config({
"screenfull": "bower_components/screenfull/dist/screenfull.min", "screenfull": "bower_components/screenfull/dist/screenfull.min",
"text": "bower_components/text/text", "text": "bower_components/text/text",
"uuid": "bower_components/node-uuid/uuid", "uuid": "bower_components/node-uuid/uuid",
"zepto": "bower_components/zepto/zepto.min" "zepto": "bower_components/zepto/zepto.min",
"lodash": "bower_components/lodash/lodash"
}, },
"shim": { "shim": {
"angular": { "angular": {

View File

@ -1,8 +1,13 @@
define([ define([
'EventEmitter', 'EventEmitter',
'legacyRegistry', 'legacyRegistry',
'./api/api' './api/api',
], function (EventEmitter, legacyRegistry, api) { './api/objects/bundle'
], function (
EventEmitter,
legacyRegistry,
api
) {
function MCT() { function MCT() {
EventEmitter.call(this); EventEmitter.call(this);
this.legacyBundle = { extensions: {} }; this.legacyBundle = { extensions: {} };

View File

@ -1,9 +1,12 @@
define([ define([
'./Type' './Type',
'./objects/ObjectAPI'
], function ( ], function (
Type Type,
ObjectAPI
) { ) {
return { return {
Type: Type Type: Type,
Objects: ObjectAPI
}; };
}); });

View File

@ -0,0 +1,70 @@
define([
'./object-utils',
'./ObjectAPI'
], function (
utils,
ObjectAPI
) {
function ObjectServiceProvider(objectService, instantiate) {
this.objectService = objectService;
this.instantiate = instantiate;
}
ObjectServiceProvider.prototype.save = function (object) {
var key = object.key,
keyString = utils.makeKeyString(key),
newObject = this.instantiate(utils.toOldFormat(object), keyString);
return object.getCapability('persistence')
.persist()
.then(function () {
return utils.toNewFormat(object, key);
});
};
ObjectServiceProvider.prototype.delete = function (object) {
// TODO!
};
ObjectServiceProvider.prototype.get = function (key) {
var keyString = utils.makeKeyString(key);
return this.objectService.getObjects([keyString])
.then(function (results) {
var model = JSON.parse(JSON.stringify(results[keyString].getModel()));
return utils.toNewFormat(model, key);
});
};
// 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 LegacyObjectAPIInterceptor(ROOTS, instantiate, objectService) {
this.getObjects = function (keys) {
var results = {},
promises = keys.map(function (keyString) {
var key = utils.parseKeyString(keyString);
return ObjectAPI.get(key)
.then(function (object) {
object = utils.toOldFormat(object)
results[keyString] = instantiate(object, keyString);
});
});
return Promise.all(promises)
.then(function () {
return results;
});
};
ObjectAPI._supersecretSetFallbackProvider(
new ObjectServiceProvider(objectService, instantiate)
);
ROOTS.forEach(function (r) {
ObjectAPI.addRoot(utils.parseKeyString(r.id));
});
return this;
}
return LegacyObjectAPIInterceptor;
});

View File

@ -0,0 +1,92 @@
define([
'lodash',
'./object-utils'
], function (
_,
utils
) {
/**
Object API. Intercepts the existing object API while also exposing
A new Object API.
MCT.objects.get('mine')
.then(function (root) {
console.log(root);
MCT.objects.getComposition(root)
.then(function (composition) {
console.log(composition)
})
});
*/
var Objects = {},
ROOT_REGISTRY = [],
PROVIDER_REGISTRY = {},
FALLBACK_PROVIDER;
Objects._supersecretSetFallbackProvider = function (p) {
FALLBACK_PROVIDER = p;
};
// Root provider is hardcoded in; can't be skipped.
var RootProvider = {
'get': function () {
return Promise.resolve({
name: 'The root object',
type: 'root',
composition: ROOT_REGISTRY
});
}
};
// Retrieve the provider for a given key.
function getProvider(key) {
if (key.identifier === 'ROOT') {
return RootProvider;
}
return PROVIDER_REGISTRY[key.namespace] || FALLBACK_PROVIDER;
};
Objects.addProvider = function (namespace, provider) {
PROVIDER_REGISTRY[namespace] = provider;
};
[
'save',
'delete',
'get'
].forEach(function (method) {
Objects[method] = function () {
var key = arguments[0],
provider = getProvider(key);
if (!provider) {
throw new Error('No Provider Matched');
}
if (!provider[method]) {
throw new Error('Provider does not support [' + method + '].');
}
return provider[method].apply(provider, arguments);
};
});
Objects.addRoot = function (key) {
ROOT_REGISTRY.unshift(key);
};
Objects.removeRoot = function (key) {
ROOT_REGISTRY = ROOT_REGISTRY.filter(function (k) {
return (
k.identifier !== key.identifier ||
k.namespace !== key.namespace
);
});
};
return Objects;
});

100
src/api/objects/README.md Normal file
View File

@ -0,0 +1,100 @@
# Object API - Overview
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'
}
```
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`.
When interacting with the API you will be dealing with key objects.
# Configuring the Object API
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);
```
### 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.

49
src/api/objects/bundle.js Normal file
View 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.
*****************************************************************************/
/*global define*/
define([
'./LegacyObjectAPIInterceptor',
'legacyRegistry'
], function (
LegacyObjectAPIInterceptor,
legacyRegistry
) {
legacyRegistry.register('src/api/objects', {
name: 'Object API',
description: 'The public Objects API',
extensions: {
components: [
{
provides: "objectService",
type: "decorator",
priority: "mandatory",
implementation: LegacyObjectAPIInterceptor,
depends: [
"roots[]",
"instantiate"
]
}
]
}
});
});

View File

@ -0,0 +1,67 @@
define([
], function (
) {
// take a key string and turn it into a key object
// 'scratch:root' ==> {namespace: 'scratch', identifier: 'root'}
var parseKeyString = function (key) {
if (typeof key === 'object') {
return key;
}
var namespace = '',
identifier = key;
for (var i = 0, escaped = false, len=key.length; i < len; i++) {
if (key[i] === ":" && !escaped) {
namespace = key.slice(0, i);
identifier = key.slice(i + 1);
break;
}
}
return {
namespace: namespace,
identifier: identifier
};
};
// take a key and turn it into a key string
// {namespace: 'scratch', identifier: 'root'} ==> 'scratch:root'
var makeKeyString = function (key) {
if (typeof key === 'string') {
return key;
}
if (!key.namespace) {
return key.identifier;
}
return [
key.namespace.replace(':', '\\:'),
key.identifier.replace(':', '\\:')
].join(':');
};
// Converts composition to use key strings instead of keys
var toOldFormat = function (model) {
delete model.key;
if (model.composition) {
model.composition = model.composition.map(makeKeyString);
}
return model;
};
// converts composition to use keys instead of key strings
var toNewFormat = function (model, key) {
model.key = key;
if (model.composition) {
model.composition = model.composition.map(parseKeyString);
}
return model;
};
return {
toOldFormat: toOldFormat,
toNewFormat: toNewFormat,
makeKeyString: makeKeyString,
parseKeyString: parseKeyString
};
});

View File

@ -0,0 +1,44 @@
define(function () {
return function grootPlugin(mct) {
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);
return mct;
};
});