mirror of
https://github.com/nasa/openmct.git
synced 2025-06-25 10:44:21 +00:00
Compare commits
129 Commits
eval-sourc
...
api-1111
Author | SHA1 | Date | |
---|---|---|---|
7ce9bd969a | |||
8cafd2da7e | |||
6264ab75f3 | |||
7a5cad20ec | |||
4de069b393 | |||
70abd5c1f9 | |||
2a3a61da86 | |||
018bd022cc | |||
4739b36bc3 | |||
c9b1035a6d | |||
6768328475 | |||
60c179eac3 | |||
a20e8d69b5 | |||
1abcb248fe | |||
a8151f5f22 | |||
cdf21f3763 | |||
341bceb4e2 | |||
0470a02272 | |||
e3dc26c130 | |||
96c3d1cac2 | |||
2af778145d | |||
5743eeb33a | |||
f06f714bdc | |||
d592bd1035 | |||
b5f62541ce | |||
33ced4bccf | |||
e37510dbab | |||
f27c41014d | |||
bd796f2beb | |||
bcc32c76d0 | |||
ff2ec6690a | |||
1e0fb3611d | |||
1d4f36a7d9 | |||
0f96fbdd62 | |||
e05fb57fe4 | |||
185cdcab08 | |||
50ccad5aaa | |||
6a23df9454 | |||
ab5b1d3754 | |||
b309f26b56 | |||
b4dc50295c | |||
382dde300a | |||
02aa08a3ef | |||
c95d9c7956 | |||
b1b8df4d87 | |||
bd3c6665fb | |||
10e90519c0 | |||
d341a8be97 | |||
8c439d8109 | |||
9c88b7ce1d | |||
2463e4d59f | |||
d73c505bea | |||
831ecc59d9 | |||
1de26d3c5d | |||
11409ce509 | |||
93872ce074 | |||
8861644f2d | |||
d4948f771b | |||
8295a0bed1 | |||
0656a298da | |||
fe2ce91d50 | |||
14f30b2489 | |||
62d90a8114 | |||
87682607a5 | |||
7bf265b478 | |||
1d31fe8d02 | |||
bfdbc71e40 | |||
1147f3aa47 | |||
719f9f45e8 | |||
95ef70a24c | |||
d5aa998b4c | |||
7890fcae69 | |||
18843cee48 | |||
1879c122c7 | |||
d7ddb96c4e | |||
bccd018d97 | |||
b55668426d | |||
5b656faa9d | |||
8d2c489fa9 | |||
4366b0870d | |||
47a543beb7 | |||
06f87c1472 | |||
c9c41cdcc8 | |||
14a56ea17e | |||
b2e7db71cc | |||
d51e6bfd92 | |||
d475d767d5 | |||
a63e053399 | |||
370b515c23 | |||
4a50f325cb | |||
dbe6a4efc1 | |||
13920d8802 | |||
b6a8c514aa | |||
e4a4704baa | |||
be0029e59a | |||
9cb273ef0a | |||
eec9b1cf4c | |||
1f96e84542 | |||
c289a27305 | |||
c944080790 | |||
96316de6e4 | |||
2240a87ddc | |||
d891affe48 | |||
21a618d1ce | |||
5de7a96ccc | |||
09a833f524 | |||
580a4e52b5 | |||
9c4e17bfab | |||
d3e5d95d6b | |||
c70793ac2d | |||
a6ef1d3423 | |||
4ca2f51d5e | |||
86ac80ddbd | |||
0525ba6b0b | |||
a79e958ffc | |||
03cb0ccb57 | |||
7205faa6bb | |||
136f2ae785 | |||
a07e2fb8e5 | |||
55b531bdeb | |||
7ece5897e8 | |||
a29c7a6eab | |||
c4fec1af6a | |||
a6996df3df | |||
0c660238f2 | |||
b73b824e55 | |||
1954d98628 | |||
7aa034ce23 | |||
385dc5d298 |
114
API.md
Normal file
114
API.md
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
# Open MCT API
|
||||||
|
|
||||||
|
The Open MCT framework public api can be utilized by building the application (`gulp install`) and then copying the file from `dist/main.js` to your directory
|
||||||
|
of choice.
|
||||||
|
|
||||||
|
Open MCT supports AMD, CommonJS, and standard browser loading; it's easy to use
|
||||||
|
in your project.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Open MCT's goal is to allow you to browse, create, edit, and visualize all of the domain knowledge you need on a daily basis.
|
||||||
|
|
||||||
|
To do this, the main building block provided by Open MCT is the domain object-- the temperature sensor on the starboard solar panel, an overlay plot comparing the results of all temperature sensor, the command dictionary for a spacecraft, the individual commands in that dictionary, your "my documents" folder: all of these things are domain objects.
|
||||||
|
|
||||||
|
Domain objects have Types-- so a specific instrument temperature sensor is a "Telemetry Point," and turning on a drill for a certain duration of time is an "Activity". Types allow you to form an ontology of knowledge and provide an abstraction for grouping, visualizing, and interpreting data.
|
||||||
|
|
||||||
|
And then we have Views. Views allow you to visualize a domain object. Views can apply to specific domain objects; they may also apply to certain types of domain objects, or they may apply to everything. Views are simply a method of visualizing domain objects.
|
||||||
|
|
||||||
|
Regions allow you to specify what views are displayed for specific types of domain objects in response to different user actions-- for instance, you may want to display a different view while editing, or you may want to update the toolbar display when objects are selected. Regions allow you to map views to specific user actions.
|
||||||
|
|
||||||
|
Domain objects can be mutated and persisted, developers can create custom actions and apply them to domain objects, and many more things can be done. For more information, read on.
|
||||||
|
|
||||||
|
## The API
|
||||||
|
|
||||||
|
### `MCT.Type(options)`
|
||||||
|
Status: First Draft
|
||||||
|
|
||||||
|
Returns a `typeInstance`. `options` is an object supporting the following properties:
|
||||||
|
|
||||||
|
* `metadata`: `object` defining metadata used in displaying the object; has the following properties:
|
||||||
|
* `label`: `string`, the human-readible name of the type. used in menus and inspector.
|
||||||
|
* `glyph`: `string`, the name of the icon to display for this type, used in labels.
|
||||||
|
* `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
|
||||||
|
|
||||||
|
Register a `typeInstance` with a given Type `key` (a `string`). There can only be one `typeInstance` registered per type `key`. typeInstances must be registered before they can be utilized.
|
||||||
|
|
||||||
|
### `MCT.Objects`
|
||||||
|
Status: First Draft
|
||||||
|
|
||||||
|
Allows you to register object providers, which allows you to integrate domain objects from various different sources. Also implements methods for mutation and persistence of objects. See [Object API](src/api/objects/README.md) for more details.
|
||||||
|
|
||||||
|
### `MCT.Composition`
|
||||||
|
Status: First Draft
|
||||||
|
|
||||||
|
Objects can contain other objects, and the Composition API allows you to fetch the composition of any given domain object, or implement custom methods for defining composition as necessary.
|
||||||
|
|
||||||
|
### `MCT.view(region, definition)`
|
||||||
|
Status: First Draft
|
||||||
|
|
||||||
|
Register a view factory for a specific region. View factories receive an instance of a domain object and return a `View` for that object, or return undefined if they do not know how to generate a view for that object.
|
||||||
|
|
||||||
|
* `ViewDefinition`: an object with the following properties:
|
||||||
|
* `canView(domainObject)`: should return truthy if the view is valid for a given domain object, falsy if it is not capable of generating a view for that object.
|
||||||
|
* `view(domainObject)`: should instantate and return a `View` for the given object.
|
||||||
|
* `metadata()`: a function that returns metadata about this view. Optional.
|
||||||
|
* `View`: an object containing a number of lifecycle methods:
|
||||||
|
* `view.show(container)`: instantiate a view (a set of dom elements) and attach it to the container.
|
||||||
|
* `view.destroy(container)`: remove any listeners and expect your dom elements to be destroyed.
|
||||||
|
|
||||||
|
For a basic introduction to views & types, check out these tutorials:
|
||||||
|
|
||||||
|
* [custom-view](custom-view.html) -- Implementing a custom view with vanilla javascript.
|
||||||
|
* [custom-view-react](custom-view-react.html) -- Implementing a custom view with React.
|
||||||
|
|
||||||
|
### `MCT.conductor`
|
||||||
|
Status: First Draft
|
||||||
|
|
||||||
|
The time conductor is an API that facilitates time synchronization across multiple components. Components that would like to be "time aware" may attach listeners to the time conductor API to allow them to remain synchronized with other components. For more information ont he time conductor API, please look at the API draft here: https://github.com/nasa/openmct/blob/66220b89ca568075f107505ba414de9457dc0427/platform/features/conductor-redux/src/README.md
|
||||||
|
|
||||||
|
### `MCT.selection`
|
||||||
|
Status: First Draft
|
||||||
|
|
||||||
|
Tracks the application's selection state (which elements of a view has a user selected?)
|
||||||
|
|
||||||
|
One or more JavaScript objects may be selected at any given time. User code is responsible for any necessary type-checking.
|
||||||
|
|
||||||
|
The following methods are exposed from this object:
|
||||||
|
|
||||||
|
* `select(value)`: Add `value` to the current selection.
|
||||||
|
* `deselect(value)`: Remove `value` from the current selection.
|
||||||
|
* `selected()`: Get array of all selected objects.
|
||||||
|
* `clear()`: Deselect all selected objects.
|
||||||
|
|
||||||
|
MCT.selection is an EventEmitter; a `change` event is emitted whenever the selection changes.
|
||||||
|
|
||||||
|
### `MCT.systems`
|
||||||
|
Status: Not Implemented, Needs to be ported from old system.
|
||||||
|
|
||||||
|
A registry for different time system definitions. Based upon the previous time format system which utilized the "formats" extension category.
|
||||||
|
|
||||||
|
### `MCT.run([container])`
|
||||||
|
Status: Stable Draft
|
||||||
|
|
||||||
|
Run the MCT application, loading the application into the `container`, a DOM element. If a container is not specified, the application is injected into the body of the page.
|
||||||
|
|
||||||
|
### `MCT.install(plugin)`
|
||||||
|
Status: Stable Draft
|
||||||
|
|
||||||
|
Install a plugin in MCT. Must be called before calling `run`. Plugins are functions which are invoked with the `MCT` instance as their first argument, and are expected to use the MCT public API to add functionality.
|
||||||
|
|
||||||
|
For an example of writing a plugin, check out [plugin-example.html](plugin-example.html)
|
||||||
|
|
||||||
|
### `MCT.setAssetPath(path)`
|
||||||
|
|
||||||
|
Sets the path (absolute or relative) at which the Open MCT static files are being hosted. The default value is '.'.
|
||||||
|
|
||||||
|
Note that this API is transitional and will be removed in a future version.
|
||||||
|
|
@ -18,6 +18,9 @@
|
|||||||
"node-uuid": "^1.4.7",
|
"node-uuid": "^1.4.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",
|
||||||
|
"lodash": "3.10.1",
|
||||||
|
"almond": "~0.3.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
13
build/jsdoc/plugins/mct-only.js
Normal file
13
build/jsdoc/plugins/mct-only.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
module.exports = {
|
||||||
|
handlers: {
|
||||||
|
processingComplete: function (e) {
|
||||||
|
e.doclets.forEach(function (doclet) {
|
||||||
|
var memberof = doclet.memberof || "";
|
||||||
|
var longname = doclet.longname || "";
|
||||||
|
|
||||||
|
doclet.ignore = longname !== 'module:openmct' &&
|
||||||
|
memberof.indexOf('module:openmct') !== 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
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>
|
144
custom-view-react.html
Normal file
144
custom-view-react.html
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||||
|
<title>Implementing a Custom Type and View </title>
|
||||||
|
<script src="dist/main.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.2.1/react.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.2.1/react-dom.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="text/babel">
|
||||||
|
|
||||||
|
// First, we're going to create the Todo List type, so that users can create
|
||||||
|
// todo lists.
|
||||||
|
|
||||||
|
MCT.type('example.todo', new MCT.Type({
|
||||||
|
metadata: {
|
||||||
|
label: "To-Do List",
|
||||||
|
glyph: "2",
|
||||||
|
description: "A list of things that need to be done."
|
||||||
|
},
|
||||||
|
initialize: function (object) {
|
||||||
|
object.tasks = [
|
||||||
|
{ description: "This is a task." }
|
||||||
|
];
|
||||||
|
},
|
||||||
|
creatable: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
/*
|
||||||
|
Refresh the page, and you should be able to create new Todo Lists.
|
||||||
|
unfortunately, when you navigate to a Todo list, you see a blank page. let's
|
||||||
|
fix that by adding a main view for that todo list.
|
||||||
|
|
||||||
|
If you're wondering why this is commented out, well, it's because we'll
|
||||||
|
write a new version later.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Task = React.createClass({
|
||||||
|
render: function() {
|
||||||
|
return (
|
||||||
|
<li>
|
||||||
|
<input type="checkbox"
|
||||||
|
checked={this.props.checked}/>
|
||||||
|
<span>{this.props.description}</span>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var TaskList = React.createClass({
|
||||||
|
render: function () {
|
||||||
|
var taskNodes = this.props.tasks.map(function(task) {
|
||||||
|
return (
|
||||||
|
<Task checked={task.checked}
|
||||||
|
description={task.description}/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<ul>
|
||||||
|
{taskNodes}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
MCT.view(MCT.regions.main, {
|
||||||
|
canView: function (domainObject) {
|
||||||
|
return domainObject.type === 'example.todo';
|
||||||
|
},
|
||||||
|
view: function (domainObject) {
|
||||||
|
var mutableObject = MCT.Objects.getMutable(domainObject);
|
||||||
|
|
||||||
|
return {
|
||||||
|
show: function (container) {
|
||||||
|
ReactDOM.render(
|
||||||
|
<TaskList tasks={domainObject.tasks}/>,
|
||||||
|
container
|
||||||
|
);
|
||||||
|
mutableObject.on('tasks', function (tasks) {
|
||||||
|
ReactDOM.render(
|
||||||
|
<TaskList tasks={tasks}/>,
|
||||||
|
container
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
Refresh the page and you should see a todo list with checkboxes! Now let's
|
||||||
|
Allow you to add tasks by mutating the object. We'll add a toolbar view to
|
||||||
|
do this.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var TaskToolbar = React.createClass({
|
||||||
|
render: function () {
|
||||||
|
return (
|
||||||
|
<button onClick={this.props.addTask}>Add Task</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
MCT.view(MCT.regions.toolbar, {
|
||||||
|
canView: function (domainObject) {
|
||||||
|
return domainObject.type === 'example.todo';
|
||||||
|
},
|
||||||
|
view: function (domainObject) {
|
||||||
|
var mutableObject = MCT.Objects.getMutable(domainObject);
|
||||||
|
|
||||||
|
function addTask(event) {
|
||||||
|
var description = prompt('Task description');
|
||||||
|
var tasks = mutableObject.get('tasks');
|
||||||
|
tasks.push({
|
||||||
|
description: description,
|
||||||
|
complete: false
|
||||||
|
});
|
||||||
|
mutableObject.set('tasks', tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
show: function (container) {
|
||||||
|
ReactDOM.render(
|
||||||
|
<TaskToolbar addTask={addTask}/>,
|
||||||
|
container
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
Refresh the page, edit the todo list, and you'll have a button that allows
|
||||||
|
you to add tasks! Unfortunately, new tasks don't show in the list. Why?
|
||||||
|
Well, if your view should update on mutation, you need to set up the correct
|
||||||
|
listeners. Let's update the TodoView we made earlier:
|
||||||
|
*/
|
||||||
|
|
||||||
|
MCT.run();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
160
custom-view.html
Normal file
160
custom-view.html
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||||
|
<title>Implementing a Custom Type and View </title>
|
||||||
|
<script src="dist/main.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
// First, we're going to create the Todo List type, so that users can create
|
||||||
|
// todo lists.
|
||||||
|
|
||||||
|
MCT.type('example.todo', new MCT.Type({
|
||||||
|
metadata: {
|
||||||
|
label: "To-Do List",
|
||||||
|
glyph: "2",
|
||||||
|
description: "A list of things that need to be done."
|
||||||
|
},
|
||||||
|
initialize: function (object) {
|
||||||
|
object.tasks = [
|
||||||
|
{ description: "This is a task." }
|
||||||
|
];
|
||||||
|
},
|
||||||
|
creatable: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
/*
|
||||||
|
Refresh the page, and you should be able to create new Todo Lists.
|
||||||
|
unfortunately, when you navigate to a Todo list, you see a blank page. let's
|
||||||
|
fix that by adding a main view for that todo list.
|
||||||
|
|
||||||
|
If you're wondering why this is commented out, well, it's because we'll
|
||||||
|
write a new version later.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// MCT.view(MCT.regions.main, {
|
||||||
|
// canView: function (domainObject) {
|
||||||
|
// return domainObject.type === 'example.todo';
|
||||||
|
// },
|
||||||
|
// view: function (domainObject) {
|
||||||
|
// function renderTask(task) {
|
||||||
|
// return [
|
||||||
|
// '<li>',
|
||||||
|
// '<input type="checkbox"' + (task.complete ? ' checked="true"' : '') + '>',
|
||||||
|
// '<span>' + task.description + '</span>',
|
||||||
|
// '</li>'
|
||||||
|
// ].join('');
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// function renderTaskList() {
|
||||||
|
// return [
|
||||||
|
// '<ul>',
|
||||||
|
// domainObject.tasks.map(renderTask).join(''),
|
||||||
|
// '</ul>'
|
||||||
|
// ].join('');
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// return {
|
||||||
|
// show: function (container) {
|
||||||
|
// container.innerHTML = renderTaskList();
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
/*
|
||||||
|
Refresh the page and you should see a todo list with checkboxes! Now let's
|
||||||
|
Allow you to add tasks by mutating the object. We'll add a toolbar view to
|
||||||
|
do this.
|
||||||
|
*/
|
||||||
|
|
||||||
|
MCT.view(MCT.regions.toolbar, {
|
||||||
|
canView: function (domainObject) {
|
||||||
|
return domainObject.type === 'example.todo';
|
||||||
|
},
|
||||||
|
view: function (domainObject) {
|
||||||
|
var mutableObject = MCT.Objects.getMutable(domainObject);
|
||||||
|
|
||||||
|
function addTask(event) {
|
||||||
|
var description = prompt('Task description');
|
||||||
|
var tasks = mutableObject.get('tasks');
|
||||||
|
tasks.push({
|
||||||
|
description: description,
|
||||||
|
complete: false
|
||||||
|
});
|
||||||
|
mutableObject.set('tasks', tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
show: function (container) {
|
||||||
|
container.addEventListener('click', addTask);
|
||||||
|
container.innerHTML = '<button>Add Task</button>';
|
||||||
|
},
|
||||||
|
destroy: function (container) {
|
||||||
|
container.removeEventListener('click', addTask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
Refresh the page, edit the todo list, and you'll have a button that allows
|
||||||
|
you to add tasks! Unfortunately, new tasks don't show in the list. Why?
|
||||||
|
Well, if your view should update on mutation, you need to set up the correct
|
||||||
|
listeners. Let's update the TodoView we made earlier:
|
||||||
|
*/
|
||||||
|
|
||||||
|
MCT.view(MCT.regions.main, {
|
||||||
|
canView: function(domainObject) {
|
||||||
|
return domainObject.type === 'example.todo'
|
||||||
|
},
|
||||||
|
view: function (domainObject) {
|
||||||
|
|
||||||
|
var mutableObject = MCT.Objects.getMutable(domainObject);
|
||||||
|
|
||||||
|
function renderTask(task) {
|
||||||
|
return [
|
||||||
|
'<li>',
|
||||||
|
'<input type="checkbox"' + (task.complete ? ' checked="true"' : '') + '>',
|
||||||
|
'<span>' + task.description + '</span>',
|
||||||
|
'</li>'
|
||||||
|
].join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTaskList(tasks) {
|
||||||
|
return [
|
||||||
|
'<ul>',
|
||||||
|
tasks.map(renderTask).join(''),
|
||||||
|
'</ul>'
|
||||||
|
].join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCheckboxChange(event) {
|
||||||
|
var checkbox = event.target;
|
||||||
|
var taskEl = checkbox.parentNode;
|
||||||
|
var taskList = taskEl.parentNode;
|
||||||
|
var taskIndex = [].slice.apply(taskList.children).indexOf(taskEl);
|
||||||
|
mutableObject.set('tasks[' + taskIndex + '].complete', checkbox.checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
show: function (container) {
|
||||||
|
container.innerHTML = renderTaskList(domainObject.tasks);
|
||||||
|
mutableObject.on('tasks', function (tasks) {
|
||||||
|
container.innerHTML = renderTaskList(tasks);
|
||||||
|
});
|
||||||
|
container.addEventListener('change', onCheckboxChange);
|
||||||
|
},
|
||||||
|
destroy: function () {
|
||||||
|
mutableObject.stopListening();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
MCT.run();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -36,7 +36,7 @@ define([
|
|||||||
legacyRegistry
|
legacyRegistry
|
||||||
) {
|
) {
|
||||||
"use strict";
|
"use strict";
|
||||||
legacyRegistry.register("example/notifications", {
|
legacyRegistry.register("example/msl-adapter", {
|
||||||
"name" : "Mars Science Laboratory Data Adapter",
|
"name" : "Mars Science Laboratory Data Adapter",
|
||||||
"extensions" : {
|
"extensions" : {
|
||||||
"types": [
|
"types": [
|
||||||
|
68
gulpfile.js
68
gulpfile.js
@ -21,40 +21,33 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
/*global require,__dirname*/
|
/*global require,__dirname*/
|
||||||
|
|
||||||
var gulp = require('gulp'),
|
var gulp = require('gulp'),
|
||||||
requirejsOptimize = require('gulp-requirejs-optimize'),
|
|
||||||
sourcemaps = require('gulp-sourcemaps'),
|
sourcemaps = require('gulp-sourcemaps'),
|
||||||
rename = require('gulp-rename'),
|
|
||||||
sass = require('gulp-sass'),
|
|
||||||
bourbon = require('node-bourbon'),
|
|
||||||
jshint = require('gulp-jshint'),
|
|
||||||
jscs = require('gulp-jscs'),
|
|
||||||
replace = require('gulp-replace-task'),
|
|
||||||
karma = require('karma'),
|
|
||||||
path = require('path'),
|
path = require('path'),
|
||||||
fs = require('fs'),
|
fs = require('fs'),
|
||||||
git = require('git-rev-sync'),
|
git = require('git-rev-sync'),
|
||||||
moment = require('moment'),
|
moment = require('moment'),
|
||||||
merge = require('merge-stream'),
|
|
||||||
project = require('./package.json'),
|
project = require('./package.json'),
|
||||||
_ = require('lodash'),
|
_ = require('lodash'),
|
||||||
paths = {
|
paths = {
|
||||||
main: 'main.js',
|
main: 'main.js',
|
||||||
dist: 'dist',
|
dist: 'dist',
|
||||||
assets: 'dist/assets',
|
|
||||||
scss: ['./platform/**/*.scss', './example/**/*.scss'],
|
scss: ['./platform/**/*.scss', './example/**/*.scss'],
|
||||||
|
assets: [
|
||||||
|
'./{example,platform}/**/*.{css,css.map,png,svg,ico,woff,eot,ttf}'
|
||||||
|
],
|
||||||
scripts: [ 'main.js', 'platform/**/*.js', 'src/**/*.js' ],
|
scripts: [ 'main.js', 'platform/**/*.js', 'src/**/*.js' ],
|
||||||
specs: [ 'platform/**/*Spec.js', 'src/**/*Spec.js' ],
|
specs: [ 'platform/**/*Spec.js', 'src/**/*Spec.js' ],
|
||||||
static: [
|
|
||||||
'index.html',
|
|
||||||
'platform/**/*',
|
|
||||||
'example/**/*',
|
|
||||||
'bower_components/**/*'
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
options = {
|
options = {
|
||||||
requirejsOptimize: {
|
requirejsOptimize: {
|
||||||
name: paths.main.replace(/\.js$/, ''),
|
name: 'bower_components/almond/almond.js',
|
||||||
|
include: paths.main.replace('.js', ''),
|
||||||
|
wrap: {
|
||||||
|
startFile: "src/start.frag",
|
||||||
|
endFile: "src/end.frag"
|
||||||
|
},
|
||||||
mainConfigFile: paths.main,
|
mainConfigFile: paths.main,
|
||||||
wrapShim: true
|
wrapShim: true
|
||||||
},
|
},
|
||||||
@ -63,7 +56,6 @@ var gulp = require('gulp'),
|
|||||||
singleRun: true
|
singleRun: true
|
||||||
},
|
},
|
||||||
sass: {
|
sass: {
|
||||||
includePaths: bourbon.includePaths,
|
|
||||||
sourceComments: true
|
sourceComments: true
|
||||||
},
|
},
|
||||||
replace: {
|
replace: {
|
||||||
@ -77,6 +69,8 @@ var gulp = require('gulp'),
|
|||||||
};
|
};
|
||||||
|
|
||||||
gulp.task('scripts', function () {
|
gulp.task('scripts', function () {
|
||||||
|
var requirejsOptimize = require('gulp-requirejs-optimize');
|
||||||
|
var replace = require('gulp-replace-task');
|
||||||
return gulp.src(paths.main)
|
return gulp.src(paths.main)
|
||||||
.pipe(sourcemaps.init())
|
.pipe(sourcemaps.init())
|
||||||
.pipe(requirejsOptimize(options.requirejsOptimize))
|
.pipe(requirejsOptimize(options.requirejsOptimize))
|
||||||
@ -86,10 +80,16 @@ gulp.task('scripts', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('test', function (done) {
|
gulp.task('test', function (done) {
|
||||||
|
var karma = require('karma');
|
||||||
new karma.Server(options.karma, done).start();
|
new karma.Server(options.karma, done).start();
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('stylesheets', function () {
|
gulp.task('stylesheets', function () {
|
||||||
|
var sass = require('gulp-sass');
|
||||||
|
var rename = require('gulp-rename');
|
||||||
|
var bourbon = require('node-bourbon');
|
||||||
|
options.sass.includePaths = bourbon.includePaths;
|
||||||
|
|
||||||
return gulp.src(paths.scss, {base: '.'})
|
return gulp.src(paths.scss, {base: '.'})
|
||||||
.pipe(sourcemaps.init())
|
.pipe(sourcemaps.init())
|
||||||
.pipe(sass(options.sass).on('error', sass.logError))
|
.pipe(sass(options.sass).on('error', sass.logError))
|
||||||
@ -103,6 +103,9 @@ gulp.task('stylesheets', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('lint', function () {
|
gulp.task('lint', function () {
|
||||||
|
var jshint = require('gulp-jshint');
|
||||||
|
var merge = require('merge-stream');
|
||||||
|
|
||||||
var nonspecs = paths.specs.map(function (glob) {
|
var nonspecs = paths.specs.map(function (glob) {
|
||||||
return "!" + glob;
|
return "!" + glob;
|
||||||
}),
|
}),
|
||||||
@ -117,6 +120,8 @@ gulp.task('lint', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('checkstyle', function () {
|
gulp.task('checkstyle', function () {
|
||||||
|
var jscs = require('gulp-jscs');
|
||||||
|
|
||||||
return gulp.src(paths.scripts)
|
return gulp.src(paths.scripts)
|
||||||
.pipe(jscs())
|
.pipe(jscs())
|
||||||
.pipe(jscs.reporter())
|
.pipe(jscs.reporter())
|
||||||
@ -124,18 +129,35 @@ gulp.task('checkstyle', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('fixstyle', function () {
|
gulp.task('fixstyle', function () {
|
||||||
|
var jscs = require('gulp-jscs');
|
||||||
|
|
||||||
return gulp.src(paths.scripts, { base: '.' })
|
return gulp.src(paths.scripts, { base: '.' })
|
||||||
.pipe(jscs({ fix: true }))
|
.pipe(jscs({ fix: true }))
|
||||||
.pipe(gulp.dest('.'));
|
.pipe(gulp.dest('.'));
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('static', ['stylesheets'], function () {
|
gulp.task('assets', ['stylesheets'], function () {
|
||||||
return gulp.src(paths.static, { base: '.' })
|
return gulp.src(paths.assets)
|
||||||
.pipe(gulp.dest(paths.dist));
|
.pipe(gulp.dest(paths.dist));
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('watch', function () {
|
gulp.task('watch', function () {
|
||||||
gulp.watch(paths.scss, ['stylesheets']);
|
return gulp.watch(paths.scss, ['stylesheets', 'assets']);
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('api', function () {
|
||||||
|
var jsdoc2md = require('gulp-jsdoc-to-markdown');
|
||||||
|
var concat = require('gulp-concat');
|
||||||
|
var markdown = require('gulp-markdown');
|
||||||
|
return gulp.src('src/**/*.js')
|
||||||
|
.pipe(concat('api.md'))
|
||||||
|
.pipe(jsdoc2md(require('./jsdoc.json')))
|
||||||
|
.pipe(markdown())
|
||||||
|
.pipe(gulp.dest(paths.dist));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('api-watch', function () {
|
||||||
|
return gulp.watch('src/**/*.js', ['api']);
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('serve', function () {
|
gulp.task('serve', function () {
|
||||||
@ -143,9 +165,9 @@ gulp.task('serve', function () {
|
|||||||
var app = require('./app.js');
|
var app = require('./app.js');
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('develop', ['serve', 'stylesheets', 'watch']);
|
gulp.task('develop', ['serve', 'install', 'watch']);
|
||||||
|
|
||||||
gulp.task('install', [ 'static', 'scripts' ]);
|
gulp.task('install', [ 'assets', 'scripts' ]);
|
||||||
|
|
||||||
gulp.task('verify', [ 'lint', 'test', 'checkstyle' ]);
|
gulp.task('verify', [ 'lint', 'test', 'checkstyle' ]);
|
||||||
|
|
||||||
|
@ -31,10 +31,14 @@
|
|||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
require(['main'], function (mct) {
|
require(['main'], function (mct) {
|
||||||
require([
|
require([
|
||||||
|
'./tutorials/todo/todo',
|
||||||
'./example/imagery/bundle',
|
'./example/imagery/bundle',
|
||||||
'./example/eventGenerator/bundle',
|
'./example/eventGenerator/bundle',
|
||||||
'./example/generator/bundle'
|
'./example/generator/bundle'
|
||||||
], mct.run.bind(mct));
|
], function (todoPlugin) {
|
||||||
|
mct.install(todoPlugin);
|
||||||
|
mct.run();
|
||||||
|
})
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<link rel="stylesheet" href="platform/commonUI/general/res/css/startup-base.css">
|
<link rel="stylesheet" href="platform/commonUI/general/res/css/startup-base.css">
|
||||||
@ -48,7 +52,5 @@
|
|||||||
<div class="l-splash-holder s-splash-holder">
|
<div class="l-splash-holder s-splash-holder">
|
||||||
<div class="l-splash s-splash"></div>
|
<div class="l-splash s-splash"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ng-view></div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
{
|
{
|
||||||
"source": {
|
"source": {
|
||||||
"include": [
|
"include": [
|
||||||
"platform/"
|
"src/"
|
||||||
],
|
],
|
||||||
"includePattern": "platform/.+\\.js$",
|
"includePattern": "src/.+\\.js$",
|
||||||
"excludePattern": ".+\\Spec\\.js$|lib/.+"
|
"excludePattern": ".+\\Spec\\.js$|lib/.+"
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"plugins/markdown"
|
"plugins/markdown",
|
||||||
|
"build/jsdoc/plugins/mct-only"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
68
main.js
68
main.js
@ -28,13 +28,15 @@ requirejs.config({
|
|||||||
"angular-route": "bower_components/angular-route/angular-route.min",
|
"angular-route": "bower_components/angular-route/angular-route.min",
|
||||||
"csv": "bower_components/comma-separated-values/csv.min",
|
"csv": "bower_components/comma-separated-values/csv.min",
|
||||||
"es6-promise": "bower_components/es6-promise/promise.min",
|
"es6-promise": "bower_components/es6-promise/promise.min",
|
||||||
|
"EventEmitter": "bower_components/eventemitter3/index",
|
||||||
"moment": "bower_components/moment/moment",
|
"moment": "bower_components/moment/moment",
|
||||||
"moment-duration-format": "bower_components/moment-duration-format/lib/moment-duration-format",
|
"moment-duration-format": "bower_components/moment-duration-format/lib/moment-duration-format",
|
||||||
"saveAs": "bower_components/FileSaver.js/FileSaver.min",
|
"saveAs": "bower_components/FileSaver.js/FileSaver.min",
|
||||||
"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": {
|
||||||
@ -43,6 +45,9 @@ requirejs.config({
|
|||||||
"angular-route": {
|
"angular-route": {
|
||||||
"deps": ["angular"]
|
"deps": ["angular"]
|
||||||
},
|
},
|
||||||
|
"EventEmitter": {
|
||||||
|
"exports": "EventEmitter"
|
||||||
|
},
|
||||||
"moment-duration-format": {
|
"moment-duration-format": {
|
||||||
"deps": ["moment"]
|
"deps": ["moment"]
|
||||||
},
|
},
|
||||||
@ -51,53 +56,32 @@ requirejs.config({
|
|||||||
},
|
},
|
||||||
"zepto": {
|
"zepto": {
|
||||||
"exports": "Zepto"
|
"exports": "Zepto"
|
||||||
|
},
|
||||||
|
"lodash": {
|
||||||
|
"exports": "lodash"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
define([
|
define([
|
||||||
'./platform/framework/src/Main',
|
'./platform/framework/src/Main',
|
||||||
'legacyRegistry',
|
'./src/defaultRegistry',
|
||||||
|
'./src/MCT'
|
||||||
|
], function (Main, defaultRegistry, MCT) {
|
||||||
|
var mct = new MCT();
|
||||||
|
|
||||||
'./platform/framework/bundle',
|
mct.legacyRegistry = defaultRegistry;
|
||||||
'./platform/core/bundle',
|
mct.run = function (domElement) {
|
||||||
'./platform/representation/bundle',
|
if (!domElement) { domElement = document.body; }
|
||||||
'./platform/commonUI/about/bundle',
|
var appDiv = document.createElement('div');
|
||||||
'./platform/commonUI/browse/bundle',
|
appDiv.setAttribute('ng-view', '');
|
||||||
'./platform/commonUI/edit/bundle',
|
appDiv.className = 'user-environ';
|
||||||
'./platform/commonUI/dialog/bundle',
|
domElement.appendChild(appDiv);
|
||||||
'./platform/commonUI/formats/bundle',
|
mct.start();
|
||||||
'./platform/commonUI/general/bundle',
|
|
||||||
'./platform/commonUI/inspect/bundle',
|
|
||||||
'./platform/commonUI/mobile/bundle',
|
|
||||||
'./platform/commonUI/themes/espresso/bundle',
|
|
||||||
'./platform/commonUI/notification/bundle',
|
|
||||||
'./platform/containment/bundle',
|
|
||||||
'./platform/execution/bundle',
|
|
||||||
'./platform/exporters/bundle',
|
|
||||||
'./platform/telemetry/bundle',
|
|
||||||
'./platform/features/clock/bundle',
|
|
||||||
'./platform/features/imagery/bundle',
|
|
||||||
'./platform/features/layout/bundle',
|
|
||||||
'./platform/features/pages/bundle',
|
|
||||||
'./platform/features/plot/bundle',
|
|
||||||
'./platform/features/timeline/bundle',
|
|
||||||
'./platform/features/table/bundle',
|
|
||||||
'./platform/forms/bundle',
|
|
||||||
'./platform/identity/bundle',
|
|
||||||
'./platform/persistence/aggregator/bundle',
|
|
||||||
'./platform/persistence/local/bundle',
|
|
||||||
'./platform/persistence/queue/bundle',
|
|
||||||
'./platform/policy/bundle',
|
|
||||||
'./platform/entanglement/bundle',
|
|
||||||
'./platform/search/bundle',
|
|
||||||
'./platform/status/bundle',
|
|
||||||
'./platform/commonUI/regions/bundle'
|
|
||||||
], function (Main, legacyRegistry) {
|
|
||||||
return {
|
|
||||||
legacyRegistry: legacyRegistry,
|
|
||||||
run: function () {
|
|
||||||
return new Main().run(legacyRegistry);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
mct.on('start', function () {
|
||||||
|
return new Main().run(defaultRegistry);
|
||||||
|
});
|
||||||
|
|
||||||
|
return mct;
|
||||||
});
|
});
|
||||||
|
64
object-provider.html
Normal file
64
object-provider.html
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||||
|
<title>Groot Tutorial</title>
|
||||||
|
<script src="dist/main.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
// First, we need a source of objects, so we're going to define a few
|
||||||
|
// objects that we wish to expose. The first object is a folder which
|
||||||
|
// contains the other objects.
|
||||||
|
|
||||||
|
|
||||||
|
var GROOT_ROOT = {
|
||||||
|
name: 'I am groot',
|
||||||
|
type: 'folder',
|
||||||
|
composition: [
|
||||||
|
{
|
||||||
|
namespace: 'groot',
|
||||||
|
identifier: 'arms'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
namespace: 'groot',
|
||||||
|
identifier: 'legs'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
namespace: 'groot',
|
||||||
|
identifier: 'torso'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Now, we will define an object provider. This will allow us to fetch the
|
||||||
|
// GROOT_ROOT object, plus any other objects in the `groot` namespace.
|
||||||
|
// For more info, see the Object API documentation.
|
||||||
|
|
||||||
|
MCT.Objects.addProvider('groot', {
|
||||||
|
// we'll only define a get function, objects from this provider will
|
||||||
|
// not be mutable.
|
||||||
|
get: function (key) {
|
||||||
|
if (key.identifier === 'groot') {
|
||||||
|
return Promise.resolve(GROOT_ROOT);
|
||||||
|
}
|
||||||
|
return Promise.resolve({
|
||||||
|
name: 'Groot\'s ' + key.identifier
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Finally, we need to add a "ROOT." This is an identifier for an object
|
||||||
|
// that Open MCT will load at run time and show at the top-level of the
|
||||||
|
// navigation tree.
|
||||||
|
|
||||||
|
MCT.Objects.addRoot({
|
||||||
|
namespace: 'groot',
|
||||||
|
identifier: 'groot'
|
||||||
|
});
|
||||||
|
|
||||||
|
MCT.run();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -12,8 +12,11 @@
|
|||||||
"git-rev-sync": "^1.4.0",
|
"git-rev-sync": "^1.4.0",
|
||||||
"glob": ">= 3.0.0",
|
"glob": ">= 3.0.0",
|
||||||
"gulp": "^3.9.0",
|
"gulp": "^3.9.0",
|
||||||
|
"gulp-concat": "^2.6.0",
|
||||||
"gulp-jscs": "^3.0.2",
|
"gulp-jscs": "^3.0.2",
|
||||||
|
"gulp-jsdoc-to-markdown": "^1.2.2",
|
||||||
"gulp-jshint": "^2.0.0",
|
"gulp-jshint": "^2.0.0",
|
||||||
|
"gulp-markdown": "^1.2.0",
|
||||||
"gulp-rename": "^1.2.2",
|
"gulp-rename": "^1.2.2",
|
||||||
"gulp-replace-task": "^0.11.0",
|
"gulp-replace-task": "^0.11.0",
|
||||||
"gulp-requirejs-optimize": "^0.3.1",
|
"gulp-requirejs-optimize": "^0.3.1",
|
||||||
@ -47,7 +50,7 @@
|
|||||||
"test": "karma start --single-run",
|
"test": "karma start --single-run",
|
||||||
"jshint": "jshint platform example",
|
"jshint": "jshint platform example",
|
||||||
"watch": "karma start",
|
"watch": "karma start",
|
||||||
"jsdoc": "jsdoc -c jsdoc.json -r -d target/docs/api",
|
"jsdoc": "jsdoc -c jsdoc.json -R API.md -r -d target/docs/api",
|
||||||
"otherdoc": "node docs/gendocs.js --in docs/src --out target/docs --suppress-toc 'docs/src/index.md|docs/src/process/index.md'",
|
"otherdoc": "node docs/gendocs.js --in docs/src --out target/docs --suppress-toc 'docs/src/index.md|docs/src/process/index.md'",
|
||||||
"docs": "npm run jsdoc ; npm run otherdoc",
|
"docs": "npm run jsdoc ; npm run otherdoc",
|
||||||
"prepublish": "node ./node_modules/bower/bin/bower install && node ./node_modules/gulp/bin/gulp.js install"
|
"prepublish": "node ./node_modules/bower/bin/bower install && node ./node_modules/gulp/bin/gulp.js install"
|
||||||
|
@ -39,6 +39,7 @@ define([
|
|||||||
"text!./res/templates/items/items.html",
|
"text!./res/templates/items/items.html",
|
||||||
"text!./res/templates/browse/object-properties.html",
|
"text!./res/templates/browse/object-properties.html",
|
||||||
"text!./res/templates/browse/inspector-region.html",
|
"text!./res/templates/browse/inspector-region.html",
|
||||||
|
"text!./res/templates/view-object.html",
|
||||||
'legacyRegistry'
|
'legacyRegistry'
|
||||||
], function (
|
], function (
|
||||||
BrowseController,
|
BrowseController,
|
||||||
@ -59,6 +60,7 @@ define([
|
|||||||
itemsTemplate,
|
itemsTemplate,
|
||||||
objectPropertiesTemplate,
|
objectPropertiesTemplate,
|
||||||
inspectorRegionTemplate,
|
inspectorRegionTemplate,
|
||||||
|
viewObjectTemplate,
|
||||||
legacyRegistry
|
legacyRegistry
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@ -129,7 +131,7 @@ define([
|
|||||||
"representations": [
|
"representations": [
|
||||||
{
|
{
|
||||||
"key": "view-object",
|
"key": "view-object",
|
||||||
"templateUrl": "templates/view-object.html"
|
"template": viewObjectTemplate
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "browse-object",
|
"key": "browse-object",
|
||||||
|
@ -48,6 +48,7 @@ define([
|
|||||||
"./src/directives/MCTSplitPane",
|
"./src/directives/MCTSplitPane",
|
||||||
"./src/directives/MCTSplitter",
|
"./src/directives/MCTSplitter",
|
||||||
"./src/directives/MCTTree",
|
"./src/directives/MCTTree",
|
||||||
|
"./src/filters/ReverseFilter.js",
|
||||||
"text!./res/templates/bottombar.html",
|
"text!./res/templates/bottombar.html",
|
||||||
"text!./res/templates/controls/action-button.html",
|
"text!./res/templates/controls/action-button.html",
|
||||||
"text!./res/templates/controls/input-filter.html",
|
"text!./res/templates/controls/input-filter.html",
|
||||||
@ -96,6 +97,7 @@ define([
|
|||||||
MCTSplitPane,
|
MCTSplitPane,
|
||||||
MCTSplitter,
|
MCTSplitter,
|
||||||
MCTTree,
|
MCTTree,
|
||||||
|
ReverseFilter,
|
||||||
bottombarTemplate,
|
bottombarTemplate,
|
||||||
actionButtonTemplate,
|
actionButtonTemplate,
|
||||||
inputFilterTemplate,
|
inputFilterTemplate,
|
||||||
@ -146,7 +148,8 @@ define([
|
|||||||
"depends": [
|
"depends": [
|
||||||
"stylesheets[]",
|
"stylesheets[]",
|
||||||
"$document",
|
"$document",
|
||||||
"THEME"
|
"THEME",
|
||||||
|
"ASSETS_PATH"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -158,7 +161,7 @@ define([
|
|||||||
],
|
],
|
||||||
"filters": [
|
"filters": [
|
||||||
{
|
{
|
||||||
"implementation": "filters/ReverseFilter.js",
|
"implementation": ReverseFilter,
|
||||||
"key": "reverse"
|
"key": "reverse"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -404,6 +407,11 @@ define([
|
|||||||
"key": "THEME",
|
"key": "THEME",
|
||||||
"value": "unspecified",
|
"value": "unspecified",
|
||||||
"priority": "fallback"
|
"priority": "fallback"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "ASSETS_PATH",
|
||||||
|
"value": ".",
|
||||||
|
"priority": "fallback"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"containers": [
|
"containers": [
|
||||||
|
@ -38,7 +38,7 @@ define(
|
|||||||
* @param $document Angular's jqLite-wrapped document element
|
* @param $document Angular's jqLite-wrapped document element
|
||||||
* @param {string} activeTheme the theme in use
|
* @param {string} activeTheme the theme in use
|
||||||
*/
|
*/
|
||||||
function StyleSheetLoader(stylesheets, $document, activeTheme) {
|
function StyleSheetLoader(stylesheets, $document, activeTheme, assetPath) {
|
||||||
var head = $document.find('head'),
|
var head = $document.find('head'),
|
||||||
document = $document[0];
|
document = $document[0];
|
||||||
|
|
||||||
@ -47,6 +47,7 @@ define(
|
|||||||
// Create a link element, and construct full path
|
// Create a link element, and construct full path
|
||||||
var link = document.createElement('link'),
|
var link = document.createElement('link'),
|
||||||
path = [
|
path = [
|
||||||
|
assetPath,
|
||||||
stylesheet.bundle.path,
|
stylesheet.bundle.path,
|
||||||
stylesheet.bundle.resources,
|
stylesheet.bundle.resources,
|
||||||
stylesheet.stylesheetUrl
|
stylesheet.stylesheetUrl
|
||||||
|
@ -42,11 +42,19 @@ define(
|
|||||||
function addWorker(worker) {
|
function addWorker(worker) {
|
||||||
var key = worker.key;
|
var key = worker.key;
|
||||||
if (!workerUrls[key]) {
|
if (!workerUrls[key]) {
|
||||||
workerUrls[key] = [
|
if (worker.scriptUrl) {
|
||||||
worker.bundle.path,
|
workerUrls[key] = [
|
||||||
worker.bundle.sources,
|
worker.bundle.path,
|
||||||
worker.scriptUrl
|
worker.bundle.sources,
|
||||||
].join("/");
|
worker.scriptUrl
|
||||||
|
].join("/");
|
||||||
|
} else if (worker.scriptText) {
|
||||||
|
var blob = new Blob(
|
||||||
|
[worker.scriptText],
|
||||||
|
{type: 'application/javascript'}
|
||||||
|
);
|
||||||
|
workerUrls[key] = URL.createObjectURL(blob);
|
||||||
|
}
|
||||||
sharedWorkers[key] = worker.shared;
|
sharedWorkers[key] = worker.shared;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,9 @@ define([
|
|||||||
"./src/controllers/TableOptionsController",
|
"./src/controllers/TableOptionsController",
|
||||||
'../../commonUI/regions/src/Region',
|
'../../commonUI/regions/src/Region',
|
||||||
'../../commonUI/browse/src/InspectorRegion',
|
'../../commonUI/browse/src/InspectorRegion',
|
||||||
|
"text!./res/templates/table-options-edit.html",
|
||||||
|
"text!./res/templates/rt-table.html",
|
||||||
|
"text!./res/templates/historical-table.html",
|
||||||
"legacyRegistry"
|
"legacyRegistry"
|
||||||
], function (
|
], function (
|
||||||
MCTTable,
|
MCTTable,
|
||||||
@ -35,6 +38,9 @@ define([
|
|||||||
TableOptionsController,
|
TableOptionsController,
|
||||||
Region,
|
Region,
|
||||||
InspectorRegion,
|
InspectorRegion,
|
||||||
|
tableOptionsEditTemplate,
|
||||||
|
rtTableTemplate,
|
||||||
|
historicalTableTemplate,
|
||||||
legacyRegistry
|
legacyRegistry
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
@ -128,7 +134,7 @@ define([
|
|||||||
"name": "Historical Table",
|
"name": "Historical Table",
|
||||||
"key": "table",
|
"key": "table",
|
||||||
"glyph": "\ue604",
|
"glyph": "\ue604",
|
||||||
"templateUrl": "templates/historical-table.html",
|
"template": historicalTableTemplate,
|
||||||
"needs": [
|
"needs": [
|
||||||
"telemetry"
|
"telemetry"
|
||||||
],
|
],
|
||||||
@ -139,7 +145,7 @@ define([
|
|||||||
"name": "Real-time Table",
|
"name": "Real-time Table",
|
||||||
"key": "rt-table",
|
"key": "rt-table",
|
||||||
"glyph": "\ue620",
|
"glyph": "\ue620",
|
||||||
"templateUrl": "templates/rt-table.html",
|
"template": rtTableTemplate,
|
||||||
"needs": [
|
"needs": [
|
||||||
"telemetry"
|
"telemetry"
|
||||||
],
|
],
|
||||||
@ -157,7 +163,7 @@ define([
|
|||||||
"representations": [
|
"representations": [
|
||||||
{
|
{
|
||||||
"key": "table-options-edit",
|
"key": "table-options-edit",
|
||||||
"templateUrl": "templates/table-options-edit.html"
|
"template": tableOptionsEditTemplate
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"stylesheets": [
|
"stylesheets": [
|
||||||
|
@ -28,6 +28,7 @@ define([
|
|||||||
"text!./res/templates/search-item.html",
|
"text!./res/templates/search-item.html",
|
||||||
"text!./res/templates/search.html",
|
"text!./res/templates/search.html",
|
||||||
"text!./res/templates/search-menu.html",
|
"text!./res/templates/search-menu.html",
|
||||||
|
"text!./src/services/GenericSearchWorker.js",
|
||||||
'legacyRegistry'
|
'legacyRegistry'
|
||||||
], function (
|
], function (
|
||||||
SearchController,
|
SearchController,
|
||||||
@ -37,6 +38,7 @@ define([
|
|||||||
searchItemTemplate,
|
searchItemTemplate,
|
||||||
searchTemplate,
|
searchTemplate,
|
||||||
searchMenuTemplate,
|
searchMenuTemplate,
|
||||||
|
searchWorkerText,
|
||||||
legacyRegistry
|
legacyRegistry
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@ -114,7 +116,7 @@ define([
|
|||||||
"workers": [
|
"workers": [
|
||||||
{
|
{
|
||||||
"key": "genericSearchWorker",
|
"key": "genericSearchWorker",
|
||||||
"scriptUrl": "services/GenericSearchWorker.js"
|
"scriptText": searchWorkerText
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
50
plugin-example.html
Normal file
50
plugin-example.html
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||||
|
<title>Implementing a basic plugin</title>
|
||||||
|
<script src="dist/main.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
function WebPlugin(websites) {
|
||||||
|
this.websites = websites;
|
||||||
|
|
||||||
|
var ROOTS = websites.reduce(function (rootMap, website) {
|
||||||
|
rootMap[website] = {
|
||||||
|
namespace: website,
|
||||||
|
identifier: 'page'
|
||||||
|
};
|
||||||
|
return rootMap;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
function installPlugin(MCT) {
|
||||||
|
Object.keys(ROOTS).forEach(function (rootUrl) {
|
||||||
|
MCT.Objects.addRoot(ROOTS[rootUrl]);
|
||||||
|
MCT.Objects.addProvider(rootUrl, {
|
||||||
|
get: function () {
|
||||||
|
return Promise.resolve({
|
||||||
|
type: 'example.page',
|
||||||
|
url: rootUrl,
|
||||||
|
name: rootUrl
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return installPlugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
var myWebPlugin = WebPlugin([
|
||||||
|
'http://www.wikipedia.org/',
|
||||||
|
'http://nasa.github.io/openmct'
|
||||||
|
]);
|
||||||
|
|
||||||
|
MCT.install(myWebPlugin);
|
||||||
|
|
||||||
|
MCT.run();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -24,10 +24,27 @@ define(function () {
|
|||||||
|
|
||||||
function BundleRegistry() {
|
function BundleRegistry() {
|
||||||
this.bundles = {};
|
this.bundles = {};
|
||||||
|
this.knownBundles = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
BundleRegistry.prototype.register = function (path, definition) {
|
BundleRegistry.prototype.register = function (path, definition) {
|
||||||
this.bundles[path] = definition;
|
if (this.knownBundles.hasOwnProperty(path)) {
|
||||||
|
throw new Error('Cannot register bundle with duplicate path', path);
|
||||||
|
}
|
||||||
|
this.knownBundles[path] = definition;
|
||||||
|
};
|
||||||
|
|
||||||
|
BundleRegistry.prototype.enable = function (path) {
|
||||||
|
if (!this.knownBundles[path]) {
|
||||||
|
throw new Error('Unknown bundle ' + path);
|
||||||
|
}
|
||||||
|
this.bundles[path] = this.knownBundles[path];
|
||||||
|
};
|
||||||
|
|
||||||
|
BundleRegistry.prototype.disable = function (path) {
|
||||||
|
if (!this.bundles[path]) {
|
||||||
|
throw new Error('Tried to disable inactive bundle ' + path);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
BundleRegistry.prototype.contains = function (path) {
|
BundleRegistry.prototype.contains = function (path) {
|
||||||
@ -42,8 +59,14 @@ define(function () {
|
|||||||
return Object.keys(this.bundles);
|
return Object.keys(this.bundles);
|
||||||
};
|
};
|
||||||
|
|
||||||
BundleRegistry.prototype.remove = function (path) {
|
BundleRegistry.prototype.remove = BundleRegistry.prototype.disable;
|
||||||
|
|
||||||
|
BundleRegistry.prototype.delete = function (path) {
|
||||||
|
if (!this.knownBundles[path]) {
|
||||||
|
throw new Error('Cannot remove Unknown Bundle ' + path);
|
||||||
|
}
|
||||||
delete this.bundles[path];
|
delete this.bundles[path];
|
||||||
|
delete this.knownBundles[path];
|
||||||
};
|
};
|
||||||
|
|
||||||
return BundleRegistry;
|
return BundleRegistry;
|
||||||
|
222
src/MCT.js
Normal file
222
src/MCT.js
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
define([
|
||||||
|
'EventEmitter',
|
||||||
|
'legacyRegistry',
|
||||||
|
'uuid',
|
||||||
|
'./api/api',
|
||||||
|
'text!./adapter/templates/edit-object-replacement.html',
|
||||||
|
'./Selection',
|
||||||
|
'./api/objects/object-utils',
|
||||||
|
'./api/TimeConductor'
|
||||||
|
], function (
|
||||||
|
EventEmitter,
|
||||||
|
legacyRegistry,
|
||||||
|
uuid,
|
||||||
|
api,
|
||||||
|
editObjectTemplate,
|
||||||
|
Selection,
|
||||||
|
objectUtils,
|
||||||
|
TimeConductor
|
||||||
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Open MCT application. This may be configured by installing plugins
|
||||||
|
* or registering extensions before the application is started.
|
||||||
|
* @class MCT
|
||||||
|
* @memberof module:openmct
|
||||||
|
* @augments {EventEmitter}
|
||||||
|
*/
|
||||||
|
function MCT() {
|
||||||
|
EventEmitter.call(this);
|
||||||
|
this.legacyBundle = { extensions: {
|
||||||
|
services: [
|
||||||
|
{
|
||||||
|
key: "mct",
|
||||||
|
implementation: function () {
|
||||||
|
return this;
|
||||||
|
}.bind(this)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
} };
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {module:openmct.Selection}
|
||||||
|
* @memberof module:openmct.MCT#
|
||||||
|
* @name selection
|
||||||
|
*/
|
||||||
|
this.selection = new Selection();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {module:openmct.TimeConductor}
|
||||||
|
* @memberof module:openmct.MCT#
|
||||||
|
* @name conductor
|
||||||
|
*/
|
||||||
|
this.conductor = new TimeConductor();
|
||||||
|
|
||||||
|
this.TimeConductor = this.conductor; // compatibility for prototype
|
||||||
|
this.on('navigation', this.selection.clear.bind(this.selection));
|
||||||
|
}
|
||||||
|
|
||||||
|
MCT.prototype = Object.create(EventEmitter.prototype);
|
||||||
|
|
||||||
|
Object.keys(api).forEach(function (k) {
|
||||||
|
MCT.prototype[k] = api[k];
|
||||||
|
});
|
||||||
|
MCT.prototype.MCT = MCT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for interacting with domain objects and the domain
|
||||||
|
* object hierarchy.
|
||||||
|
*
|
||||||
|
* @type {module:openmct.ObjectAPI}
|
||||||
|
* @memberof module:openmct.MCT#
|
||||||
|
* @name Objects
|
||||||
|
*/
|
||||||
|
MCT.Objects = api.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for retrieving and interpreting telemetry data associated
|
||||||
|
* with a domain object.
|
||||||
|
*
|
||||||
|
* @type {module:openmct.TelemetryAPI}
|
||||||
|
* @memberof module:openmct.MCT#
|
||||||
|
* @name Telemetry
|
||||||
|
*/
|
||||||
|
|
||||||
|
MCT.prototype.legacyExtension = function (category, extension) {
|
||||||
|
this.legacyBundle.extensions[category] =
|
||||||
|
this.legacyBundle.extensions[category] || [];
|
||||||
|
this.legacyBundle.extensions[category].push(extension);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set path to where assets are hosted. This should be the path to main.js.
|
||||||
|
* @memberof module:openmct.MCT#
|
||||||
|
* @method setAssetPath
|
||||||
|
*/
|
||||||
|
MCT.prototype.setAssetPath = function (path) {
|
||||||
|
this.legacyExtension('constants', {
|
||||||
|
key: "ASSETS_PATH",
|
||||||
|
value: path
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a new type of view.
|
||||||
|
*
|
||||||
|
* @param {string} region the region identifier (see mct.regions)
|
||||||
|
* @param {module:openmct.ViewProvider} provider the provider for this view
|
||||||
|
* @method view
|
||||||
|
* @memberof module:openmct.MCT#
|
||||||
|
*/
|
||||||
|
MCT.prototype.view = function (region, definition) {
|
||||||
|
var viewKey = region + uuid();
|
||||||
|
var adaptedViewKey = "adapted-view-" + region;
|
||||||
|
|
||||||
|
this.legacyExtension(
|
||||||
|
region === this.regions.main ? 'views' : 'representations',
|
||||||
|
{
|
||||||
|
name: "A view",
|
||||||
|
key: adaptedViewKey,
|
||||||
|
editable: true,
|
||||||
|
template: '<mct-view region="\'' +
|
||||||
|
region +
|
||||||
|
'\'" ' +
|
||||||
|
'key="\'' +
|
||||||
|
viewKey +
|
||||||
|
'\'" ' +
|
||||||
|
'mct-object="domainObject">' +
|
||||||
|
'</mct-view>'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.legacyExtension('policies', {
|
||||||
|
category: "view",
|
||||||
|
implementation: function Policy() {
|
||||||
|
this.allow = function (view, domainObject) {
|
||||||
|
if (view.key === adaptedViewKey) {
|
||||||
|
var model = domainObject.getModel();
|
||||||
|
var newDO = objectUtils.toNewFormat(model);
|
||||||
|
return definition.canView(newDO);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.legacyExtension('newViews', {
|
||||||
|
factory: definition,
|
||||||
|
region: region,
|
||||||
|
key: viewKey
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a new [type]{@link module:openmct.Type} of domain object.
|
||||||
|
* @param {string} key a unique identifier for this type of object
|
||||||
|
* @param {module:openmct.Type} type the new type
|
||||||
|
* @memberof module:openmct.MCT#
|
||||||
|
* @method type
|
||||||
|
*/
|
||||||
|
MCT.prototype.type = function (key, type) {
|
||||||
|
var legacyDef = type.toLegacyDefinition();
|
||||||
|
legacyDef.key = key;
|
||||||
|
type.key = key;
|
||||||
|
|
||||||
|
this.legacyExtension('types', legacyDef);
|
||||||
|
this.legacyExtension('representations', {
|
||||||
|
key: "edit-object",
|
||||||
|
priority: "preferred",
|
||||||
|
template: editObjectTemplate,
|
||||||
|
type: key
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start running Open MCT. This should be called only after any plugins
|
||||||
|
* have been installed.
|
||||||
|
* @fires module:openmct.MCT~start
|
||||||
|
* @memberof module:openmct.MCT#
|
||||||
|
* @method start
|
||||||
|
*/
|
||||||
|
MCT.prototype.start = function () {
|
||||||
|
this.legacyExtension('runs', {
|
||||||
|
depends: ['navigationService'],
|
||||||
|
implementation: function (navigationService) {
|
||||||
|
navigationService
|
||||||
|
.addListener(this.emit.bind(this, 'navigation'));
|
||||||
|
}.bind(this)
|
||||||
|
});
|
||||||
|
|
||||||
|
legacyRegistry.register('adapter', this.legacyBundle);
|
||||||
|
legacyRegistry.enable('adapter');
|
||||||
|
/**
|
||||||
|
* Fired by [MCT]{@link module:openmct.MCT} when the application
|
||||||
|
* is started.
|
||||||
|
* @event start
|
||||||
|
* @memberof module:openmct.MCT~
|
||||||
|
*/
|
||||||
|
this.emit('start');
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install a plugin in MCT.
|
||||||
|
*
|
||||||
|
* @param {Function} plugin a plugin install function which will be
|
||||||
|
* invoked with the mct instance.
|
||||||
|
* @memberof module:openmct.MCT#
|
||||||
|
*/
|
||||||
|
MCT.prototype.install = function (plugin) {
|
||||||
|
plugin(this);
|
||||||
|
};
|
||||||
|
|
||||||
|
MCT.prototype.regions = {
|
||||||
|
main: "MAIN",
|
||||||
|
properties: "PROPERTIES",
|
||||||
|
toolbar: "TOOLBAR"
|
||||||
|
};
|
||||||
|
|
||||||
|
return MCT;
|
||||||
|
});
|
72
src/Selection.js
Normal file
72
src/Selection.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
define(['EventEmitter'], function (EventEmitter) {
|
||||||
|
/**
|
||||||
|
* Maintains selection state for the application.
|
||||||
|
* @interface
|
||||||
|
* @memberof module:openmct
|
||||||
|
* @extends {EventEmitter}
|
||||||
|
*/
|
||||||
|
function Selection() {
|
||||||
|
EventEmitter.call(this);
|
||||||
|
this.selectedValues = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired whenever the current selection state changes.
|
||||||
|
* @event change
|
||||||
|
* @memberof module:openmct.Selection~
|
||||||
|
*/
|
||||||
|
|
||||||
|
Selection.prototype = Object.create(EventEmitter.prototype);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new value to the current selection state.
|
||||||
|
* @param {*} value the newly selected value
|
||||||
|
* @returns {Function} a function to deselect this value
|
||||||
|
* @method select
|
||||||
|
* @memberof module:openmct.Selection#
|
||||||
|
* @fires module:openmct.Selection~change
|
||||||
|
*/
|
||||||
|
Selection.prototype.select = function (value) {
|
||||||
|
this.selectedValues.push(value);
|
||||||
|
this.emit('change', this.selectedValues);
|
||||||
|
return this.deselect.bind(this, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a value from the current selection state.
|
||||||
|
* @param {*} value the value to deselect
|
||||||
|
* @method deselect
|
||||||
|
* @memberof module:openmct.Selection#
|
||||||
|
* @fires module:openmct.Selection~change
|
||||||
|
*/
|
||||||
|
Selection.prototype.deselect = function (value) {
|
||||||
|
this.selectedValues = this.selectedValues.filter(function (v) {
|
||||||
|
return v !== value;
|
||||||
|
});
|
||||||
|
this.emit('change', this.selectedValues);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current selection state.
|
||||||
|
* @param {*} value the newly selected value
|
||||||
|
* @returns {Array} all currently-selected objects
|
||||||
|
* @method selected
|
||||||
|
* @memberof module:openmct.Selection#
|
||||||
|
*/
|
||||||
|
Selection.prototype.selected = function () {
|
||||||
|
return this.selectedValues;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deselect all currently-selected objects.
|
||||||
|
* @method clear
|
||||||
|
* @memberof module:openmct.Selection#
|
||||||
|
* @fires module:openmct.Selection~change
|
||||||
|
*/
|
||||||
|
Selection.prototype.clear = function () {
|
||||||
|
this.selectedValues = [];
|
||||||
|
this.emit('change', this.selectedValues);
|
||||||
|
};
|
||||||
|
|
||||||
|
return Selection;
|
||||||
|
});
|
44
src/adapter/actions/ActionDialogDecorator.js
Normal file
44
src/adapter/actions/ActionDialogDecorator.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
define([
|
||||||
|
'../../api/objects/object-utils'
|
||||||
|
], function (objectUtils) {
|
||||||
|
function ActionDialogDecorator(mct, newViews, actionService) {
|
||||||
|
this.actionService = actionService;
|
||||||
|
this.mct = mct;
|
||||||
|
this.definitions = newViews.filter(function (newView) {
|
||||||
|
return newView.region === mct.regions.properties;
|
||||||
|
}).map(function (newView) {
|
||||||
|
return newView.factory;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionDialogDecorator.prototype.getActions = function (context) {
|
||||||
|
var mct = this.mct;
|
||||||
|
var definitions = this.definitions;
|
||||||
|
|
||||||
|
return this.actionService.getActions(context).map(function (action) {
|
||||||
|
if (action.dialogService) {
|
||||||
|
var domainObject = objectUtils.toNewFormat(
|
||||||
|
context.domainObject.getModel(),
|
||||||
|
objectUtils.parseKeyString(context.domainObject.getId())
|
||||||
|
);
|
||||||
|
|
||||||
|
definitions = definitions.filter(function (definition) {
|
||||||
|
return definition.canView(domainObject);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (definitions.length > 0) {
|
||||||
|
action.dialogService = Object.create(action.dialogService);
|
||||||
|
action.dialogService.getUserInput = function (form, value) {
|
||||||
|
return new mct.Dialog(
|
||||||
|
definitions[0].view(context.domainObject),
|
||||||
|
form.title
|
||||||
|
).show();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return action;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return ActionDialogDecorator;
|
||||||
|
});
|
65
src/adapter/bundle.js
Normal file
65
src/adapter/bundle.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
define([
|
||||||
|
'legacyRegistry',
|
||||||
|
'./actions/ActionDialogDecorator',
|
||||||
|
'./directives/MCTView',
|
||||||
|
'./services/Instantiate',
|
||||||
|
'./capabilities/APICapabilityDecorator',
|
||||||
|
'./policies/AdapterCompositionPolicy'
|
||||||
|
], function (
|
||||||
|
legacyRegistry,
|
||||||
|
ActionDialogDecorator,
|
||||||
|
MCTView,
|
||||||
|
Instantiate,
|
||||||
|
APICapabilityDecorator,
|
||||||
|
AdapterCompositionPolicy
|
||||||
|
) {
|
||||||
|
legacyRegistry.register('src/adapter', {
|
||||||
|
"extensions": {
|
||||||
|
"directives": [
|
||||||
|
{
|
||||||
|
key: "mctView",
|
||||||
|
implementation: MCTView,
|
||||||
|
depends: [
|
||||||
|
"newViews[]",
|
||||||
|
"mct"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
services: [
|
||||||
|
{
|
||||||
|
key: "instantiate",
|
||||||
|
priority: "mandatory",
|
||||||
|
implementation: Instantiate,
|
||||||
|
depends: [
|
||||||
|
"capabilityService",
|
||||||
|
"identifierService",
|
||||||
|
"cacheService"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: "decorator",
|
||||||
|
provides: "capabilityService",
|
||||||
|
implementation: APICapabilityDecorator,
|
||||||
|
depends: [
|
||||||
|
"$injector"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "decorator",
|
||||||
|
provides: "actionService",
|
||||||
|
implementation: ActionDialogDecorator,
|
||||||
|
depends: [ "mct", "newViews[]" ]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
policies: [
|
||||||
|
{
|
||||||
|
category: "composition",
|
||||||
|
implementation: AdapterCompositionPolicy,
|
||||||
|
depends: [ "mct" ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
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;
|
||||||
|
});
|
65
src/adapter/directives/MCTView.js
Normal file
65
src/adapter/directives/MCTView.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
define([
|
||||||
|
'angular',
|
||||||
|
'./Region',
|
||||||
|
'../../api/objects/object-utils'
|
||||||
|
], function (
|
||||||
|
angular,
|
||||||
|
Region,
|
||||||
|
objectUtils
|
||||||
|
) {
|
||||||
|
function MCTView(newViews, PublicAPI) {
|
||||||
|
var definitions = {};
|
||||||
|
|
||||||
|
newViews.forEach(function (newView) {
|
||||||
|
definitions[newView.region] = definitions[newView.region] || {};
|
||||||
|
definitions[newView.region][newView.key] = newView.factory;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
restrict: 'E',
|
||||||
|
link: function (scope, element, attrs) {
|
||||||
|
var key, mctObject, regionId, region;
|
||||||
|
|
||||||
|
function maybeShow() {
|
||||||
|
if (!definitions[regionId] || !definitions[regionId][key] || !mctObject) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
region.show(definitions[regionId][key].view(mctObject));
|
||||||
|
}
|
||||||
|
|
||||||
|
function setKey(k) {
|
||||||
|
key = k;
|
||||||
|
maybeShow();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setObject(obj) {
|
||||||
|
mctObject = undefined;
|
||||||
|
PublicAPI.Objects.get(objectUtils.parseKeyString(obj.getId()))
|
||||||
|
.then(function (mobj) {
|
||||||
|
mctObject = mobj;
|
||||||
|
maybeShow();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setRegionId(r) {
|
||||||
|
regionId = r;
|
||||||
|
maybeShow();
|
||||||
|
}
|
||||||
|
|
||||||
|
region = new Region(element[0]);
|
||||||
|
|
||||||
|
scope.$watch('key', setKey);
|
||||||
|
scope.$watch('region', setRegionId);
|
||||||
|
scope.$watch('mctObject', setObject);
|
||||||
|
},
|
||||||
|
scope: {
|
||||||
|
key: "=",
|
||||||
|
region: "=",
|
||||||
|
mctObject: "="
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return MCTView;
|
||||||
|
});
|
23
src/adapter/directives/Region.js
Normal file
23
src/adapter/directives/Region.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
define([], function () {
|
||||||
|
function Region(element) {
|
||||||
|
this.activeView = undefined;
|
||||||
|
this.element = element;
|
||||||
|
}
|
||||||
|
|
||||||
|
Region.prototype.clear = function () {
|
||||||
|
if (this.activeView) {
|
||||||
|
this.activeView.destroy();
|
||||||
|
this.activeView = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Region.prototype.show = function (view) {
|
||||||
|
this.clear();
|
||||||
|
this.activeView = view;
|
||||||
|
if (this.activeView) {
|
||||||
|
this.activeView.show(this.element);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Region;
|
||||||
|
});
|
26
src/adapter/policies/AdapterCompositionPolicy.js
Normal file
26
src/adapter/policies/AdapterCompositionPolicy.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
define([], function () {
|
||||||
|
function AdapterCompositionPolicy(mct) {
|
||||||
|
this.mct = mct;
|
||||||
|
}
|
||||||
|
|
||||||
|
AdapterCompositionPolicy.prototype.allow = function (
|
||||||
|
containerType,
|
||||||
|
childType
|
||||||
|
) {
|
||||||
|
var containerObject = containerType.getInitialModel();
|
||||||
|
var childObject = childType.getInitialModel();
|
||||||
|
|
||||||
|
containerObject.type = containerType.getKey();
|
||||||
|
childObject.type = childType.getKey();
|
||||||
|
|
||||||
|
var composition = this.mct.Composition(containerObject);
|
||||||
|
|
||||||
|
if (composition) {
|
||||||
|
return composition.canContain(childObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
return AdapterCompositionPolicy;
|
||||||
|
});
|
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;
|
||||||
|
}
|
||||||
|
);
|
46
src/adapter/templates/edit-object-replacement.html
Normal file
46
src/adapter/templates/edit-object-replacement.html
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<div class="abs l-flex-col" ng-controller="EditObjectController as EditObjectController">
|
||||||
|
<div mct-before-unload="EditObjectController.getUnloadWarning()"
|
||||||
|
class="holder flex-elem l-flex-row object-browse-bar ">
|
||||||
|
<div class="items-select left flex-elem l-flex-row grows">
|
||||||
|
<mct-representation key="'back-arrow'"
|
||||||
|
mct-object="domainObject"
|
||||||
|
class="flex-elem l-back"></mct-representation>
|
||||||
|
<mct-representation key="'object-header'"
|
||||||
|
mct-object="domainObject"
|
||||||
|
class="l-flex-row flex-elem grows object-header">
|
||||||
|
</mct-representation>
|
||||||
|
</div>
|
||||||
|
<div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed">
|
||||||
|
<mct-representation key="'switcher'"
|
||||||
|
mct-object="domainObject"
|
||||||
|
ng-model="representation">
|
||||||
|
</mct-representation>
|
||||||
|
<!-- Temporarily, on mobile, the action buttons are hidden-->
|
||||||
|
<mct-representation key="'action-group'"
|
||||||
|
mct-object="domainObject"
|
||||||
|
parameters="{ category: 'view-control' }"
|
||||||
|
class="mobile-hide">
|
||||||
|
</mct-representation>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="holder l-flex-col flex-elem grows l-object-wrapper">
|
||||||
|
<div class="holder l-flex-col flex-elem grows l-object-wrapper-inner">
|
||||||
|
<!-- Toolbar and Save/Cancel buttons -->
|
||||||
|
<div class="l-edit-controls flex-elem l-flex-row flex-align-end">
|
||||||
|
<mct-representation key="'adapted-view-TOOLBAR'"
|
||||||
|
mct-object="domainObject"
|
||||||
|
class="flex-elem grows">
|
||||||
|
</mct-representation>
|
||||||
|
<mct-representation key="'edit-action-buttons'"
|
||||||
|
mct-object="domainObject"
|
||||||
|
class='flex-elem conclude-editing'>
|
||||||
|
</mct-representation>
|
||||||
|
</div>
|
||||||
|
<mct-representation key="representation.selected.key"
|
||||||
|
mct-object="representation.selected.key && domainObject"
|
||||||
|
class="abs flex-elem grows object-holder-main scroll"
|
||||||
|
toolbar="toolbar">
|
||||||
|
</mct-representation>
|
||||||
|
</div><!--/ l-object-wrapper-inner -->
|
||||||
|
</div>
|
||||||
|
</div>
|
202
src/api/TimeConductor.js
Normal file
202
src/api/TimeConductor.js
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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(['EventEmitter'], function (EventEmitter) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The public API for setting and querying time conductor state. The
|
||||||
|
* time conductor is the means by which the temporal bounds of a view
|
||||||
|
* are controlled. Time-sensitive views will typically respond to
|
||||||
|
* changes to bounds or other properties of the time conductor and
|
||||||
|
* update the data displayed based on the time conductor state.
|
||||||
|
*
|
||||||
|
* The TimeConductor extends the EventEmitter class. A number of events are
|
||||||
|
* fired when properties of the time conductor change, which are
|
||||||
|
* documented below.
|
||||||
|
* @interface
|
||||||
|
* @memberof module:openmct
|
||||||
|
*/
|
||||||
|
function TimeConductor() {
|
||||||
|
EventEmitter.call(this);
|
||||||
|
|
||||||
|
//The Time System
|
||||||
|
this.system = undefined;
|
||||||
|
//The Time Of Interest
|
||||||
|
this.toi = undefined;
|
||||||
|
|
||||||
|
this.boundsVal = {
|
||||||
|
start: undefined,
|
||||||
|
end: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
//Default to fixed mode
|
||||||
|
this.followMode = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeConductor.prototype = Object.create(EventEmitter.prototype);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the given bounds. This can be used for pre-validation of
|
||||||
|
* bounds, for example by views validating user inputs.
|
||||||
|
* @param bounds The start and end time of the conductor.
|
||||||
|
* @returns {string | true} A validation error, or true if valid
|
||||||
|
* @memberof module:openmct.TimeConductor#
|
||||||
|
* @method validateBounds
|
||||||
|
*/
|
||||||
|
TimeConductor.prototype.validateBounds = function (bounds) {
|
||||||
|
if ((bounds.start === undefined) ||
|
||||||
|
(bounds.end === undefined) ||
|
||||||
|
isNaN(bounds.start) ||
|
||||||
|
isNaN(bounds.end)
|
||||||
|
) {
|
||||||
|
return "Start and end must be specified as integer values";
|
||||||
|
} else if (bounds.start > bounds.end) {
|
||||||
|
return "Specified start date exceeds end bound";
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
function throwOnError(validationResult) {
|
||||||
|
if (validationResult !== true) {
|
||||||
|
throw new Error(validationResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or set the follow mode of the time conductor. In follow mode the
|
||||||
|
* time conductor ticks, regularly updating the bounds from a timing
|
||||||
|
* source appropriate to the selected time system and mode of the time
|
||||||
|
* conductor.
|
||||||
|
* @fires module:openmct.TimeConductor~follow
|
||||||
|
* @param {boolean} followMode
|
||||||
|
* @returns {boolean}
|
||||||
|
* @memberof module:openmct.TimeConductor#
|
||||||
|
* @method follow
|
||||||
|
*/
|
||||||
|
TimeConductor.prototype.follow = function (followMode) {
|
||||||
|
if (arguments.length > 0) {
|
||||||
|
this.followMode = followMode;
|
||||||
|
/**
|
||||||
|
* The TimeConductor has toggled into or out of follow mode.
|
||||||
|
* @event follow
|
||||||
|
* @memberof module:openmct.TimeConductor~
|
||||||
|
* @property {boolean} followMode true if follow mode is
|
||||||
|
* enabled, otherwise false.
|
||||||
|
*/
|
||||||
|
this.emit('follow', this.followMode);
|
||||||
|
}
|
||||||
|
return this.followMode;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} TimeConductorBounds
|
||||||
|
* @property {number} start The start time displayed by the time conductor in ms since epoch. Epoch determined by current time system
|
||||||
|
* @property {number} end The end time displayed by the time conductor in ms since epoch.
|
||||||
|
* @memberof module:openmct.TimeConductor~
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or set the start and end time of the time conductor. Basic validation
|
||||||
|
* of bounds is performed.
|
||||||
|
*
|
||||||
|
* @param {module:openmct.TimeConductorBounds~TimeConductorBounds} newBounds
|
||||||
|
* @throws {Error} Validation error
|
||||||
|
* @fires module:openmct.TimeConductor~bounds
|
||||||
|
* @returns {module:openmct.TimeConductorBounds~TimeConductorBounds}
|
||||||
|
* @memberof module:openmct.TimeConductor#
|
||||||
|
* @method bounds
|
||||||
|
*/
|
||||||
|
TimeConductor.prototype.bounds = function (newBounds) {
|
||||||
|
if (arguments.length > 0) {
|
||||||
|
throwOnError(this.validateBounds(newBounds));
|
||||||
|
this.boundsVal = newBounds;
|
||||||
|
/**
|
||||||
|
* The start time, end time, or both have been updated.
|
||||||
|
* @event bounds
|
||||||
|
* @memberof module:openmct.TimeConductor~
|
||||||
|
* @property {TimeConductorBounds} bounds
|
||||||
|
*/
|
||||||
|
this.emit('bounds', this.boundsVal);
|
||||||
|
}
|
||||||
|
return this.boundsVal;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or set the time system of the TimeConductor. Time systems determine
|
||||||
|
* units, epoch, and other aspects of time representation. When changing
|
||||||
|
* the time system in use, new valid bounds must also be provided.
|
||||||
|
* @param {TimeSystem} newTimeSystem
|
||||||
|
* @param {module:openmct.TimeConductor~TimeConductorBounds} bounds
|
||||||
|
* @fires module:openmct.TimeConductor~timeSystem
|
||||||
|
* @returns {TimeSystem} The currently applied time system
|
||||||
|
* @memberof module:openmct.TimeConductor#
|
||||||
|
* @method timeSystem
|
||||||
|
*/
|
||||||
|
TimeConductor.prototype.timeSystem = function (newTimeSystem, bounds) {
|
||||||
|
if (arguments.length >= 2) {
|
||||||
|
this.system = newTimeSystem;
|
||||||
|
/**
|
||||||
|
* The time system used by the time
|
||||||
|
* conductor has changed. A change in Time System will always be
|
||||||
|
* followed by a bounds event specifying new query bounds.
|
||||||
|
*
|
||||||
|
* @event module:openmct.TimeConductor~timeSystem
|
||||||
|
* @property {TimeSystem} The value of the currently applied
|
||||||
|
* Time System
|
||||||
|
* */
|
||||||
|
this.emit('timeSystem', this.system);
|
||||||
|
// Do something with bounds here. Try and convert between
|
||||||
|
// time systems? Or just set defaults when time system changes?
|
||||||
|
// eg.
|
||||||
|
this.bounds(bounds);
|
||||||
|
} else if (arguments.length === 1) {
|
||||||
|
throw new Error('Must set bounds when changing time system');
|
||||||
|
}
|
||||||
|
return this.system;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or set the Time of Interest. The Time of Interest is the temporal
|
||||||
|
* focus of the current view. It can be manipulated by the user from the
|
||||||
|
* time conductor or from other views.
|
||||||
|
* @fires module:openmct.TimeConductor~timeOfInterest
|
||||||
|
* @param newTOI
|
||||||
|
* @returns {number} the current time of interest
|
||||||
|
* @memberof module:openmct.TimeConductor#
|
||||||
|
* @method timeOfInterest
|
||||||
|
*/
|
||||||
|
TimeConductor.prototype.timeOfInterest = function (newTOI) {
|
||||||
|
if (arguments.length > 0) {
|
||||||
|
this.toi = newTOI;
|
||||||
|
/**
|
||||||
|
* The Time of Interest has moved.
|
||||||
|
* @event timeOfInterest
|
||||||
|
* @memberof module:openmct.TimeConductor~
|
||||||
|
* @property {number} Current time of interest
|
||||||
|
*/
|
||||||
|
this.emit('timeOfInterest', this.toi);
|
||||||
|
}
|
||||||
|
return this.toi;
|
||||||
|
};
|
||||||
|
|
||||||
|
return TimeConductor;
|
||||||
|
});
|
110
src/api/TimeConductorSpec.js
Normal file
110
src/api/TimeConductorSpec.js
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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(['./TimeConductor'], function (TimeConductor) {
|
||||||
|
describe("The Time Conductor", function () {
|
||||||
|
var tc,
|
||||||
|
timeSystem,
|
||||||
|
bounds,
|
||||||
|
eventListener,
|
||||||
|
toi,
|
||||||
|
follow;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
tc = new TimeConductor();
|
||||||
|
timeSystem = {};
|
||||||
|
bounds = {start: 0, end: 0};
|
||||||
|
eventListener = jasmine.createSpy("eventListener");
|
||||||
|
toi = 111;
|
||||||
|
follow = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Supports setting and querying of time of interest and and follow mode", function () {
|
||||||
|
expect(tc.timeOfInterest()).not.toBe(toi);
|
||||||
|
tc.timeOfInterest(toi);
|
||||||
|
expect(tc.timeOfInterest()).toBe(toi);
|
||||||
|
|
||||||
|
expect(tc.follow()).not.toBe(follow);
|
||||||
|
tc.follow(follow);
|
||||||
|
expect(tc.follow()).toBe(follow);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Allows setting of valid bounds", function () {
|
||||||
|
bounds = {start: 0, end: 1};
|
||||||
|
expect(tc.bounds()).not.toBe(bounds);
|
||||||
|
expect(tc.bounds.bind(tc, bounds)).not.toThrow();
|
||||||
|
expect(tc.bounds()).toBe(bounds);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Disallows setting of invalid bounds", function () {
|
||||||
|
bounds = {start: 1, end: 0};
|
||||||
|
expect(tc.bounds()).not.toBe(bounds);
|
||||||
|
expect(tc.bounds.bind(tc, bounds)).toThrow();
|
||||||
|
expect(tc.bounds()).not.toBe(bounds);
|
||||||
|
|
||||||
|
bounds = {start: 1};
|
||||||
|
expect(tc.bounds()).not.toBe(bounds);
|
||||||
|
expect(tc.bounds.bind(tc, bounds)).toThrow();
|
||||||
|
expect(tc.bounds()).not.toBe(bounds);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Allows setting of time system with bounds", function () {
|
||||||
|
expect(tc.timeSystem()).not.toBe(timeSystem);
|
||||||
|
expect(tc.timeSystem.bind(tc, timeSystem, bounds)).not.toThrow();
|
||||||
|
expect(tc.timeSystem()).toBe(timeSystem);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Disallows setting of time system without bounds", function () {
|
||||||
|
expect(tc.timeSystem()).not.toBe(timeSystem);
|
||||||
|
expect(tc.timeSystem.bind(tc, timeSystem)).toThrow();
|
||||||
|
expect(tc.timeSystem()).not.toBe(timeSystem);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Emits an event when time system changes", function () {
|
||||||
|
expect(eventListener).not.toHaveBeenCalled();
|
||||||
|
tc.on("timeSystem", eventListener);
|
||||||
|
tc.timeSystem(timeSystem, bounds);
|
||||||
|
expect(eventListener).toHaveBeenCalledWith(timeSystem);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Emits an event when time of interest changes", function () {
|
||||||
|
expect(eventListener).not.toHaveBeenCalled();
|
||||||
|
tc.on("timeOfInterest", eventListener);
|
||||||
|
tc.timeOfInterest(toi);
|
||||||
|
expect(eventListener).toHaveBeenCalledWith(toi);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Emits an event when bounds change", function () {
|
||||||
|
expect(eventListener).not.toHaveBeenCalled();
|
||||||
|
tc.on("bounds", eventListener);
|
||||||
|
tc.bounds(bounds);
|
||||||
|
expect(eventListener).toHaveBeenCalledWith(bounds);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Emits an event when follow mode changes", function () {
|
||||||
|
expect(eventListener).not.toHaveBeenCalled();
|
||||||
|
tc.on("follow", eventListener);
|
||||||
|
tc.follow(follow);
|
||||||
|
expect(eventListener).toHaveBeenCalledWith(follow);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
60
src/api/Type.js
Normal file
60
src/api/Type.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
define(function () {
|
||||||
|
/**
|
||||||
|
* @typedef TypeDefinition
|
||||||
|
* @memberof module:openmct.Type~
|
||||||
|
* @property {Metadata} metadata displayable metadata about this type
|
||||||
|
* @property {function (object)} [initialize] a function which initializes
|
||||||
|
* the model for new domain objects of this type
|
||||||
|
* @property {boolean} [creatable] true if users should be allowed to
|
||||||
|
* create this type (default: false)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Type describes a kind of domain object that may appear or be
|
||||||
|
* created within Open MCT.
|
||||||
|
*
|
||||||
|
* @param {module:opemct.Type~TypeDefinition} definition
|
||||||
|
* @class Type
|
||||||
|
* @memberof module:openmct
|
||||||
|
*/
|
||||||
|
function Type(definition) {
|
||||||
|
this.definition = definition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a domain object is an instance of this type.
|
||||||
|
* @param domainObject
|
||||||
|
* @returns {boolean} true if the domain object is of this type
|
||||||
|
* @memberof module:openmct.Type#
|
||||||
|
* @method check
|
||||||
|
*/
|
||||||
|
Type.prototype.check = function (domainObject) {
|
||||||
|
// Depends on assignment from MCT.
|
||||||
|
return domainObject.type === this.key;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a definition for this type that can be registered using the
|
||||||
|
* legacy bundle format.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
Type.prototype.toLegacyDefinition = function () {
|
||||||
|
var def = {};
|
||||||
|
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 = {};
|
||||||
|
this.definition.initialize(def.model);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.definition.creatable) {
|
||||||
|
def.features = ['creation'];
|
||||||
|
}
|
||||||
|
return def;
|
||||||
|
};
|
||||||
|
|
||||||
|
return Type;
|
||||||
|
});
|
91
src/api/View.js
Normal file
91
src/api/View.js
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
define([], function () {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A View is used to provide displayable content, and to react to
|
||||||
|
* associated life cycle events.
|
||||||
|
*
|
||||||
|
* @interface
|
||||||
|
* @memberof module:openmct
|
||||||
|
*/
|
||||||
|
function View() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populate the supplied DOM element with the contents of this view.
|
||||||
|
*
|
||||||
|
* View implementations should use this method to attach any
|
||||||
|
* listeners or acquire other resources that are necessary to keep
|
||||||
|
* the contents of this view up-to-date.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} container the DOM element to populate
|
||||||
|
* @method show
|
||||||
|
* @memberof module:openmct.View#
|
||||||
|
*/
|
||||||
|
View.prototype.show = function (container) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release any resources associated with this view.
|
||||||
|
*
|
||||||
|
* View implementations should use this method to detach any
|
||||||
|
* listeners or release other resources that are no longer necessary
|
||||||
|
* once a view is no longer used.
|
||||||
|
*
|
||||||
|
* @method destroy
|
||||||
|
* @memberof module:openmct.View#
|
||||||
|
*/
|
||||||
|
View.prototype.destroy = function () {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exposes types of views in Open MCT.
|
||||||
|
*
|
||||||
|
* @interface ViewProvider
|
||||||
|
* @memberof module:openmct
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this provider can supply views for a domain object.
|
||||||
|
* @method canView
|
||||||
|
* @memberof module:openmct.ViewProvider#
|
||||||
|
* @param {module:openmct.DomainObject} domainObject the domain object
|
||||||
|
* to be viewed
|
||||||
|
* @returns {boolean} true if this domain object can be viewed using
|
||||||
|
* this provider
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide a view of this domain object.
|
||||||
|
* @method view
|
||||||
|
* @memberof module:openmct.ViewProvider#
|
||||||
|
* @param {module:openmct.DomainObject} domainObject the domain object
|
||||||
|
* to be viewed
|
||||||
|
* @returns {module:openmct.View} a view of this domain object
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get metadata associated with this view provider. This may be used
|
||||||
|
* to populate the user interface with options associated with this
|
||||||
|
* view provider.
|
||||||
|
*
|
||||||
|
* @method metadata
|
||||||
|
* @memberof module:openmct.ViewProvider#
|
||||||
|
* @returns {module:openmct.ViewProvider~ViewMetadata} view metadata
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef ViewMetadata
|
||||||
|
* @memberof module:openmct.ViewProvider~
|
||||||
|
* @property {string} name the human-readable name of this view
|
||||||
|
* @property {string} key a machine-readable name for this view
|
||||||
|
* @property {string} [description] a longer-form description (typically
|
||||||
|
* a single sentence or short paragraph) of this kind of view
|
||||||
|
* @property {string} cssclass the CSS class to apply to labels for this
|
||||||
|
* view (to add icons, for instance)
|
||||||
|
*/
|
||||||
|
|
||||||
|
return View;
|
||||||
|
});
|
44
src/api/ViewDefinition.js
Normal file
44
src/api/ViewDefinition.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
define(function () {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a kind of view.
|
||||||
|
* @interface
|
||||||
|
*/
|
||||||
|
function ViewDefinition() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get metadata about this view, as may be used in the user interface
|
||||||
|
* to present options for this view.
|
||||||
|
* @param {*} object the object to be shown in this view
|
||||||
|
* @returns {mct.ViewMetadata} metadata about this view
|
||||||
|
*/
|
||||||
|
ViewDefinition.prototype.metadata = function (object) {
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate a new view of this object. Callers of this method are
|
||||||
|
* responsible for calling `canView` before instantiating views in this
|
||||||
|
* manner.
|
||||||
|
*
|
||||||
|
* @param {*} object the object to be shown in this view
|
||||||
|
* @returns {mct.View} a view of this object
|
||||||
|
*/
|
||||||
|
ViewDefinition.prototype.view = function (object) {
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this view is capable of showing this object. Users of
|
||||||
|
* views should use this method before calling `show`.
|
||||||
|
*
|
||||||
|
* Subclasses should override this method to control the applicability
|
||||||
|
* of this view to other objects.
|
||||||
|
*
|
||||||
|
* @param {*} object the object to be shown in this view
|
||||||
|
* @returns {boolean} true if this view is applicable to this object
|
||||||
|
*/
|
||||||
|
ViewDefinition.prototype.canView = function (object) {
|
||||||
|
};
|
||||||
|
|
||||||
|
return ViewDefinition;
|
||||||
|
});
|
23
src/api/api.js
Normal file
23
src/api/api.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
define([
|
||||||
|
'./Type',
|
||||||
|
'./TimeConductor',
|
||||||
|
'./View',
|
||||||
|
'./objects/ObjectAPI',
|
||||||
|
'./composition/CompositionAPI',
|
||||||
|
'./ui/Dialog'
|
||||||
|
], function (
|
||||||
|
Type,
|
||||||
|
TimeConductor,
|
||||||
|
View,
|
||||||
|
ObjectAPI,
|
||||||
|
CompositionAPI,
|
||||||
|
Dialog
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
Type: Type,
|
||||||
|
View: View,
|
||||||
|
Objects: ObjectAPI,
|
||||||
|
Composition: CompositionAPI,
|
||||||
|
Dialog: Dialog
|
||||||
|
};
|
||||||
|
});
|
48
src/api/composition/CompositionAPI.js
Normal file
48
src/api/composition/CompositionAPI.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the composition (if any) of this domain object. The
|
||||||
|
* composition of a domain object is the list of other domain objects
|
||||||
|
* it "contains" (for instance, that should be displayed beneath it
|
||||||
|
* in the tree.)
|
||||||
|
* @method Composition
|
||||||
|
* @returns {module:openmct.CompositionCollection}
|
||||||
|
* @memberof module:openmct.MCT#
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
|
||||||
|
});
|
206
src/api/composition/CompositionCollection.js
Normal file
206
src/api/composition/CompositionCollection.js
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
define([
|
||||||
|
'EventEmitter',
|
||||||
|
'lodash',
|
||||||
|
'../objects/object-utils'
|
||||||
|
], function (
|
||||||
|
EventEmitter,
|
||||||
|
_,
|
||||||
|
objectUtils
|
||||||
|
) {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A CompositionCollection represents the list of domain objects contained
|
||||||
|
* by another domain object. It provides methods for loading this
|
||||||
|
* list asynchronously, and for modifying this list.
|
||||||
|
*
|
||||||
|
* @class CompositionCollection
|
||||||
|
* @param {module:openmct.DomainObject} domainObject the domain object
|
||||||
|
* whose composition will be contained
|
||||||
|
* @param {module:openmct.CompositionProvider} provider the provider
|
||||||
|
* to use to retrieve other domain objects
|
||||||
|
* @memberof module:openmct
|
||||||
|
* @augments EventEmitter
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the index of a domain object within this composition. If the
|
||||||
|
* domain object is not contained here, -1 will be returned.
|
||||||
|
*
|
||||||
|
* A call to [load]{@link module:openmct.CompositionCollection#load}
|
||||||
|
* must have resolved before using this method.
|
||||||
|
*
|
||||||
|
* @param {module:openmct.DomainObject} child the domain object for which
|
||||||
|
* an index should be retrieved
|
||||||
|
* @returns {number} the index of that domain object
|
||||||
|
* @memberof module:openmct.CompositionCollection#
|
||||||
|
* @name indexOf
|
||||||
|
*/
|
||||||
|
CompositionCollection.prototype.indexOf = function (child) {
|
||||||
|
return _.findIndex(this._children, function (other) {
|
||||||
|
return objectUtils.equals(child, other);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the index of a domain object within this composition.
|
||||||
|
*
|
||||||
|
* A call to [load]{@link module:openmct.CompositionCollection#load}
|
||||||
|
* must have resolved before using this method.
|
||||||
|
*
|
||||||
|
* @param {module:openmct.DomainObject} child the domain object for which
|
||||||
|
* containment should be checked
|
||||||
|
* @returns {boolean} true if the domain object is contained here
|
||||||
|
* @memberof module:openmct.CompositionCollection#
|
||||||
|
* @name contains
|
||||||
|
*/
|
||||||
|
CompositionCollection.prototype.contains = function (child) {
|
||||||
|
return this.indexOf(child) !== -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a domain object to this composition.
|
||||||
|
*
|
||||||
|
* A call to [load]{@link module:openmct.CompositionCollection#load}
|
||||||
|
* must have resolved before using this method.
|
||||||
|
*
|
||||||
|
* @param {module:openmct.DomainObject} child the domain object to add
|
||||||
|
* @param {boolean} skipMutate true if the underlying provider should
|
||||||
|
* not be updated
|
||||||
|
* @memberof module:openmct.CompositionCollection#
|
||||||
|
* @name add
|
||||||
|
*/
|
||||||
|
CompositionCollection.prototype.add = function (child, skipMutate) {
|
||||||
|
if (!this._children) {
|
||||||
|
throw new Error("Must load composition before you can add!");
|
||||||
|
}
|
||||||
|
if (!this.canContain(child)) {
|
||||||
|
throw new Error("This object cannot contain that object.");
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the domain objects in this composition.
|
||||||
|
*
|
||||||
|
* @returns {Promise.<Array.<module:openmct.DomainObject>>} a promise for
|
||||||
|
* the domain objects in this composition
|
||||||
|
* @memberof {module:openmct.CompositionCollection#}
|
||||||
|
* @name load
|
||||||
|
*/
|
||||||
|
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));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a domain object from this composition.
|
||||||
|
*
|
||||||
|
* A call to [load]{@link module:openmct.CompositionCollection#load}
|
||||||
|
* must have resolved before using this method.
|
||||||
|
*
|
||||||
|
* @param {module:openmct.DomainObject} child the domain object to remove
|
||||||
|
* @param {boolean} skipMutate true if the underlying provider should
|
||||||
|
* not be updated
|
||||||
|
* @memberof module:openmct.CompositionCollection#
|
||||||
|
* @name remove
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this composition can contain this domain object.
|
||||||
|
* @name canContain
|
||||||
|
* @memberof module:openmct.CompositionCollection
|
||||||
|
* @param {module:openmct.DomainObject} the domain object to contain
|
||||||
|
* @returns {boolean} true if containment is allowed
|
||||||
|
*/
|
||||||
|
CompositionCollection.prototype.canContain = function (domainObject) {
|
||||||
|
return this.provider.canContain(this.domainObject, domainObject);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop using this composition collection. This will release any resources
|
||||||
|
* associated with this collection.
|
||||||
|
* @name destroy
|
||||||
|
* @memberof module:openmct.CompositionCollection
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
});
|
134
src/api/composition/DefaultCompositionProvider.js
Normal file
134
src/api/composition/DefaultCompositionProvider.js
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
define([
|
||||||
|
'lodash',
|
||||||
|
'EventEmitter',
|
||||||
|
'../objects/ObjectAPI',
|
||||||
|
'../objects/object-utils'
|
||||||
|
], function (
|
||||||
|
_,
|
||||||
|
EventEmitter,
|
||||||
|
ObjectAPI,
|
||||||
|
objectUtils
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* A CompositionProvider provides the underlying implementation of
|
||||||
|
* composition-related behavior for certain types of domain object.
|
||||||
|
*
|
||||||
|
* @interface CompositionProvider
|
||||||
|
* @memberof module:openmct
|
||||||
|
* @augments EventEmitter
|
||||||
|
*/
|
||||||
|
|
||||||
|
function makeEventName(domainObject, event) {
|
||||||
|
return event + ':' + objectUtils.makeKeyString(domainObject.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DefaultCompositionProvider() {
|
||||||
|
EventEmitter.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultCompositionProvider.prototype =
|
||||||
|
Object.create(EventEmitter.prototype);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this provider should be used to load composition for a
|
||||||
|
* particular domain object.
|
||||||
|
* @param {module:openmct.DomainObject} domainObject the domain object
|
||||||
|
* to check
|
||||||
|
* @returns {boolean} true if this provider can provide
|
||||||
|
* composition for a given domain object
|
||||||
|
* @memberof {module:openmct.CompositionProvider#}
|
||||||
|
* @name appliesTo
|
||||||
|
*/
|
||||||
|
DefaultCompositionProvider.prototype.appliesTo = function (domainObject) {
|
||||||
|
return !!domainObject.composition;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load any domain objects contained in the composition of this domain
|
||||||
|
* object.
|
||||||
|
* @param {module:openmct.DomainObjcet} domainObject the domain object
|
||||||
|
* for which to load composition
|
||||||
|
* @returns {Promise.<Array.<module:openmct.DomainObject>>} a promise for
|
||||||
|
* the domain objects in this composition
|
||||||
|
* @memberof {module:openmct.CompositionProvider#}
|
||||||
|
* @name load
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if one domain object can contain another.
|
||||||
|
* @param {module:openmct.DomainObject} domainObject the domain object
|
||||||
|
* which will act as the container
|
||||||
|
* @param {module:openmct.DomainObject} child the domain object to be
|
||||||
|
* contained
|
||||||
|
* @returns {boolean} true if this is allowed
|
||||||
|
*/
|
||||||
|
DefaultCompositionProvider.prototype.canContain = function (domainObject, child) {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a domain object from another domain object's composition.
|
||||||
|
*
|
||||||
|
* @param {module:openmct.DomainObject} domainObject the domain object
|
||||||
|
* which should have its composition modified
|
||||||
|
* @param {module:openmct.DomainObject} child the domain object to remove
|
||||||
|
* @memberof module:openmct.CompositionProvider#
|
||||||
|
* @name remove
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a domain object to another domain object's composition.
|
||||||
|
*
|
||||||
|
* @param {module:openmct.DomainObject} domainObject the domain object
|
||||||
|
* which should have its composition modified
|
||||||
|
* @param {module:openmct.DomainObject} child the domain object to add
|
||||||
|
* @memberof module:openmct.CompositionProvider#
|
||||||
|
* @name add
|
||||||
|
*/
|
||||||
|
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)`
|
109
src/api/objects/LegacyObjectAPIInterceptor.js
Normal file
109
src/api/objects/LegacyObjectAPIInterceptor.js
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
define([
|
||||||
|
'./object-utils',
|
||||||
|
'./ObjectAPI',
|
||||||
|
'./objectEventEmitter'
|
||||||
|
], function (
|
||||||
|
utils,
|
||||||
|
ObjectAPI,
|
||||||
|
objectEventEmitter
|
||||||
|
) {
|
||||||
|
function ObjectServiceProvider(objectService, instantiate, topic) {
|
||||||
|
this.objectService = objectService;
|
||||||
|
this.instantiate = instantiate;
|
||||||
|
|
||||||
|
this.generalTopic = topic('mutation');
|
||||||
|
this.bridgeEventBuses();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bridges old and new style mutation events to provide compatibility between the two APIs
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
ObjectServiceProvider.prototype.bridgeEventBuses = function () {
|
||||||
|
var removeGeneralTopicListener;
|
||||||
|
|
||||||
|
var handleMutation = function (newStyleObject) {
|
||||||
|
var keyString = utils.makeKeyString(newStyleObject.key);
|
||||||
|
var oldStyleObject = this.instantiate(utils.toOldFormat(newStyleObject), keyString);
|
||||||
|
|
||||||
|
// Don't trigger self
|
||||||
|
removeGeneralTopicListener();
|
||||||
|
|
||||||
|
oldStyleObject.getCapability('mutation').mutate(function () {
|
||||||
|
return utils.toOldFormat(newStyleObject);
|
||||||
|
});
|
||||||
|
|
||||||
|
removeGeneralTopicListener = this.generalTopic.listen(handleLegacyMutation);
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
var handleLegacyMutation = function (legacyObject){
|
||||||
|
var newStyleObject = utils.toNewFormat(legacyObject.getModel(), legacyObject.getId());
|
||||||
|
|
||||||
|
//Don't trigger self
|
||||||
|
objectEventEmitter.off('mutation', handleMutation);
|
||||||
|
objectEventEmitter.emit(newStyleObject.key.identifier + ":*", newStyleObject);
|
||||||
|
objectEventEmitter.on('mutation', handleMutation);
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
objectEventEmitter.on('mutation', handleMutation);
|
||||||
|
removeGeneralTopicListener = this.generalTopic.listen(handleLegacyMutation);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 = 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, topic, 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, topic)
|
||||||
|
);
|
||||||
|
|
||||||
|
ROOTS.forEach(function (r) {
|
||||||
|
ObjectAPI.addRoot(utils.parseKeyString(r.id));
|
||||||
|
});
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
return LegacyObjectAPIInterceptor;
|
||||||
|
});
|
69
src/api/objects/MutableObject.js
Normal file
69
src/api/objects/MutableObject.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
define([
|
||||||
|
'lodash',
|
||||||
|
'./objectEventEmitter'
|
||||||
|
], function (
|
||||||
|
_,
|
||||||
|
objectEventEmitter
|
||||||
|
) {
|
||||||
|
var ANY_OBJECT_EVENT = "mutation";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The MutableObject wraps a DomainObject and provides getters and
|
||||||
|
* setters for
|
||||||
|
* @param eventEmitter
|
||||||
|
* @param object
|
||||||
|
* @interface MutableObject
|
||||||
|
* @memberof module:openmct
|
||||||
|
*/
|
||||||
|
function MutableObject(object) {
|
||||||
|
this.object = object;
|
||||||
|
this.unlisteners = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function qualifiedEventName(object, eventName) {
|
||||||
|
return [object.key.identifier, eventName].join(':');
|
||||||
|
}
|
||||||
|
|
||||||
|
MutableObject.prototype.stopListening = function () {
|
||||||
|
this.unlisteners.forEach(function (unlisten) {
|
||||||
|
unlisten();
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observe changes to this domain object.
|
||||||
|
* @param {string} path the property to observe
|
||||||
|
* @param {Function} callback a callback to invoke when new values for
|
||||||
|
* this property are observed
|
||||||
|
* @method on
|
||||||
|
* @memberof module:openmct.MutableObject#
|
||||||
|
*/
|
||||||
|
MutableObject.prototype.on = function(path, callback) {
|
||||||
|
var fullPath = qualifiedEventName(this.object, path);
|
||||||
|
objectEventEmitter.on(fullPath, callback);
|
||||||
|
this.unlisteners.push(objectEventEmitter.off.bind(objectEventEmitter, fullPath, callback));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modify this domain object.
|
||||||
|
* @param {string} path the property to modify
|
||||||
|
* @param {*} value the new value for this property
|
||||||
|
* @method set
|
||||||
|
* @memberof module:openmct.MutableObject#
|
||||||
|
*/
|
||||||
|
MutableObject.prototype.set = function (path, value) {
|
||||||
|
|
||||||
|
_.set(this.object, path, value);
|
||||||
|
_.set(this.object, 'modified', Date.now());
|
||||||
|
|
||||||
|
//Emit event specific to property
|
||||||
|
objectEventEmitter.emit(qualifiedEventName(this.object, path), value);
|
||||||
|
//Emit wildcare event
|
||||||
|
objectEventEmitter.emit(qualifiedEventName(this.object, '*'), this.object);
|
||||||
|
|
||||||
|
//Emit a general "any object" event
|
||||||
|
objectEventEmitter.emit(ANY_OBJECT_EVENT, this.object);
|
||||||
|
};
|
||||||
|
|
||||||
|
return MutableObject;
|
||||||
|
});
|
110
src/api/objects/MutableObjectSpec.js
Normal file
110
src/api/objects/MutableObjectSpec.js
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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(['./MutableObject'], function (MutableObject) {
|
||||||
|
describe('Mutable Domain Objects', function () {
|
||||||
|
var domainObject,
|
||||||
|
mutableObject,
|
||||||
|
eventEmitter,
|
||||||
|
arrayProperty,
|
||||||
|
objectProperty,
|
||||||
|
identifier;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
identifier = 'objectId';
|
||||||
|
arrayProperty = [
|
||||||
|
'First array element',
|
||||||
|
'Second array element',
|
||||||
|
'Third array element'
|
||||||
|
];
|
||||||
|
objectProperty = {
|
||||||
|
prop1: 'val1',
|
||||||
|
prop2: 'val2',
|
||||||
|
prop3: {
|
||||||
|
propA: 'valA',
|
||||||
|
propB: 'valB',
|
||||||
|
propC: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
domainObject = {
|
||||||
|
key: {
|
||||||
|
identifier: identifier
|
||||||
|
},
|
||||||
|
stringProperty: 'stringValue',
|
||||||
|
objectProperty: objectProperty,
|
||||||
|
arrayProperty: arrayProperty
|
||||||
|
};
|
||||||
|
eventEmitter = jasmine.createSpyObj('eventEmitter', [
|
||||||
|
'emit'
|
||||||
|
]);
|
||||||
|
mutableObject = new MutableObject(eventEmitter, domainObject);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Supports getting and setting of object properties', function () {
|
||||||
|
expect(domainObject.stringProperty).toEqual('stringValue');
|
||||||
|
mutableObject.set('stringProperty', 'updated');
|
||||||
|
expect(domainObject.stringProperty).toEqual('updated');
|
||||||
|
|
||||||
|
var newArrayProperty = [];
|
||||||
|
expect(domainObject.arrayProperty).toEqual(arrayProperty);
|
||||||
|
mutableObject.set('arrayProperty', newArrayProperty);
|
||||||
|
expect(domainObject.arrayProperty).toEqual(newArrayProperty);
|
||||||
|
|
||||||
|
var newObjectProperty = [];
|
||||||
|
expect(domainObject.objectProperty).toEqual(objectProperty);
|
||||||
|
mutableObject.set('objectProperty', newObjectProperty);
|
||||||
|
expect(domainObject.objectProperty).toEqual(newObjectProperty);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Supports getting and setting of nested properties', function () {
|
||||||
|
expect(domainObject.objectProperty).toEqual(objectProperty);
|
||||||
|
expect(domainObject.objectProperty.prop1).toEqual(objectProperty.prop1);
|
||||||
|
expect(domainObject.objectProperty.prop3.propA).toEqual(objectProperty.prop3.propA);
|
||||||
|
|
||||||
|
mutableObject.set('objectProperty.prop1', 'new-prop-1');
|
||||||
|
expect(domainObject.objectProperty.prop1).toEqual('new-prop-1');
|
||||||
|
|
||||||
|
mutableObject.set('objectProperty.prop3.propA', 'new-prop-A');
|
||||||
|
expect(domainObject.objectProperty.prop3.propA).toEqual('new-prop-A');
|
||||||
|
|
||||||
|
mutableObject.set('arrayProperty.1', 'New Second Array Element');
|
||||||
|
expect(arrayProperty[1]).toEqual('New Second Array Element');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Fires events when properties change', function () {
|
||||||
|
var newString = 'updated'
|
||||||
|
mutableObject.set('stringProperty', newString);
|
||||||
|
expect(eventEmitter.emit).toHaveBeenCalledWith([identifier, 'stringProperty'].join(':'), newString);
|
||||||
|
|
||||||
|
var newArray = [];
|
||||||
|
mutableObject.set('arrayProperty', newArray);
|
||||||
|
expect(eventEmitter.emit).toHaveBeenCalledWith([identifier, 'arrayProperty'].join(':'), newArray);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Fires wildcard event when any property changes', function () {
|
||||||
|
var newString = 'updated'
|
||||||
|
mutableObject.set('objectProperty.prop3.propA', newString);
|
||||||
|
expect(eventEmitter.emit).toHaveBeenCalledWith([identifier, '*'].join(':'), domainObject);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
177
src/api/objects/ObjectAPI.js
Normal file
177
src/api/objects/ObjectAPI.js
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
define([
|
||||||
|
'lodash',
|
||||||
|
'./object-utils',
|
||||||
|
'./MutableObject'
|
||||||
|
], function (
|
||||||
|
_,
|
||||||
|
utils,
|
||||||
|
MutableObject
|
||||||
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
Object API. Intercepts the existing object API while also exposing
|
||||||
|
A new Object API.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utilities for loading, saving, and manipulating domain objects.
|
||||||
|
* @interface ObjectAPI
|
||||||
|
* @memberof module:openmct
|
||||||
|
* @implements {module:openmct.ObjectProvider}
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a new object provider for a particular namespace.
|
||||||
|
*
|
||||||
|
* @param {string} namespace the namespace for which to provide objects
|
||||||
|
* @param {module:openmct.ObjectProvider} provider the provider which
|
||||||
|
* will handle loading domain objects from this namespace
|
||||||
|
* @memberof {module:openmct.ObjectAPI#}
|
||||||
|
* @name addProvider
|
||||||
|
*/
|
||||||
|
Objects.addProvider = function (namespace, provider) {
|
||||||
|
PROVIDER_REGISTRY[namespace] = provider;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the ability to read, write, and delete domain objects.
|
||||||
|
*
|
||||||
|
* When registering a new object provider, all methods on this interface
|
||||||
|
* are optional.
|
||||||
|
*
|
||||||
|
* @interface ObjectProvider
|
||||||
|
* @memberof module:openmct
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save this domain object in its current state.
|
||||||
|
*
|
||||||
|
* @method save
|
||||||
|
* @memberof module:openmct.ObjectProvider#
|
||||||
|
* @param {module:openmct.DomainObject} domainObject the domain object to
|
||||||
|
* save
|
||||||
|
* @returns {Promise} a promise which will resolve when the domain object
|
||||||
|
* has been saved, or be rejected if it cannot be saved
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete this domain object.
|
||||||
|
*
|
||||||
|
* @method delete
|
||||||
|
* @memberof module:openmct.ObjectProvider#
|
||||||
|
* @param {module:openmct.DomainObject} domainObject the domain object to
|
||||||
|
* delete
|
||||||
|
* @returns {Promise} a promise which will resolve when the domain object
|
||||||
|
* has been deleted, or be rejected if it cannot be deleted
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a domain object.
|
||||||
|
*
|
||||||
|
* @method get
|
||||||
|
* @memberof module:openmct.ObjectProvider#
|
||||||
|
* @param {string} key the key for the domain object to load
|
||||||
|
* @returns {Promise} a promise which will resolve when the domain object
|
||||||
|
* has been saved, or be rejected if it cannot be saved
|
||||||
|
*/
|
||||||
|
|
||||||
|
[
|
||||||
|
'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);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a root-level object.
|
||||||
|
* @param {module:openmct.Identifier} id the identifier of the root-level
|
||||||
|
* object to add.
|
||||||
|
* @method addRoot
|
||||||
|
* @memberof module:openmct.ObjectAPI#
|
||||||
|
*/
|
||||||
|
Objects.addRoot = function (key) {
|
||||||
|
ROOT_REGISTRY.unshift(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a root-level object.
|
||||||
|
* @param {module:openmct.Identifier} id the identifier of the root-level
|
||||||
|
* object to remove.
|
||||||
|
* @method removeRoot
|
||||||
|
* @memberof module:openmct.ObjectAPI#
|
||||||
|
*/
|
||||||
|
Objects.removeRoot = function (key) {
|
||||||
|
ROOT_REGISTRY = ROOT_REGISTRY.filter(function (k) {
|
||||||
|
return (
|
||||||
|
k.identifier !== key.identifier ||
|
||||||
|
k.namespace !== key.namespace
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an interface for observing and mutating this domain object.
|
||||||
|
* @param {module:openmct.DomainObject} object the object to mutate
|
||||||
|
* @returns {module:openmct.MutableObject} an interface for mutating
|
||||||
|
* and observing this domain object
|
||||||
|
* @method getMutable
|
||||||
|
* @memberof module:openmct.ObjectAPI#
|
||||||
|
*/
|
||||||
|
Objects.getMutable = function (object) {
|
||||||
|
return new MutableObject(object);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uniquely identifies a domain object.
|
||||||
|
*
|
||||||
|
* @typedef Identifier
|
||||||
|
* @memberof module:openmct
|
||||||
|
* @property {string} namespace the namespace to/from which this domain
|
||||||
|
* object should be loaded/stored.
|
||||||
|
* @property {string} key a unique identifier for the domain object
|
||||||
|
* within that namespace
|
||||||
|
*/
|
||||||
|
|
||||||
|
return Objects;
|
||||||
|
});
|
54
src/api/objects/README.md
Normal file
54
src/api/objects/README.md
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
## Roots
|
||||||
|
|
||||||
|
"Roots" are objects that Open MCT will load at run time and form the basic entry point for users. You can register new root objects by calling the
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
# defining providers
|
||||||
|
|
||||||
|
# Using the object API
|
||||||
|
|
||||||
|
The object API provides methods for getting, saving, and deleting objects. Plugin developers may not frequently need to interact with this API, as they should receive instances of the objects as needed.
|
||||||
|
|
||||||
|
* 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
|
||||||
|
|
||||||
|
See the tutorial in [object-provider.html](object-provider.html).
|
||||||
|
|
||||||
|
### Making a custom provider:
|
||||||
|
|
||||||
|
All methods on the provider interface are optional.
|
||||||
|
|
||||||
|
* `provider.get(key)` -> returns 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.
|
||||||
|
|
||||||
|
|
50
src/api/objects/bundle.js
Normal file
50
src/api/objects/bundle.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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",
|
||||||
|
"topic"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
88
src/api/objects/object-utils.js
Normal file
88
src/api/objects/object-utils.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
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 < key.length; i++) {
|
||||||
|
if (escaped) {
|
||||||
|
escaped = false;
|
||||||
|
} else {
|
||||||
|
if (key[i] === "\\") {
|
||||||
|
escaped = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (key[i] === ":") {
|
||||||
|
// namespace = key.slice(0, i);
|
||||||
|
identifier = key.slice(i + 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
namespace += key[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === namespace) {
|
||||||
|
namespace = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
model = JSON.parse(JSON.stringify(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 = JSON.parse(JSON.stringify(model));
|
||||||
|
model.key = key;
|
||||||
|
if (model.composition) {
|
||||||
|
model.composition = model.composition.map(parseKeyString);
|
||||||
|
}
|
||||||
|
return model;
|
||||||
|
};
|
||||||
|
|
||||||
|
var equals = function (a, b) {
|
||||||
|
return makeKeyString(a.key) === makeKeyString(b.key);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
toOldFormat: toOldFormat,
|
||||||
|
toNewFormat: toNewFormat,
|
||||||
|
makeKeyString: makeKeyString,
|
||||||
|
parseKeyString: parseKeyString,
|
||||||
|
equals: equals
|
||||||
|
};
|
||||||
|
});
|
10
src/api/objects/objectEventEmitter.js
Normal file
10
src/api/objects/objectEventEmitter.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
define([
|
||||||
|
"EventEmitter"
|
||||||
|
], function (
|
||||||
|
EventEmitter
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Provides a singleton event bus for sharing between objects.
|
||||||
|
*/
|
||||||
|
return new EventEmitter();
|
||||||
|
});
|
71
src/api/telemetry/README.md
Normal file
71
src/api/telemetry/README.md
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# Telemetry API - Overview
|
||||||
|
|
||||||
|
The Telemetry API provides basic methods for retrieving historical and realtime telemetry data, retrieving telemetry metadata, and registering additional telemetry providers.
|
||||||
|
|
||||||
|
The Telemetry API also provides a set of helpers built upon these basics-- TelemetryFormatters help you format telemetry values for display purposes, LimitEvaluators help you display evaluate and display alarm states, while TelemetryCollections provide a method for seamlessly combining historical and realtime data, while supporting more advanced client side filtering and interactivity.
|
||||||
|
|
||||||
|
|
||||||
|
## Getting Telemetry Data
|
||||||
|
|
||||||
|
|
||||||
|
### `MCT.telemetry.request(domainObject, options)`
|
||||||
|
|
||||||
|
Request historical telemetry for a domain object. Options allows you to specify filters (start, end, etc.), sort order, and strategies for retrieving telemetry (aggregation, latest available, etc.).
|
||||||
|
|
||||||
|
Returns a `Promise` for an array of telemetry values.
|
||||||
|
|
||||||
|
### `MCT.telemetry.subscribe(domainObject, callback, options)`
|
||||||
|
|
||||||
|
Subscribe to realtime telemetry for a specific domain object. callback will be called whenever data is received from a realtime provider. Options allows you to specify ???
|
||||||
|
|
||||||
|
## Understanding Telemetry
|
||||||
|
|
||||||
|
### `MCT.telemetry.getMetadata(domainObject)`
|
||||||
|
|
||||||
|
Retrieve telemetry metadata for a domain object. Telemetry metadata helps you understand the sort of telemetry data a domain object can provide-- for instances, the possible enumerations or states, the units, and more.
|
||||||
|
|
||||||
|
### `MCT.telemetry.Formatter`
|
||||||
|
|
||||||
|
Telemetry formatters help you format telemetry values for display. Under the covers, they use telemetry metadata to interpret your telemetry data, and then they use the format API to format that data for display.
|
||||||
|
|
||||||
|
|
||||||
|
### `MCT.telemetry.LimitEvaluator`
|
||||||
|
|
||||||
|
Limit Evaluators help you evaluate limit and alarm status of individual telemetry datums for display purposes without having to interact directly with the Limit API.
|
||||||
|
|
||||||
|
## Adding new telemetry sources
|
||||||
|
|
||||||
|
### `MCT.telemetry.registerProvider(telemetryProvider)`
|
||||||
|
|
||||||
|
Register a telemetry provider with the telemetry service. This allows you to connect alternative telemetry sources to For more information, see the `MCT.telemetry.BaseProvider`
|
||||||
|
|
||||||
|
### `MCT.telemetry.BaseProvider`
|
||||||
|
|
||||||
|
The base provider is a great starting point for developers who would like to implement their own telemetry provider. At the same time, you can implement your own telemetry provider as long as it meets the TelemetryProvider (see other docs).
|
||||||
|
|
||||||
|
## Other tools
|
||||||
|
|
||||||
|
### `MCT.telemetry.TelemetryCollection`
|
||||||
|
|
||||||
|
The TelemetryCollection is a useful tool for building advanced displays. It helps you seamlessly handle both historical and realtime telemetry data, while making it easier to deal with large data sets and interactive displays that need to frequently requery data.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# API Reference (TODO)
|
||||||
|
|
||||||
|
* Telemetry Metadata
|
||||||
|
* Request Options
|
||||||
|
-- start
|
||||||
|
-- end
|
||||||
|
-- sort
|
||||||
|
-- ???
|
||||||
|
-- strategies -- specify which strategies you want. an array provides for fallback strategies without needing decoration. Design fallbacks into API.
|
||||||
|
|
||||||
|
### `MCT.telemetry.request(domainObject, options)`
|
||||||
|
### `MCT.telemetry.subscribe(domainObject, callback, options)`
|
||||||
|
### `MCT.telemetry.getMetadata(domainObject)`
|
||||||
|
### `MCT.telemetry.Formatter`
|
||||||
|
### `MCT.telemetry.LimitEvaluator`
|
||||||
|
### `MCT.telemetry.registerProvider(telemetryProvider)`
|
||||||
|
### `MCT.telemetry.BaseProvider`
|
||||||
|
### `MCT.telemetry.TelemetryCollection`
|
295
src/api/telemetry/TelemetryAPI.js
Normal file
295
src/api/telemetry/TelemetryAPI.js
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
/*global define,window,console,MCT*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
|
||||||
|
var key = '114ced6c-deb7-4169-ae71-68c571665514';
|
||||||
|
MCT.objects.getObject([key])
|
||||||
|
.then(function (results) {
|
||||||
|
console.log('got results');
|
||||||
|
return results[key];
|
||||||
|
})
|
||||||
|
.then(function (domainObject) {
|
||||||
|
console.log('got object');
|
||||||
|
MCT.telemetry.subscribe(domainObject, function (datum) {
|
||||||
|
console.log('gotData!', datum);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
define([
|
||||||
|
'lodash',
|
||||||
|
'eventemitter2'
|
||||||
|
], function (
|
||||||
|
_,
|
||||||
|
EventEmitter
|
||||||
|
) {
|
||||||
|
|
||||||
|
// format map is a placeholder until we figure out format service.
|
||||||
|
var FORMAT_MAP = {
|
||||||
|
generic: function (range) {
|
||||||
|
return function (datum) {
|
||||||
|
return datum[range.key];
|
||||||
|
};
|
||||||
|
},
|
||||||
|
enum: function (range) {
|
||||||
|
var enumMap = _.indexBy(range.enumerations, 'value');
|
||||||
|
return function (datum) {
|
||||||
|
try {
|
||||||
|
return enumMap[datum[range.valueKey]].text;
|
||||||
|
} catch (e) {
|
||||||
|
return datum[range.valueKey];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FORMAT_MAP.number =
|
||||||
|
FORMAT_MAP.float =
|
||||||
|
FORMAT_MAP.integer =
|
||||||
|
FORMAT_MAP.ascii =
|
||||||
|
FORMAT_MAP.generic;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for retrieving telemetry data associated with a domain
|
||||||
|
* object.
|
||||||
|
* @interface TelemetryAPI
|
||||||
|
* @memberof module:openmct
|
||||||
|
*/
|
||||||
|
function TelemetryAPI(
|
||||||
|
formatService
|
||||||
|
) {
|
||||||
|
|
||||||
|
var FORMATTER_CACHE = new WeakMap(),
|
||||||
|
EVALUATOR_CACHE = new WeakMap();
|
||||||
|
|
||||||
|
function testAPI() {
|
||||||
|
var key = '114ced6c-deb7-4169-ae71-68c571665514';
|
||||||
|
window.MCT.objects.getObjects([key])
|
||||||
|
.then(function (results) {
|
||||||
|
console.log('got results');
|
||||||
|
return results[key];
|
||||||
|
})
|
||||||
|
.then(function (domainObject) {
|
||||||
|
var formatter = new MCT.telemetry.Formatter(domainObject);
|
||||||
|
console.log('got object');
|
||||||
|
window.MCT.telemetry.subscribe(domainObject, function (datum) {
|
||||||
|
var formattedValues = {};
|
||||||
|
Object.keys(datum).forEach(function (key) {
|
||||||
|
formattedValues[key] = formatter.format(datum, key);
|
||||||
|
});
|
||||||
|
console.log(
|
||||||
|
'datum:',
|
||||||
|
datum,
|
||||||
|
'formatted:',
|
||||||
|
formattedValues
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFormatter(range) {
|
||||||
|
if (FORMAT_MAP[range.type]) {
|
||||||
|
return FORMAT_MAP[range.type](range);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var format = formatService.getFormat(range.type).format.bind(
|
||||||
|
formatService.getFormat(range.type)
|
||||||
|
),
|
||||||
|
formatter = function (datum) {
|
||||||
|
return format(datum[range.key]);
|
||||||
|
};
|
||||||
|
return formatter;
|
||||||
|
} catch (e) {
|
||||||
|
console.log('could not retrieve format', range, e, e.message);
|
||||||
|
return FORMAT_MAP.generic(range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function TelemetryFormatter(domainObject) {
|
||||||
|
this.metadata = domainObject.getCapability('telemetry').getMetadata();
|
||||||
|
this.formats = {};
|
||||||
|
var ranges = this.metadata.ranges.concat(this.metadata.domains);
|
||||||
|
|
||||||
|
ranges.forEach(function (range) {
|
||||||
|
this.formats[range.key] = getFormatter(range);
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the 'key' from the datum and format it accordingly to
|
||||||
|
* telemetry metadata in domain object.
|
||||||
|
*/
|
||||||
|
TelemetryFormatter.prototype.format = function (datum, key) {
|
||||||
|
return this.formats[key](datum);
|
||||||
|
};
|
||||||
|
|
||||||
|
function LimitEvaluator(domainObject) {
|
||||||
|
this.domainObject = domainObject;
|
||||||
|
this.evaluator = domainObject.getCapability('limit');
|
||||||
|
if (!this.evaluator) {
|
||||||
|
this.evalute = function () {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** TODO: Do we need a telemetry parser, or do we assume telemetry
|
||||||
|
is numeric by default? */
|
||||||
|
|
||||||
|
LimitEvaluator.prototype.evaluate = function (datum, key) {
|
||||||
|
return this.evaluator.evaluate(datum, key);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Basic telemetry collection, needs more magic. **/
|
||||||
|
function TelemetryCollection(domainObject) {
|
||||||
|
this.domainObject = domainObject;
|
||||||
|
this.data = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
_.extend(TelemetryCollection.prototype, EventEmitter.prototype);
|
||||||
|
|
||||||
|
TelemetryCollection.prototype.request = function (options) {
|
||||||
|
request(this.domainObject, options).then(function (data) {
|
||||||
|
data.forEach(function (datum) {
|
||||||
|
this.addDatum(datum);
|
||||||
|
}, this);
|
||||||
|
}.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
TelemetryCollection.prototype.addDatum = function (datum) {
|
||||||
|
this.data.push(datum);
|
||||||
|
this.emit('add', datum);
|
||||||
|
};
|
||||||
|
|
||||||
|
TelemetryCollection.prototype.subscribe = function (options) {
|
||||||
|
if (this.unsubscribe) {
|
||||||
|
this.unsubscribe();
|
||||||
|
delete this.unsubscribe;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.unsubscribe = subscribe(
|
||||||
|
this.domainObject,
|
||||||
|
function (telemetrySeries) {
|
||||||
|
telemetrySeries.getData().forEach(this.addDatum, this);
|
||||||
|
}.bind(this),
|
||||||
|
options
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function registerProvider(provider) {
|
||||||
|
// Not yet implemented.
|
||||||
|
console.log('registering provider', provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerEvaluator(evaluator) {
|
||||||
|
// not yet implemented.
|
||||||
|
console.log('registering evaluator', evaluator);
|
||||||
|
}
|
||||||
|
|
||||||
|
function request(domainObject, options) {
|
||||||
|
return domainObject.getCapability('telemetry')
|
||||||
|
.requestData(options)
|
||||||
|
.then(function (telemetrySeries) {
|
||||||
|
return telemetrySeries.getData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function subscribe(domainObject, callback, options) {
|
||||||
|
return domainObject.getCapability('telemetry')
|
||||||
|
.subscribe(function (series) {
|
||||||
|
series.getData().forEach(callback);
|
||||||
|
}, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
var Telemetry = {
|
||||||
|
/**
|
||||||
|
* Register a new telemetry provider.
|
||||||
|
* @method registerProvider
|
||||||
|
* @memberof module:openmct.TelemetryAPI#
|
||||||
|
*/
|
||||||
|
registerProvider: registerProvider,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a new limit evaluator.
|
||||||
|
* @method registerEvaluator
|
||||||
|
* @memberof module:openmct.TelemetryAPI#
|
||||||
|
*/
|
||||||
|
registerEvaluator: registerEvaluator,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request historical telemetry data.
|
||||||
|
* @method request
|
||||||
|
* @memberof module:openmct.TelemetryAPI#
|
||||||
|
*/
|
||||||
|
request: request,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe to updates to telemetry data.
|
||||||
|
* @method subscribe
|
||||||
|
* @memberof module:openmct.TelemetryAPI#
|
||||||
|
*/
|
||||||
|
subscribe: subscribe,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get metadata associated with telemetry for this domain object.
|
||||||
|
* @param {module:openmct.DomainObject} domainObject the domain
|
||||||
|
* object for which to request telemetry
|
||||||
|
* @returns {module:openmct.TelemetryAPI~TelemetryMetadata}
|
||||||
|
* telemetry metadata
|
||||||
|
* @method getMetadata
|
||||||
|
* @memberof module:openmct.TelemetryAPI#
|
||||||
|
*/
|
||||||
|
getMetadata: function (domainObject) {
|
||||||
|
return domainObject.getCapability('telemetry').getMetadata();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a telemetry formatter for this domain object.
|
||||||
|
* @param {module:openmct.DomainObject} domainObject the domain
|
||||||
|
* object for which to format telemetry
|
||||||
|
* @returns {module:openmct.TelemetryAPI~TelemetryFormatter}
|
||||||
|
* @method Formatter
|
||||||
|
* @memberof module:openmct.TelemetryAPI#
|
||||||
|
*/
|
||||||
|
Formatter: function (domainObject) {
|
||||||
|
if (!FORMATTER_CACHE.has(domainObject)) {
|
||||||
|
FORMATTER_CACHE.set(
|
||||||
|
domainObject,
|
||||||
|
new TelemetryFormatter(domainObject)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return FORMATTER_CACHE.get(domainObject);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a limit evaluator for this domain object.
|
||||||
|
* @param {module:openmct.DomainObject} domainObject the domain
|
||||||
|
* object for which to evaluate limits
|
||||||
|
* @returns {module:openmct.TelemetryAPI~LimitEvaluator}
|
||||||
|
* @method LimitEvaluator
|
||||||
|
* @memberof module:openmct.TelemetryAPI#
|
||||||
|
*/
|
||||||
|
LimitEvaluator: function (domainObject) {
|
||||||
|
if (!EVALUATOR_CACHE.has(domainObject)) {
|
||||||
|
EVALUATOR_CACHE.set(
|
||||||
|
domainObject,
|
||||||
|
new LimitEvaluator(domainObject)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return EVALUATOR_CACHE.get(domainObject);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.MCT = window.MCT || {};
|
||||||
|
window.MCT.telemetry = Telemetry;
|
||||||
|
window.testAPI = testAPI;
|
||||||
|
|
||||||
|
return Telemetry;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TelemetryAPI;
|
||||||
|
});
|
46
src/api/telemetry/bundle.js
Normal file
46
src/api/telemetry/bundle.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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([
|
||||||
|
'./TelemetryAPI',
|
||||||
|
'legacyRegistry'
|
||||||
|
], function (
|
||||||
|
TelemetryAPI,
|
||||||
|
legacyRegistry
|
||||||
|
) {
|
||||||
|
legacyRegistry.register('api/telemetry-api', {
|
||||||
|
name: 'Telemetry API',
|
||||||
|
description: 'The public Telemetry API',
|
||||||
|
extensions: {
|
||||||
|
runs: [
|
||||||
|
{
|
||||||
|
key: "TelemetryAPI",
|
||||||
|
implementation: TelemetryAPI,
|
||||||
|
depends: [
|
||||||
|
'formatService'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
78
src/api/ui/Dialog.js
Normal file
78
src/api/ui/Dialog.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
define(['text!./dialog.html', 'zepto'], function (dialogTemplate, $) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dialog may be displayed to show blocking content to users.
|
||||||
|
* @param {module:openmct.View} view the view to show in the dialog
|
||||||
|
* @param {string [title] the title for this dialog
|
||||||
|
* @constructor
|
||||||
|
* @memberof module:openmct
|
||||||
|
*/
|
||||||
|
function Dialog(view, title) {
|
||||||
|
this.view = view;
|
||||||
|
this.title = title;
|
||||||
|
this.showing = false;
|
||||||
|
this.enabledState = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display this dialog.
|
||||||
|
* @returns {Promise} a promise that will be resolved if the user
|
||||||
|
* chooses "OK", an rejected if the user chooses "cancel"
|
||||||
|
* @method show
|
||||||
|
* @memberof module:openmct.Dialog#
|
||||||
|
*/
|
||||||
|
Dialog.prototype.show = function () {
|
||||||
|
if (this.showing) {
|
||||||
|
throw new Error("Dialog already showing.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var $body = $('body');
|
||||||
|
var $dialog = $(dialogTemplate);
|
||||||
|
var $contents = $dialog.find('.contents .editor');
|
||||||
|
var $close = $dialog.find('.close');
|
||||||
|
|
||||||
|
var $ok = $dialog.find('.ok');
|
||||||
|
var $cancel = $dialog.find('.cancel');
|
||||||
|
|
||||||
|
if (this.title) {
|
||||||
|
$dialog.find('.title').text(this.title);
|
||||||
|
}
|
||||||
|
|
||||||
|
$body.append($dialog);
|
||||||
|
this.view.show($contents[0]);
|
||||||
|
this.$dialog = $dialog;
|
||||||
|
this.$ok = $ok;
|
||||||
|
this.showing = true;
|
||||||
|
|
||||||
|
[$ok, $cancel, $close].forEach(function ($button) {
|
||||||
|
$button.on('click', this.hide.bind(this));
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
$ok.on('click', resolve);
|
||||||
|
$cancel.on('click', reject);
|
||||||
|
$close.on('click', reject);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Dialog.prototype.hide = function () {
|
||||||
|
if (!this.showing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$dialog.remove();
|
||||||
|
this.view.destroy();
|
||||||
|
this.showing = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
Dialog.prototype.enabled = function (state) {
|
||||||
|
if (state !== undefined) {
|
||||||
|
this.enabledState = state;
|
||||||
|
if (this.showing) {
|
||||||
|
this.$ok.toggleClass('disabled', !state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.enabledState;
|
||||||
|
};
|
||||||
|
|
||||||
|
return Dialog;
|
||||||
|
});
|
21
src/api/ui/dialog.html
Normal file
21
src/api/ui/dialog.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<div class="abs overlay">
|
||||||
|
<div class="abs blocker"></div>
|
||||||
|
<div class="abs holder">
|
||||||
|
<a class="clk-icon icon ui-symbol close">x</a>
|
||||||
|
<div class="abs contents">
|
||||||
|
<div class="abs top-bar">
|
||||||
|
<div class="title"></div>
|
||||||
|
<div class="hint"></div>
|
||||||
|
</div>
|
||||||
|
<div class='abs editor'>
|
||||||
|
</div>
|
||||||
|
<div class="abs bottom-bar">
|
||||||
|
<a class='s-btn major ok'>OK</a>
|
||||||
|
<a class='s-btn cancel'>Cancel</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
113
src/defaultRegistry.js
Normal file
113
src/defaultRegistry.js
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
define([
|
||||||
|
'legacyRegistry',
|
||||||
|
|
||||||
|
'../src/adapter/bundle',
|
||||||
|
'../src/api/objects/bundle',
|
||||||
|
|
||||||
|
'../example/builtins/bundle',
|
||||||
|
'../example/composite/bundle',
|
||||||
|
'../example/eventGenerator/bundle',
|
||||||
|
'../example/export/bundle',
|
||||||
|
'../example/extensions/bundle',
|
||||||
|
'../example/forms/bundle',
|
||||||
|
'../example/generator/bundle',
|
||||||
|
'../example/identity/bundle',
|
||||||
|
'../example/imagery/bundle',
|
||||||
|
'../example/mobile/bundle',
|
||||||
|
'../example/msl/bundle',
|
||||||
|
'../example/notifications/bundle',
|
||||||
|
'../example/persistence/bundle',
|
||||||
|
'../example/plotOptions/bundle',
|
||||||
|
'../example/policy/bundle',
|
||||||
|
'../example/profiling/bundle',
|
||||||
|
'../example/scratchpad/bundle',
|
||||||
|
'../example/taxonomy/bundle',
|
||||||
|
'../example/worker/bundle',
|
||||||
|
|
||||||
|
'../platform/commonUI/about/bundle',
|
||||||
|
'../platform/commonUI/browse/bundle',
|
||||||
|
'../platform/commonUI/dialog/bundle',
|
||||||
|
'../platform/commonUI/edit/bundle',
|
||||||
|
'../platform/commonUI/formats/bundle',
|
||||||
|
'../platform/commonUI/general/bundle',
|
||||||
|
'../platform/commonUI/inspect/bundle',
|
||||||
|
'../platform/commonUI/mobile/bundle',
|
||||||
|
'../platform/commonUI/notification/bundle',
|
||||||
|
'../platform/commonUI/regions/bundle',
|
||||||
|
'../platform/commonUI/themes/espresso/bundle',
|
||||||
|
'../platform/commonUI/themes/snow/bundle',
|
||||||
|
'../platform/containment/bundle',
|
||||||
|
'../platform/core/bundle',
|
||||||
|
'../platform/entanglement/bundle',
|
||||||
|
'../platform/execution/bundle',
|
||||||
|
'../platform/exporters/bundle',
|
||||||
|
'../platform/features/clock/bundle',
|
||||||
|
'../platform/features/conductor/bundle',
|
||||||
|
'../platform/features/imagery/bundle',
|
||||||
|
'../platform/features/layout/bundle',
|
||||||
|
'../platform/features/pages/bundle',
|
||||||
|
'../platform/features/plot/bundle',
|
||||||
|
'../platform/features/static-markup/bundle',
|
||||||
|
'../platform/features/table/bundle',
|
||||||
|
'../platform/features/timeline/bundle',
|
||||||
|
'../platform/forms/bundle',
|
||||||
|
'../platform/framework/bundle',
|
||||||
|
'../platform/framework/src/load/Bundle',
|
||||||
|
'../platform/identity/bundle',
|
||||||
|
'../platform/persistence/aggregator/bundle',
|
||||||
|
'../platform/persistence/couch/bundle',
|
||||||
|
'../platform/persistence/elastic/bundle',
|
||||||
|
'../platform/persistence/local/bundle',
|
||||||
|
'../platform/persistence/queue/bundle',
|
||||||
|
'../platform/policy/bundle',
|
||||||
|
'../platform/representation/bundle',
|
||||||
|
'../platform/search/bundle',
|
||||||
|
'../platform/status/bundle',
|
||||||
|
'../platform/telemetry/bundle',
|
||||||
|
], function (legacyRegistry) {
|
||||||
|
|
||||||
|
var DEFAULTS = [
|
||||||
|
'src/adapter',
|
||||||
|
'src/api/objects',
|
||||||
|
'platform/framework',
|
||||||
|
'platform/core',
|
||||||
|
'platform/representation',
|
||||||
|
'platform/commonUI/about',
|
||||||
|
'platform/commonUI/browse',
|
||||||
|
'platform/commonUI/edit',
|
||||||
|
'platform/commonUI/dialog',
|
||||||
|
'platform/commonUI/formats',
|
||||||
|
'platform/commonUI/general',
|
||||||
|
'platform/commonUI/inspect',
|
||||||
|
'platform/commonUI/mobile',
|
||||||
|
'platform/commonUI/themes/espresso',
|
||||||
|
'platform/commonUI/notification',
|
||||||
|
'platform/containment',
|
||||||
|
'platform/execution',
|
||||||
|
'platform/exporters',
|
||||||
|
'platform/telemetry',
|
||||||
|
'platform/features/clock',
|
||||||
|
'platform/features/imagery',
|
||||||
|
'platform/features/layout',
|
||||||
|
'platform/features/pages',
|
||||||
|
'platform/features/plot',
|
||||||
|
'platform/features/timeline',
|
||||||
|
'platform/features/table',
|
||||||
|
'platform/forms',
|
||||||
|
'platform/identity',
|
||||||
|
'platform/persistence/aggregator',
|
||||||
|
'platform/persistence/local',
|
||||||
|
'platform/persistence/queue',
|
||||||
|
'platform/policy',
|
||||||
|
'platform/entanglement',
|
||||||
|
'platform/search',
|
||||||
|
'platform/status',
|
||||||
|
'platform/commonUI/regions'
|
||||||
|
];
|
||||||
|
|
||||||
|
DEFAULTS.forEach(function (bundlePath) {
|
||||||
|
legacyRegistry.enable(bundlePath);
|
||||||
|
});
|
||||||
|
|
||||||
|
return legacyRegistry;
|
||||||
|
});
|
3
src/end.frag
Normal file
3
src/end.frag
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
return require('main');
|
||||||
|
}));
|
16
src/openmct.js
Normal file
16
src/openmct.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
define(['./MCT', './api/Type'], function (MCT, Type) {
|
||||||
|
/**
|
||||||
|
* Open MCT is an extensible web application for building mission
|
||||||
|
* control user interfaces. This module is itself an instance of
|
||||||
|
* [MCT]{@link module:openmct.MCT}, which provides an interface for
|
||||||
|
* configuring and executing the application.
|
||||||
|
*
|
||||||
|
* @exports openmct
|
||||||
|
*/
|
||||||
|
var openmct = new MCT();
|
||||||
|
|
||||||
|
openmct.MCT = MCT;
|
||||||
|
openmct.Type = Type;
|
||||||
|
|
||||||
|
return openmct;
|
||||||
|
});
|
9
src/start.frag
Normal file
9
src/start.frag
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
(function (root, factory) {
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
define([], factory);
|
||||||
|
} else if (typeof exports === 'object') {
|
||||||
|
module.exports = factory();
|
||||||
|
} else {
|
||||||
|
root.MCT = factory();
|
||||||
|
}
|
||||||
|
}(this, function() {
|
10
test-main.js
10
test-main.js
@ -48,13 +48,15 @@ requirejs.config({
|
|||||||
"angular-route": "bower_components/angular-route/angular-route.min",
|
"angular-route": "bower_components/angular-route/angular-route.min",
|
||||||
"csv": "bower_components/comma-separated-values/csv.min",
|
"csv": "bower_components/comma-separated-values/csv.min",
|
||||||
"es6-promise": "bower_components/es6-promise/promise.min",
|
"es6-promise": "bower_components/es6-promise/promise.min",
|
||||||
|
"EventEmitter": "bower_components/eventemitter3/index",
|
||||||
"moment": "bower_components/moment/moment",
|
"moment": "bower_components/moment/moment",
|
||||||
"moment-duration-format": "bower_components/moment-duration-format/lib/moment-duration-format",
|
"moment-duration-format": "bower_components/moment-duration-format/lib/moment-duration-format",
|
||||||
"saveAs": "bower_components/FileSaver.js/FileSaver.min",
|
"saveAs": "bower_components/FileSaver.js/FileSaver.min",
|
||||||
"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": {
|
||||||
@ -64,6 +66,9 @@ requirejs.config({
|
|||||||
"angular-route": {
|
"angular-route": {
|
||||||
"deps": [ "angular" ]
|
"deps": [ "angular" ]
|
||||||
},
|
},
|
||||||
|
"EventEmitter": {
|
||||||
|
"exports": "EventEmitter"
|
||||||
|
},
|
||||||
"moment-duration-format": {
|
"moment-duration-format": {
|
||||||
"deps": [ "moment" ]
|
"deps": [ "moment" ]
|
||||||
},
|
},
|
||||||
@ -72,6 +77,9 @@ requirejs.config({
|
|||||||
},
|
},
|
||||||
"zepto": {
|
"zepto": {
|
||||||
"exports": "Zepto"
|
"exports": "Zepto"
|
||||||
|
},
|
||||||
|
"lodash": {
|
||||||
|
"exports": "lodash"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
127
tutorial-server/app.js
Normal file
127
tutorial-server/app.js
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
/*global require,process,console*/
|
||||||
|
|
||||||
|
var CONFIG = {
|
||||||
|
port: 8081,
|
||||||
|
dictionary: "dictionary.json",
|
||||||
|
interval: 1000
|
||||||
|
};
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var WebSocketServer = require('ws').Server,
|
||||||
|
fs = require('fs'),
|
||||||
|
wss = new WebSocketServer({ port: CONFIG.port }),
|
||||||
|
dictionary = JSON.parse(fs.readFileSync(CONFIG.dictionary, "utf8")),
|
||||||
|
spacecraft = {
|
||||||
|
"prop.fuel": 77,
|
||||||
|
"prop.thrusters": "OFF",
|
||||||
|
"comms.recd": 0,
|
||||||
|
"comms.sent": 0,
|
||||||
|
"pwr.temp": 245,
|
||||||
|
"pwr.c": 8.15,
|
||||||
|
"pwr.v": 30
|
||||||
|
},
|
||||||
|
histories = {},
|
||||||
|
listeners = [];
|
||||||
|
|
||||||
|
function updateSpacecraft() {
|
||||||
|
spacecraft["prop.fuel"] = Math.max(
|
||||||
|
0,
|
||||||
|
spacecraft["prop.fuel"] -
|
||||||
|
(spacecraft["prop.thrusters"] === "ON" ? 0.5 : 0)
|
||||||
|
);
|
||||||
|
spacecraft["pwr.temp"] = spacecraft["pwr.temp"] * 0.985
|
||||||
|
+ Math.random() * 0.25 + Math.sin(Date.now());
|
||||||
|
spacecraft["pwr.c"] = spacecraft["pwr.c"] * 0.985;
|
||||||
|
spacecraft["pwr.v"] = 30 + Math.pow(Math.random(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateTelemetry() {
|
||||||
|
var timestamp = Date.now(), sent = 0;
|
||||||
|
Object.keys(spacecraft).forEach(function (id) {
|
||||||
|
var state = { timestamp: timestamp, value: spacecraft[id] };
|
||||||
|
histories[id] = histories[id] || []; // Initialize
|
||||||
|
histories[id].push(state);
|
||||||
|
spacecraft["comms.sent"] += JSON.stringify(state).length;
|
||||||
|
});
|
||||||
|
listeners.forEach(function (listener) {
|
||||||
|
listener();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
updateSpacecraft();
|
||||||
|
generateTelemetry();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleConnection(ws) {
|
||||||
|
var subscriptions = {}, // Active subscriptions for this connection
|
||||||
|
handlers = { // Handlers for specific requests
|
||||||
|
dictionary: function () {
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
type: "dictionary",
|
||||||
|
value: dictionary
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
subscribe: function (id) {
|
||||||
|
subscriptions[id] = true;
|
||||||
|
},
|
||||||
|
unsubscribe: function (id) {
|
||||||
|
delete subscriptions[id];
|
||||||
|
},
|
||||||
|
history: function (id) {
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
type: "history",
|
||||||
|
id: id,
|
||||||
|
value: histories[id]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function notifySubscribers() {
|
||||||
|
Object.keys(subscriptions).forEach(function (id) {
|
||||||
|
var history = histories[id];
|
||||||
|
if (history) {
|
||||||
|
ws.send(JSON.stringify({
|
||||||
|
type: "data",
|
||||||
|
id: id,
|
||||||
|
value: history[history.length - 1]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for requests
|
||||||
|
ws.on('message', function (message) {
|
||||||
|
var parts = message.split(' '),
|
||||||
|
handler = handlers[parts[0]];
|
||||||
|
if (handler) {
|
||||||
|
handler.apply(handlers, parts.slice(1));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Stop sending telemetry updates for this connection when closed
|
||||||
|
ws.on('close', function () {
|
||||||
|
listeners = listeners.filter(function (listener) {
|
||||||
|
return listener !== notifySubscribers;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Notify subscribers when telemetry is updated
|
||||||
|
listeners.push(notifySubscribers);
|
||||||
|
}
|
||||||
|
|
||||||
|
update();
|
||||||
|
setInterval(update, CONFIG.interval);
|
||||||
|
|
||||||
|
wss.on('connection', handleConnection);
|
||||||
|
|
||||||
|
console.log("Example spacecraft running on port ");
|
||||||
|
console.log("Press Enter to toggle thruster state.");
|
||||||
|
process.stdin.on('data', function (data) {
|
||||||
|
spacecraft['prop.thrusters'] =
|
||||||
|
(spacecraft['prop.thrusters'] === "OFF") ? "ON" : "OFF";
|
||||||
|
console.log("Thrusters " + spacecraft["prop.thrusters"]);
|
||||||
|
});
|
||||||
|
}());
|
66
tutorial-server/dictionary.json
Normal file
66
tutorial-server/dictionary.json
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
"name": "Example Spacecraft",
|
||||||
|
"identifier": "sc",
|
||||||
|
"subsystems": [
|
||||||
|
{
|
||||||
|
"name": "Propulsion",
|
||||||
|
"identifier": "prop",
|
||||||
|
"measurements": [
|
||||||
|
{
|
||||||
|
"name": "Fuel",
|
||||||
|
"identifier": "prop.fuel",
|
||||||
|
"units": "kilograms",
|
||||||
|
"type": "float"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Thrusters",
|
||||||
|
"identifier": "prop.thrusters",
|
||||||
|
"units": "None",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Communications",
|
||||||
|
"identifier": "comms",
|
||||||
|
"measurements": [
|
||||||
|
{
|
||||||
|
"name": "Received",
|
||||||
|
"identifier": "comms.recd",
|
||||||
|
"units": "bytes",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Sent",
|
||||||
|
"identifier": "comms.sent",
|
||||||
|
"units": "bytes",
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Power",
|
||||||
|
"identifier": "pwr",
|
||||||
|
"measurements": [
|
||||||
|
{
|
||||||
|
"name": "Generator Temperature",
|
||||||
|
"identifier": "pwr.temp",
|
||||||
|
"units": "\u0080C",
|
||||||
|
"type": "float"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Generator Current",
|
||||||
|
"identifier": "pwr.c",
|
||||||
|
"units": "A",
|
||||||
|
"type": "float"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Generator Voltage",
|
||||||
|
"identifier": "pwr.v",
|
||||||
|
"units": "V",
|
||||||
|
"type": "float"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
66
tutorials/bargraph/bundle.js
Normal file
66
tutorials/bargraph/bundle.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
define([
|
||||||
|
'legacyRegistry',
|
||||||
|
'./src/controllers/BarGraphController'
|
||||||
|
], function (
|
||||||
|
legacyRegistry,
|
||||||
|
BarGraphController
|
||||||
|
) {
|
||||||
|
legacyRegistry.register("tutorials/bargraph", {
|
||||||
|
"name": "Bar Graph",
|
||||||
|
"description": "Provides the Bar Graph view of telemetry elements.",
|
||||||
|
"extensions": {
|
||||||
|
"views": [
|
||||||
|
{
|
||||||
|
"name": "Bar Graph",
|
||||||
|
"key": "example.bargraph",
|
||||||
|
"glyph": "H",
|
||||||
|
"templateUrl": "templates/bargraph.html",
|
||||||
|
"needs": [ "telemetry" ],
|
||||||
|
"delegation": true,
|
||||||
|
"editable": true,
|
||||||
|
"toolbar": {
|
||||||
|
"sections": [
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"name": "Low",
|
||||||
|
"property": "low",
|
||||||
|
"required": true,
|
||||||
|
"control": "textfield",
|
||||||
|
"size": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Middle",
|
||||||
|
"property": "middle",
|
||||||
|
"required": true,
|
||||||
|
"control": "textfield",
|
||||||
|
"size": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "High",
|
||||||
|
"property": "high",
|
||||||
|
"required": true,
|
||||||
|
"control": "textfield",
|
||||||
|
"size": 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stylesheets": [
|
||||||
|
{
|
||||||
|
"stylesheetUrl": "css/bargraph.css"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"controllers": [
|
||||||
|
{
|
||||||
|
"key": "BarGraphController",
|
||||||
|
"implementation": BarGraphController,
|
||||||
|
"depends": [ "$scope", "telemetryHandler" ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
35
tutorials/bargraph/res/templates/bargraph.html
Normal file
35
tutorials/bargraph/res/templates/bargraph.html
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<div class="example-bargraph" ng-controller="BarGraphController">
|
||||||
|
<div class="example-tick-labels">
|
||||||
|
<div ng-repeat="value in [low, middle, high] track by $index"
|
||||||
|
class="example-tick-label"
|
||||||
|
style="bottom: {{ toPercent(value) }}%">
|
||||||
|
{{value}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="example-graph-area">
|
||||||
|
<div ng-repeat="telemetryObject in telemetryObjects"
|
||||||
|
style="left: {{barWidth * $index}}%; width: {{barWidth}}%"
|
||||||
|
class="example-bar-holder">
|
||||||
|
<div class="example-bar"
|
||||||
|
ng-style="{
|
||||||
|
bottom: getBottom(telemetryObject) + '%',
|
||||||
|
top: getTop(telemetryObject) + '%'
|
||||||
|
}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="bottom: {{ toPercent(middle) }}%"
|
||||||
|
class="example-graph-tick">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="example-bar-labels">
|
||||||
|
<div ng-repeat="telemetryObject in telemetryObjects"
|
||||||
|
style="left: {{barWidth * $index}}%; width: {{barWidth}}%"
|
||||||
|
class="example-bar-holder example-label">
|
||||||
|
<mct-representation key="'label'"
|
||||||
|
mct-object="telemetryObject">
|
||||||
|
</mct-representation>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
75
tutorials/bargraph/src/controllers/BarGraphController.js
Normal file
75
tutorials/bargraph/src/controllers/BarGraphController.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
define(function () {
|
||||||
|
function BarGraphController($scope, telemetryHandler) {
|
||||||
|
var handle;
|
||||||
|
|
||||||
|
// Expose configuration constants directly in scope
|
||||||
|
function exposeConfiguration() {
|
||||||
|
$scope.low = $scope.configuration.low;
|
||||||
|
$scope.middle = $scope.configuration.middle;
|
||||||
|
$scope.high = $scope.configuration.high;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate a default value in the configuration
|
||||||
|
function setDefault(key, value) {
|
||||||
|
if ($scope.configuration[key] === undefined) {
|
||||||
|
$scope.configuration[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter-setter for configuration properties (for view proxy)
|
||||||
|
function getterSetter(property) {
|
||||||
|
return function (value) {
|
||||||
|
value = parseFloat(value);
|
||||||
|
if (!isNaN(value)) {
|
||||||
|
$scope.configuration[property] = value;
|
||||||
|
exposeConfiguration();
|
||||||
|
}
|
||||||
|
return $scope.configuration[property];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add min/max defaults
|
||||||
|
setDefault('low', -1);
|
||||||
|
setDefault('middle', 0);
|
||||||
|
setDefault('high', 1);
|
||||||
|
exposeConfiguration($scope.configuration);
|
||||||
|
|
||||||
|
// Expose view configuration options
|
||||||
|
if ($scope.selection) {
|
||||||
|
$scope.selection.proxy({
|
||||||
|
low: getterSetter('low'),
|
||||||
|
middle: getterSetter('middle'),
|
||||||
|
high: getterSetter('high')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert value to a percent between 0-100
|
||||||
|
$scope.toPercent = function (value) {
|
||||||
|
var pct = 100 * (value - $scope.low) /
|
||||||
|
($scope.high - $scope.low);
|
||||||
|
return Math.min(100, Math.max(0, pct));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get bottom and top (as percentages) for current value
|
||||||
|
$scope.getBottom = function (telemetryObject) {
|
||||||
|
var value = handle.getRangeValue(telemetryObject);
|
||||||
|
return $scope.toPercent(Math.min($scope.middle, value));
|
||||||
|
};
|
||||||
|
$scope.getTop = function (telemetryObject) {
|
||||||
|
var value = handle.getRangeValue(telemetryObject);
|
||||||
|
return 100 - $scope.toPercent(Math.max($scope.middle, value));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Use the telemetryHandler to get telemetry objects here
|
||||||
|
handle = telemetryHandler.handle($scope.domainObject, function () {
|
||||||
|
$scope.telemetryObjects = handle.getTelemetryObjects();
|
||||||
|
$scope.barWidth =
|
||||||
|
100 / Math.max(($scope.telemetryObjects).length, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Release subscriptions when scope is destroyed
|
||||||
|
$scope.$on('$destroy', handle.unsubscribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
return BarGraphController;
|
||||||
|
});
|
90
tutorials/telemetry/bundle.js
Normal file
90
tutorials/telemetry/bundle.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
define([
|
||||||
|
'legacyRegistry',
|
||||||
|
'./src/ExampleTelemetryServerAdapter',
|
||||||
|
'./src/ExampleTelemetryInitializer',
|
||||||
|
'./src/ExampleTelemetryModelProvider'
|
||||||
|
], function (
|
||||||
|
legacyRegistry,
|
||||||
|
ExampleTelemetryServerAdapter,
|
||||||
|
ExampleTelemetryInitializer,
|
||||||
|
ExampleTelemetryModelProvider
|
||||||
|
) {
|
||||||
|
legacyRegistry.register("tutorials/telemetry", {
|
||||||
|
"name": "Example Telemetry Adapter",
|
||||||
|
"extensions": {
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"name": "Spacecraft",
|
||||||
|
"key": "example.spacecraft",
|
||||||
|
"glyph": "o"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Subsystem",
|
||||||
|
"key": "example.subsystem",
|
||||||
|
"glyph": "o",
|
||||||
|
"model": { "composition": [] }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Measurement",
|
||||||
|
"key": "example.measurement",
|
||||||
|
"glyph": "T",
|
||||||
|
"model": { "telemetry": {} },
|
||||||
|
"telemetry": {
|
||||||
|
"source": "example.source",
|
||||||
|
"domains": [
|
||||||
|
{
|
||||||
|
"name": "Time",
|
||||||
|
"key": "timestamp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"roots": [
|
||||||
|
{
|
||||||
|
"id": "example:sc",
|
||||||
|
"priority": "preferred",
|
||||||
|
"model": {
|
||||||
|
"type": "example.spacecraft",
|
||||||
|
"name": "My Spacecraft",
|
||||||
|
"composition": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"key": "example.adapter",
|
||||||
|
"implementation": "ExampleTelemetryServerAdapter.js",
|
||||||
|
"depends": [ "$q", "EXAMPLE_WS_URL" ]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"constants": [
|
||||||
|
{
|
||||||
|
"key": "EXAMPLE_WS_URL",
|
||||||
|
"priority": "fallback",
|
||||||
|
"value": "ws://localhost:8081"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"runs": [
|
||||||
|
{
|
||||||
|
"implementation": "ExampleTelemetryInitializer.js",
|
||||||
|
"depends": [ "example.adapter", "objectService" ]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"provides": "modelService",
|
||||||
|
"type": "provider",
|
||||||
|
"implementation": "ExampleTelemetryModelProvider.js",
|
||||||
|
"depends": [ "example.adapter", "$q" ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"provides": "telemetryService",
|
||||||
|
"type": "provider",
|
||||||
|
"implementation": "ExampleTelemetryProvider.js",
|
||||||
|
"depends": [ "example.adapter", "$q" ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
47
tutorials/telemetry/src/ExampleTelemetryInitializer.js
Normal file
47
tutorials/telemetry/src/ExampleTelemetryInitializer.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
define(
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var TAXONOMY_ID = "example:sc",
|
||||||
|
PREFIX = "example_tlm:";
|
||||||
|
|
||||||
|
function ExampleTelemetryInitializer(adapter, objectService) {
|
||||||
|
// Generate a domain object identifier for a dictionary element
|
||||||
|
function makeId(element) {
|
||||||
|
return PREFIX + element.identifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the dictionary is available, add all subsystems
|
||||||
|
// to the composition of My Spacecraft
|
||||||
|
function initializeTaxonomy(dictionary) {
|
||||||
|
// Get the top-level container for dictionary objects
|
||||||
|
// from a group of domain objects.
|
||||||
|
function getTaxonomyObject(domainObjects) {
|
||||||
|
return domainObjects[TAXONOMY_ID];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate
|
||||||
|
function populateModel(taxonomyObject) {
|
||||||
|
return taxonomyObject.useCapability(
|
||||||
|
"mutation",
|
||||||
|
function (model) {
|
||||||
|
model.name =
|
||||||
|
dictionary.name;
|
||||||
|
model.composition =
|
||||||
|
dictionary.subsystems.map(makeId);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up My Spacecraft, and populate it accordingly.
|
||||||
|
objectService.getObjects([TAXONOMY_ID])
|
||||||
|
.then(getTaxonomyObject)
|
||||||
|
.then(populateModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter.dictionary().then(initializeTaxonomy);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExampleTelemetryInitializer;
|
||||||
|
}
|
||||||
|
);
|
78
tutorials/telemetry/src/ExampleTelemetryModelProvider.js
Normal file
78
tutorials/telemetry/src/ExampleTelemetryModelProvider.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
define(
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var PREFIX = "example_tlm:",
|
||||||
|
FORMAT_MAPPINGS = {
|
||||||
|
float: "number",
|
||||||
|
integer: "number",
|
||||||
|
string: "string"
|
||||||
|
};
|
||||||
|
|
||||||
|
function ExampleTelemetryModelProvider(adapter, $q) {
|
||||||
|
var modelPromise, empty = $q.when({});
|
||||||
|
|
||||||
|
// Check if this model is in our dictionary (by prefix)
|
||||||
|
function isRelevant(id) {
|
||||||
|
return id.indexOf(PREFIX) === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a domain object identifier by adding a prefix
|
||||||
|
function makeId(element) {
|
||||||
|
return PREFIX + element.identifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create domain object models from this dictionary
|
||||||
|
function buildTaxonomy(dictionary) {
|
||||||
|
var models = {};
|
||||||
|
|
||||||
|
// Create & store a domain object model for a measurement
|
||||||
|
function addMeasurement(measurement) {
|
||||||
|
var format = FORMAT_MAPPINGS[measurement.type];
|
||||||
|
models[makeId(measurement)] = {
|
||||||
|
type: "example.measurement",
|
||||||
|
name: measurement.name,
|
||||||
|
telemetry: {
|
||||||
|
key: measurement.identifier,
|
||||||
|
ranges: [{
|
||||||
|
key: "value",
|
||||||
|
name: "Value",
|
||||||
|
units: measurement.units,
|
||||||
|
format: format
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create & store a domain object model for a subsystem
|
||||||
|
function addSubsystem(subsystem) {
|
||||||
|
var measurements =
|
||||||
|
(subsystem.measurements || []);
|
||||||
|
models[makeId(subsystem)] = {
|
||||||
|
type: "example.subsystem",
|
||||||
|
name: subsystem.name,
|
||||||
|
composition: measurements.map(makeId)
|
||||||
|
};
|
||||||
|
measurements.forEach(addMeasurement);
|
||||||
|
}
|
||||||
|
|
||||||
|
(dictionary.subsystems || []).forEach(addSubsystem);
|
||||||
|
|
||||||
|
return models;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin generating models once the dictionary is available
|
||||||
|
modelPromise = adapter.dictionary().then(buildTaxonomy);
|
||||||
|
|
||||||
|
return {
|
||||||
|
getModels: function (ids) {
|
||||||
|
// Return models for the dictionary only when they
|
||||||
|
// are relevant to the request.
|
||||||
|
return ids.some(isRelevant) ? modelPromise : empty;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExampleTelemetryModelProvider;
|
||||||
|
}
|
||||||
|
);
|
80
tutorials/telemetry/src/ExampleTelemetryProvider.js
Normal file
80
tutorials/telemetry/src/ExampleTelemetryProvider.js
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
define(
|
||||||
|
['./ExampleTelemetrySeries'],
|
||||||
|
function (ExampleTelemetrySeries) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var SOURCE = "example.source";
|
||||||
|
|
||||||
|
function ExampleTelemetryProvider(adapter, $q) {
|
||||||
|
var subscribers = {};
|
||||||
|
|
||||||
|
// Used to filter out requests for telemetry
|
||||||
|
// from some other source
|
||||||
|
function matchesSource(request) {
|
||||||
|
return (request.source === SOURCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for data, notify subscribers
|
||||||
|
adapter.listen(function (message) {
|
||||||
|
var packaged = {};
|
||||||
|
packaged[SOURCE] = {};
|
||||||
|
packaged[SOURCE][message.id] =
|
||||||
|
new ExampleTelemetrySeries([message.value]);
|
||||||
|
(subscribers[message.id] || []).forEach(function (cb) {
|
||||||
|
cb(packaged);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
requestTelemetry: function (requests) {
|
||||||
|
var packaged = {},
|
||||||
|
relevantReqs = requests.filter(matchesSource);
|
||||||
|
|
||||||
|
// Package historical telemetry that has been received
|
||||||
|
function addToPackage(history) {
|
||||||
|
packaged[SOURCE][history.id] =
|
||||||
|
new ExampleTelemetrySeries(history.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve telemetry for a specific measurement
|
||||||
|
function handleRequest(request) {
|
||||||
|
var key = request.key;
|
||||||
|
return adapter.history(key).then(addToPackage);
|
||||||
|
}
|
||||||
|
|
||||||
|
packaged[SOURCE] = {};
|
||||||
|
return $q.all(relevantReqs.map(handleRequest))
|
||||||
|
.then(function () { return packaged; });
|
||||||
|
},
|
||||||
|
subscribe: function (callback, requests) {
|
||||||
|
var keys = requests.filter(matchesSource)
|
||||||
|
.map(function (req) { return req.key; });
|
||||||
|
|
||||||
|
function notCallback(cb) {
|
||||||
|
return cb !== callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
function unsubscribe(key) {
|
||||||
|
subscribers[key] =
|
||||||
|
(subscribers[key] || []).filter(notCallback);
|
||||||
|
if (subscribers[key].length < 1) {
|
||||||
|
adapter.unsubscribe(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keys.forEach(function (key) {
|
||||||
|
subscribers[key] = subscribers[key] || [];
|
||||||
|
adapter.subscribe(key);
|
||||||
|
subscribers[key].push(callback);
|
||||||
|
});
|
||||||
|
|
||||||
|
return function () {
|
||||||
|
keys.forEach(unsubscribe);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExampleTelemetryProvider;
|
||||||
|
}
|
||||||
|
);
|
23
tutorials/telemetry/src/ExampleTelemetrySeries.js
Normal file
23
tutorials/telemetry/src/ExampleTelemetrySeries.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*global define*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function ExampleTelemetrySeries(data) {
|
||||||
|
return {
|
||||||
|
getPointCount: function () {
|
||||||
|
return data.length;
|
||||||
|
},
|
||||||
|
getDomainValue: function (index) {
|
||||||
|
return (data[index] || {}).timestamp;
|
||||||
|
},
|
||||||
|
getRangeValue: function (index) {
|
||||||
|
return (data[index] || {}).value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExampleTelemetrySeries;
|
||||||
|
}
|
||||||
|
);
|
60
tutorials/telemetry/src/ExampleTelemetryServerAdapter.js
Normal file
60
tutorials/telemetry/src/ExampleTelemetryServerAdapter.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function ExampleTelemetryServerAdapter($q, wsUrl) {
|
||||||
|
var ws = new WebSocket(wsUrl),
|
||||||
|
histories = {},
|
||||||
|
listeners = [],
|
||||||
|
dictionary = $q.defer();
|
||||||
|
|
||||||
|
// Handle an incoming message from the server
|
||||||
|
ws.onmessage = function (event) {
|
||||||
|
var message = JSON.parse(event.data);
|
||||||
|
|
||||||
|
switch (message.type) {
|
||||||
|
case "dictionary":
|
||||||
|
dictionary.resolve(message.value);
|
||||||
|
break;
|
||||||
|
case "history":
|
||||||
|
histories[message.id].resolve(message);
|
||||||
|
delete histories[message.id];
|
||||||
|
break;
|
||||||
|
case "data":
|
||||||
|
listeners.forEach(function (listener) {
|
||||||
|
listener(message);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Request dictionary once connection is established
|
||||||
|
ws.onopen = function () {
|
||||||
|
ws.send("dictionary");
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
dictionary: function () {
|
||||||
|
return dictionary.promise;
|
||||||
|
},
|
||||||
|
history: function (id) {
|
||||||
|
histories[id] = histories[id] || $q.defer();
|
||||||
|
ws.send("history " + id);
|
||||||
|
return histories[id].promise;
|
||||||
|
},
|
||||||
|
subscribe: function (id) {
|
||||||
|
ws.send("subscribe " + id);
|
||||||
|
},
|
||||||
|
unsubscribe: function (id) {
|
||||||
|
ws.send("unsubscribe " + id);
|
||||||
|
},
|
||||||
|
listen: function (callback) {
|
||||||
|
listeners.push(callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExampleTelemetryServerAdapter;
|
||||||
|
}
|
||||||
|
);
|
3
tutorials/todo/todo-dialog.html
Normal file
3
tutorials/todo/todo-dialog.html
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<label>Description:
|
||||||
|
<input type="text" class="description">
|
||||||
|
</label>
|
5
tutorials/todo/todo-task.html
Normal file
5
tutorials/todo/todo-task.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<li>
|
||||||
|
<input type="checkbox" class="example-task-checked">
|
||||||
|
<span class="example-task-description">
|
||||||
|
</span>
|
||||||
|
</li>
|
9
tutorials/todo/todo-toolbar.html
Normal file
9
tutorials/todo/todo-toolbar.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<div class="tool-bar btn-bar contents abs">
|
||||||
|
<a class="s-btn labeled example-add">
|
||||||
|
<span class="ui-symbol icon">+</span>
|
||||||
|
<span class="title-label">Add Task</span>
|
||||||
|
</a>
|
||||||
|
<a class="s-btn example-remove">
|
||||||
|
<span class="ui-symbol icon">Z</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
29
tutorials/todo/todo.css
Normal file
29
tutorials/todo/todo.css
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
.example-todo div.example-button-group {
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-todo .example-button-group a {
|
||||||
|
padding: 3px;
|
||||||
|
margin: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-todo .example-button-group a.selected {
|
||||||
|
border: 1px gray solid;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-todo .example-task-completed .example-task-description {
|
||||||
|
text-decoration: line-through;
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-todo .example-task-description.selected {
|
||||||
|
background: #46A;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-todo .example-message {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
14
tutorials/todo/todo.html
Normal file
14
tutorials/todo/todo.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<div class="example-todo">
|
||||||
|
<div class="example-button-group">
|
||||||
|
<a class="example-todo-button" data-filter="all">All</a>
|
||||||
|
<a class="example-todo-button" data-filter="incomplete">Incomplete</a>
|
||||||
|
<a class="example-todo-button" data-filter="complete">Complete</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="example-todo-task-list">
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="example-no-tasks">
|
||||||
|
There are no tasks to show.
|
||||||
|
</div>
|
||||||
|
</div>
|
273
tutorials/todo/todo.js
Normal file
273
tutorials/todo/todo.js
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
define([
|
||||||
|
"text!./todo.html",
|
||||||
|
"text!./todo-task.html",
|
||||||
|
"text!./todo-toolbar.html",
|
||||||
|
"text!./todo-dialog.html",
|
||||||
|
"../../src/api/objects/object-utils",
|
||||||
|
"zepto"
|
||||||
|
], function (todoTemplate, taskTemplate, toolbarTemplate, dialogTemplate, utils, $) {
|
||||||
|
/**
|
||||||
|
* @param {mct.MCT} mct
|
||||||
|
*/
|
||||||
|
return function todoPlugin(mct) {
|
||||||
|
var todoType = new mct.Type({
|
||||||
|
metadata: {
|
||||||
|
label: "To-Do List",
|
||||||
|
glyph: "2",
|
||||||
|
description: "A list of things that need to be done."
|
||||||
|
},
|
||||||
|
initialize: function (model) {
|
||||||
|
model.tasks = [
|
||||||
|
{ description: "This is a task." }
|
||||||
|
];
|
||||||
|
},
|
||||||
|
creatable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
function TodoView(domainObject) {
|
||||||
|
this.tasks = domainObject.tasks;
|
||||||
|
this.filterValue = "all";
|
||||||
|
|
||||||
|
this.setTaskStatus = this.setTaskStatus.bind(this);
|
||||||
|
this.selectTask = this.selectTask.bind(this);
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
this.mutableObject = mct.Objects.getMutable(domainObject);
|
||||||
|
this.mutableObject.on('tasks', this.updateTasks.bind(this));
|
||||||
|
=======
|
||||||
|
//If anything on object changes, re-render view
|
||||||
|
this.mutableObject.on("*", this.objectChanged);
|
||||||
|
};
|
||||||
|
|
||||||
|
TodoView.prototype.show = function (container) {
|
||||||
|
var self = this;
|
||||||
|
this.destroy();
|
||||||
|
|
||||||
|
self.$els = $(todoTemplate);
|
||||||
|
self.$buttons = {
|
||||||
|
all: self.$els.find('.example-todo-button-all'),
|
||||||
|
incomplete: self.$els.find('.example-todo-button-incomplete'),
|
||||||
|
complete: self.$els.find('.example-todo-button-complete')
|
||||||
|
};
|
||||||
|
|
||||||
|
$(container).empty().append(self.$els);
|
||||||
|
>>>>>>> origin/api-tutorials
|
||||||
|
|
||||||
|
this.$el = $(todoTemplate);
|
||||||
|
this.$emptyMessage = this.$el.find('.example-no-tasks');
|
||||||
|
this.$taskList = this.$el.find('.example-todo-task-list');
|
||||||
|
this.$el.on('click', '[data-filter]', this.updateFilter.bind(this));
|
||||||
|
this.$el.on('change', 'li', this.setTaskStatus.bind(this));
|
||||||
|
this.$el.on('click', '.example-task-description', this.selectTask.bind(this));
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
this.updateSelection = this.updateSelection.bind(this);
|
||||||
|
mct.selection.on('change', this.updateSelection);
|
||||||
|
}
|
||||||
|
|
||||||
|
TodoView.prototype.show = function (container) {
|
||||||
|
$(container).empty().append(this.$el);
|
||||||
|
this.render();
|
||||||
|
=======
|
||||||
|
self.initialize();
|
||||||
|
self.objectChanged(this.domainObject);
|
||||||
|
|
||||||
|
mct.selection.on('change', self.render);
|
||||||
|
>>>>>>> origin/api-tutorials
|
||||||
|
};
|
||||||
|
|
||||||
|
TodoView.prototype.destroy = function () {
|
||||||
|
this.mutableObject.stopListening();
|
||||||
|
mct.selection.off('change', this.updateSelection);
|
||||||
|
};
|
||||||
|
|
||||||
|
TodoView.prototype.updateSelection = function (selection) {
|
||||||
|
if (selection && selection.length) {
|
||||||
|
this.selection = selection[0].index;
|
||||||
|
} else {
|
||||||
|
this.selection = -1;
|
||||||
|
}
|
||||||
|
this.render();
|
||||||
|
};
|
||||||
|
|
||||||
|
TodoView.prototype.updateTasks = function (tasks) {
|
||||||
|
this.tasks = tasks;
|
||||||
|
this.render();
|
||||||
|
};
|
||||||
|
|
||||||
|
TodoView.prototype.updateFilter = function (e) {
|
||||||
|
this.filterValue = $(e.target).data('filter');
|
||||||
|
this.render();
|
||||||
|
};
|
||||||
|
|
||||||
|
TodoView.prototype.setTaskStatus = function (e) {
|
||||||
|
var $checkbox = $(e.target);
|
||||||
|
var taskIndex = $checkbox.data('taskIndex');
|
||||||
|
var completed = !!$checkbox.prop('checked');
|
||||||
|
this.tasks[taskIndex].completed = completed;
|
||||||
|
this.mutableObject.set('tasks[' + taskIndex + '].checked', completed);
|
||||||
|
};
|
||||||
|
|
||||||
|
TodoView.prototype.selectTask = function (e) {
|
||||||
|
var taskIndex = $(e.target).data('taskIndex');
|
||||||
|
mct.selection.clear();
|
||||||
|
mct.selection.select({index: taskIndex});
|
||||||
|
};
|
||||||
|
|
||||||
|
TodoView.prototype.getFilteredTasks = function () {
|
||||||
|
return this.tasks.filter({
|
||||||
|
all: function () {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
incomplete: function (task) {
|
||||||
|
return !task.completed;
|
||||||
|
},
|
||||||
|
complete: function (task) {
|
||||||
|
return task.completed;
|
||||||
|
}
|
||||||
|
}[this.filterValue]);
|
||||||
|
};
|
||||||
|
|
||||||
|
TodoView.prototype.render = function () {
|
||||||
|
var filteredTasks = this.getFilteredTasks();
|
||||||
|
this.$emptyMessage.toggle(filteredTasks.length === 0);
|
||||||
|
this.$taskList.empty();
|
||||||
|
filteredTasks
|
||||||
|
.forEach(function (task) {
|
||||||
|
var $taskEl = $(taskTemplate),
|
||||||
|
taskIndex = this.tasks.indexOf(task);
|
||||||
|
$taskEl.find('.example-task-checked')
|
||||||
|
.prop('checked', task.completed)
|
||||||
|
.attr('data-task-index', taskIndex);
|
||||||
|
$taskEl.find('.example-task-description')
|
||||||
|
.text(task.description)
|
||||||
|
.toggleClass('selected', taskIndex === this.selection)
|
||||||
|
.attr('data-task-index', taskIndex);
|
||||||
|
|
||||||
|
this.$taskList.append($taskEl);
|
||||||
|
}, this);
|
||||||
|
};
|
||||||
|
|
||||||
|
function TodoToolbarView(domainObject) {
|
||||||
|
this.tasks = domainObject.tasks;
|
||||||
|
this.mutableObject = mct.Objects.getMutable(domainObject);
|
||||||
|
|
||||||
|
this.handleSelectionChange = this.handleSelectionChange.bind(this);
|
||||||
|
|
||||||
|
this.mutableObject.on('tasks', this.updateTasks.bind(this));
|
||||||
|
mct.selection.on('change', this.handleSelectionChange);
|
||||||
|
this.$el = $(toolbarTemplate);
|
||||||
|
this.$remove = this.$el.find('.example-remove');
|
||||||
|
this.$el.on('click', '.example-add', this.addTask.bind(this));
|
||||||
|
this.$el.on('click', '.example-remove', this.removeTask.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
TodoToolbarView.prototype.updateTasks = function (tasks) {
|
||||||
|
this.tasks = tasks;
|
||||||
|
};
|
||||||
|
|
||||||
|
TodoToolbarView.prototype.addTask = function () {
|
||||||
|
var $dialog = $(dialogTemplate),
|
||||||
|
view = {
|
||||||
|
show: function (container) {
|
||||||
|
$(container).append($dialog);
|
||||||
|
},
|
||||||
|
destroy: function () {}
|
||||||
|
};
|
||||||
|
|
||||||
|
mct.dialog(view, "Add a Task").then(function () {
|
||||||
|
var description = $dialog.find('input').val();
|
||||||
|
this.tasks.push({description: description});
|
||||||
|
this.mutableObject.set('tasks', this.tasks);
|
||||||
|
}.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
TodoToolbarView.prototype.removeTask = function () {
|
||||||
|
if (this.selection >= 0) {
|
||||||
|
this.tasks.splice(this.selection, 1);
|
||||||
|
this.mutableObject.set('tasks', this.tasks);
|
||||||
|
mct.selection.clear();
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TodoToolbarView.prototype.show = function (container) {
|
||||||
|
var self = this;
|
||||||
|
this.destroy();
|
||||||
|
this.$els = $(toolbarTemplate);
|
||||||
|
this.render();
|
||||||
|
$(container).append(this.$els);
|
||||||
|
};
|
||||||
|
|
||||||
|
TodoToolbarView.prototype.render = function () {
|
||||||
|
var self = this;
|
||||||
|
var $els = this.$els;
|
||||||
|
|
||||||
|
self.mutableObject = mct.Objects.getMutable(this.domainObject);
|
||||||
|
|
||||||
|
var $add = $els.find('a.example-add');
|
||||||
|
var $remove = $els.find('a.example-remove');
|
||||||
|
|
||||||
|
$add.on('click', function () {
|
||||||
|
var $dialog = $(dialogTemplate),
|
||||||
|
view = {
|
||||||
|
show: function (container) {
|
||||||
|
$(container).append($dialog);
|
||||||
|
},
|
||||||
|
destroy: function () {}
|
||||||
|
};
|
||||||
|
|
||||||
|
new mct.Dialog(view, "Add a Task").show().then(function () {
|
||||||
|
var description = $dialog.find('input').val();
|
||||||
|
var tasks = self.mutableObject.get('tasks');
|
||||||
|
tasks.push({ description: description });
|
||||||
|
self.mutableObject.set('tasks', tasks);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$remove.on('click', function () {
|
||||||
|
var index = mct.selection.selected()[0].index;
|
||||||
|
if (index !== undefined) {
|
||||||
|
var tasks = self.mutableObject.get('tasks').filter(function (t, i) {
|
||||||
|
return i !== index;
|
||||||
|
});
|
||||||
|
self.mutableObject.set("tasks", tasks);
|
||||||
|
self.mutableObject.set("selected", undefined);
|
||||||
|
mct.selection.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
self.$remove = $remove;
|
||||||
|
self.handleSelectionChange();
|
||||||
|
mct.selection.on('change', self.handleSelectionChange);
|
||||||
|
};
|
||||||
|
|
||||||
|
TodoToolbarView.prototype.handleSelectionChange = function () {
|
||||||
|
var selected = mct.selection.selected();
|
||||||
|
if (this.$remove) {
|
||||||
|
this.$remove.toggle(selected.length > 0);
|
||||||
|
}
|
||||||
|
this.render();
|
||||||
|
};
|
||||||
|
|
||||||
|
TodoToolbarView.prototype.destroy = function () {
|
||||||
|
mct.selection.off('change', this.handleSelectionChange);
|
||||||
|
this.mutableObject.stopListening();
|
||||||
|
};
|
||||||
|
|
||||||
|
mct.type('example.todo', todoType);
|
||||||
|
mct.view(mct.regions.main, {
|
||||||
|
view: function (domainObject) {
|
||||||
|
return new TodoView(domainObject);
|
||||||
|
},
|
||||||
|
canView: todoType.check.bind(todoType)
|
||||||
|
});
|
||||||
|
|
||||||
|
mct.view(mct.regions.toolbar, {
|
||||||
|
view: function (domainObject) {
|
||||||
|
return new TodoToolbarView(domainObject);
|
||||||
|
},
|
||||||
|
canView: todoType.check.bind(todoType)
|
||||||
|
});
|
||||||
|
|
||||||
|
return mct;
|
||||||
|
};
|
||||||
|
});
|
Reference in New Issue
Block a user