Compare commits

...

114 Commits

Author SHA1 Message Date
7ce9bd969a [API] Telemetry JSdoc 2016-09-06 10:14:04 -07:00
8cafd2da7e Merge remote-tracking branch 'origin/api-tutorial/telemetry' into api-1110 2016-09-06 10:06:30 -07:00
6264ab75f3 Merge remote-tracking branch 'origin/api-tutorials' into api-1110
Conflicts:
	src/MCT.js
	src/api/composition/CompositionCollection.js
	src/api/composition/DefaultCompositionProvider.js
	src/api/objects/MutableObject.js
2016-09-06 10:03:59 -07:00
7a5cad20ec [API] Add JSDoc for Dialog 2016-09-06 09:58:08 -07:00
4de069b393 [API] JSDoc for Selection 2016-09-06 09:54:51 -07:00
70abd5c1f9 [API] Document start event 2016-09-06 09:47:06 -07:00
2a3a61da86 [API] Fix event memberofs in TimeConductor 2016-09-06 09:45:24 -07:00
018bd022cc [API] Document View API 2016-09-06 09:42:04 -07:00
4739b36bc3 [API] Add metadata to View jsdoc 2016-09-06 09:14:04 -07:00
c9b1035a6d [API] Document Type.check 2016-09-06 09:13:01 -07:00
6768328475 [API] Document MutableObject 2016-09-06 08:43:01 -07:00
60c179eac3 [API] Add missing parameter names 2016-09-06 08:32:18 -07:00
a20e8d69b5 [API] More Objects doc 2016-09-06 08:31:42 -07:00
1abcb248fe [API] Include API.md as main page 2016-09-06 08:13:26 -07:00
a8151f5f22 [API] Document CompositionProvider 2016-09-06 08:09:03 -07:00
cdf21f3763 [API] Mark Composition as instance method 2016-09-06 07:58:52 -07:00
341bceb4e2 [API] Document composition API 2016-09-06 07:57:44 -07:00
0470a02272 [API] Reference ObjectAPI 2016-09-02 16:04:38 -07:00
e3dc26c130 [API] Clean up docs 2016-09-02 15:25:28 -07:00
96c3d1cac2 [API] Use JSDoc config 2016-09-02 15:19:20 -07:00
2af778145d [API] Clean up JSDoc slightly 2016-09-02 15:16:11 -07:00
5743eeb33a [API] Reference EventEmitter 2016-09-02 15:03:12 -07:00
f06f714bdc [API] Document some public fields 2016-09-02 15:02:18 -07:00
d592bd1035 [API] Give up on borrows-style documentation 2016-09-02 14:58:06 -07:00
b5f62541ce [API] Add watch for API docs 2016-09-02 14:54:27 -07:00
33ced4bccf [API] Expose MCT on openmct 2016-09-01 15:51:25 -07:00
e37510dbab [API] Ignore internal API after processing 2016-09-01 15:13:52 -07:00
f27c41014d [API] JSDoc for openmct.start() 2016-09-01 14:50:18 -07:00
bd796f2beb [API] Simple constructor documentation 2016-09-01 14:44:24 -07:00
bcc32c76d0 [API] Module-level JSDoc 2016-09-01 14:42:55 -07:00
ff2ec6690a [API] Rename module to openmct 2016-09-01 14:35:52 -07:00
1e0fb3611d [API] Render API docs to HTML 2016-09-01 12:34:54 -07:00
1d4f36a7d9 [API] Include JSDoc for mct namespace only 2016-08-26 11:48:27 -07:00
0f96fbdd62 [API] Treat mct as a namespace 2016-08-26 11:31:56 -07:00
e05fb57fe4 [API] Quasi-sensible JSDoc starting point 2016-08-26 09:47:50 -07:00
185cdcab08 [API] Begin adding mct.js
...which will provide an instance of OpenMCT at startup,
as well as house documentation for entry point to public API.
2016-08-26 09:31:12 -07:00
50ccad5aaa [API] Rename MCT to OpenMCT 2016-08-26 09:26:12 -07:00
6a23df9454 [API] Add JSDoc for MCT 2016-08-25 14:54:07 -07:00
ab5b1d3754 [API] Add JSDoc task 2016-08-25 13:49:21 -07:00
b309f26b56 [API] Add gulp-jsdoc-to-markdown dep
...to aid in generating API docs as a measure of API completeness
and consistency, for #1110 and #1111
2016-08-25 13:38:44 -07:00
b4dc50295c Merge pull request #1131 from nasa/open1094
Resolve synchronization issues with MutableObject
2016-08-25 13:26:50 -07:00
382dde300a Merge remote-tracking branch 'origin/api-tutorials' into open1094
Conflicts:
	index.html
	tutorials/todo/todo.js
2016-08-25 13:25:54 -07:00
02aa08a3ef Merge pull request #1121 from nasa/api-containment
[API] Containment
2016-08-25 13:19:14 -07:00
c95d9c7956 Merge pull request #1107 from nasa/api-type-forms
[API] Handle forms with a "properties" region
2016-08-25 13:17:26 -07:00
b1b8df4d87 Use MutationCapability 2016-08-23 13:57:12 +01:00
bd3c6665fb Added bridge between old and new event models 2016-08-22 16:14:01 +01:00
10e90519c0 Tidy todo views, remove unnecessary code 2016-08-22 16:10:45 +01:00
d341a8be97 Selection changes include new selection 2016-08-22 16:10:45 +01:00
8c439d8109 Adding compatibility between old and new style mutation events 2016-08-22 07:59:17 +01:00
9c88b7ce1d Removed setters from MutableObject and fixed non-working todo list tutorial
Refactoring MutableObject

Fixed non-working todo example
2016-08-19 13:39:23 -07:00
2463e4d59f [API] Update Dialog API usage 2016-08-12 12:54:39 -07:00
d73c505bea [API] Fix typo, add missing this 2016-08-12 11:17:00 -07:00
831ecc59d9 [API] Wire in canContain via policy 2016-08-12 10:24:59 -07:00
1de26d3c5d [API] Throw error on containment violation 2016-08-12 09:30:02 -07:00
11409ce509 [API] Add containment methods 2016-08-12 09:27:46 -07:00
93872ce074 [API] Expose Dialog as constructor
...and use it that way from todo plugin
2016-08-11 16:04:26 -07:00
8861644f2d [API] Adjust Dialog API
...to allow OK button to be enabled/disabled.
2016-08-11 16:02:04 -07:00
d4948f771b Merge branch 'api-todo-update' into api-type-forms 2016-08-11 15:31:11 -07:00
8295a0bed1 [API] Update todo tutorial
...to expect new domain object API (instead of explicitly
wrapping it.)
2016-08-11 15:29:46 -07:00
0656a298da [API] Remove test usage of properties region 2016-08-04 14:00:48 -07:00
fe2ce91d50 [API] Show a custom view in dialog 2016-07-28 16:16:23 -07:00
14f30b2489 [API] Restrict dialog overrides
...to those domain objects which have some view for the
properties region registered.
2016-07-28 16:05:02 -07:00
62d90a8114 [API] Show dialog via mct 2016-07-28 15:57:15 -07:00
87682607a5 [API] Rename dependency in adapter layer 2016-07-28 15:53:07 -07:00
7bf265b478 [API] Move mct service up 2016-07-28 15:52:52 -07:00
1d31fe8d02 [API] Override dialogService in actions
An ugly hack to allow dialogs to be shown for Save As
and Edit Properties, without requiring form generation.
This will permit views to be shown instead in certain
cases, https://github.com/nasa/openmct/pull/999#issuecomment-236045158
2016-07-28 15:48:30 -07:00
bfdbc71e40 [API] Define a properties region 2016-07-28 15:34:03 -07:00
1147f3aa47 tutorials: support arbitrary hosting directory (#1097)
* Include all bundles in artifact

change bundle registry such that all bundles are immediately registered,
but must be specifically enabled.  A default registry class enables bundles
that make sense for demonstration purposes.

Added methods to the registry to allow enabling and disabling of bundles
without having to load additional files.

* support alternate asset/worker paths

Change the gulp glob for assets to copy over a more minimal
set of files-- only css, fonts, and images.  Results in a
smaller distributable archive.

Update stylesheet loader to use a constant for the assets path.

This can be customized at run time via MCT.setAssetPath() to
allow MCT to be hosted in various locations.

Update worker loader to support loading workers from blobs to
support packaging as standalone file.

* Load templates via requirejs

* [gulp] lazy-require where reasonable

Require things right before starting tasks to reduce gulp start up time.

* document setAssetPath
2016-07-25 14:38:44 -07:00
719f9f45e8 [API] Add documentation for selection state (#1096) 2016-07-22 14:09:31 -07:00
95ef70a24c [API] Use selection state from toolbar (#1070)
* [API] Allow selection

* [API] Keep in sync using model

* [API] Add selection as EventEmitter

* [API] Use selection from ToDo tutorial

* [API] Restore selection functionality
2016-07-22 13:56:45 -07:00
d5aa998b4c [API] Draft Composition API (#1068)
* [Objects] util for equality checking

Add a method for checking object equality, useful for other services.

* [Composition] Draft Composition API

Draft composition API.  Composition collections provide an observable
for watching and mutating the composition of an object.

Composition providers implement the loading and modification of composition.

The default composition provider uses the composition attribute of
domain objects, while allowing other providers to implement their
own loading and mutation behavior.

* add todo about event listener bindings

* [Type] Add form property for defining form fields

* [tutorial] Add Composition tutorial

* provider doesn't have to implement events, load returns array of children

* use new composition in old api

* correct key name

* Override instantiate to provide model ids

Override instantiate in public API adapter to prevent making changes to
platform code.  Instantiate now passes the id of the domain object with the
model so that capabilities can convert to a new-style domain object and use
that to detect functionality.

* Implement mutation capability with decorator

Implementation mutation capability override with decorator to adapter code
outside of platform.  Capability override ensures that models are kept in
sync even though they are no longer shared objects.

* override composition cleanly

Override composition capability without making changes inside platform.

* cleanup after temporary collections

* remove unused try/catch
2016-07-22 13:53:03 -07:00
7890fcae69 tutorial consistency . (#1079)
* [API] use new-style objects consistently

* rewrite todo tutorial in test-api.html

* [API] Add API doc, update object API

* [Tutorials] Rename tutorials, remove old

* Fix Links

* updates

* initial

* hope this works

* Object Utils always return new objects instead of mutating existing objects

* keep domain object model in-sync when listening

Keep the domain object model in sync with the latest version when
listening for mutation events.

* Remove old-style plugins

* Update views to use new API

* Tidy Code

* Update API Docs

* Add Plugin API and Example
2016-07-21 14:39:02 -07:00
18843cee48 [API] Change approach to applies-to checking (#1072)
* [API] Allow selection

* [API] Keep in sync using model

* [API] Add selection as EventEmitter

* [API] Use selection from ToDo tutorial

* [API] Add appliesTo-style method

* [API] Remove destroy method, simplify show

* [View] Return a no-op

* [API] Use new applies-to checking

* [API] Rename TodoView to TodoRenderer

* [API] Rewire views

* [API] Wire up so that things work

* [API] Begin adding container

...to attempt to give views something to listen to for destroy-like
events

* [API] Begin using regions...

* [API] Begin working through Region stuff

* [API] Revise Region API

...for similarity with Marionette,
https://github.com/nasa/openmct/pull/1072#issuecomment-230902986

* [API] Begin separating View, ViewDefinition

* [API] Finish separating View/ViewDefinition

* [API] Update MCTView

...to reflect updates to Region/View/ViewDefinition APIs

* [API] Simplify View API

...merging closely-related populate/show methods, and restoring
compatibility with todo tutorial

* [API] Wire in from todo tutorial plugin

* [API] Switch back to region constants

* [API] Update method signature, add JSDoc

* [API] Update variable name

* [API] Remove unnecessary separate regions file

* [API] Relocate Region; not external api

* [API] Revert changes to api.js

...as these ended up becoming entirely superficial
2016-07-20 13:46:03 -07:00
1879c122c7 Mutation API (#1074)
* [API] Allow selection

* [API] Keep in sync using model

* [API] Add selection as EventEmitter

* [API] Use selection from ToDo tutorial

* Object events prototype

* Added examples

* Transitional API

* Modified todo list code to work with new setters

* [API] Removed emitting of events on container when property changes value to remove ambiguity. Listeners must be listening to the same path used in the setter to catch changes
2016-07-07 14:30:45 -07:00
d7ddb96c4e [API] UMD Packaging (#1078)
* [Bundle] load filter with requirejs

* [Build] Use almond, wrap in UMD

Use almond for built version of application and wrap in UMD so that
it supports requirejs, commonjs, and basic browser loading.

* [Main] Can choose where to load app

MCT.run allows you to specify a dom element to load application
within.  If element is not specified, will use body.

* [MCT] set class on injected div

Set class on injected div so extra markup is not required.

* [Build] Re-enable optimize

* Add minimal bootstrap example
2016-07-07 14:25:23 -07:00
bccd018d97 Telemetry Draft 2016-07-01 10:26:49 -07:00
b55668426d Merge pull request #1062 from nasa/tc-redux
[Time Conductor] V2 Public API
2016-07-01 10:22:16 -07:00
5b656faa9d Added tests 2016-07-01 10:22:44 -07:00
8d2c489fa9 [TimeConductor] Set bounds on timeSystem Change
Always set bounds on timeSystem change as not having valid bounds would
put views in inconsistent states.
2016-07-01 10:22:44 -07:00
4366b0870d [Time Conductor] API redesign. Initial commit of V2 public API. Addresses #933 2016-07-01 10:22:44 -07:00
47a543beb7 Merge pull request #1067 from nasa/api-css
[API] Remove stylesheet from example
2016-07-01 10:17:36 -07:00
06f87c1472 Merge pull request #1029 from nasa/api-toolbar-add-only
[API Prototype] Add toolbar
2016-07-01 10:13:29 -07:00
c9c41cdcc8 Merge remote-tracking branch 'origin/api-tutorials' into api-toolbar-add-only
Conflicts:
	src/MCT.js
2016-07-01 10:10:33 -07:00
14a56ea17e Merge pull request #1028 from nasa/api-view
[API Prototype] Support imperative view registration
2016-07-01 10:09:29 -07:00
b2e7db71cc Merge remote-tracking branch 'origin/api-tutorials' into api-view
Conflicts:
	src/MCT.js
	src/api/api.js
2016-07-01 10:08:05 -07:00
d51e6bfd92 Merge pull request #1030 from nasa/api-tutorial/objects
Api tutorial/objects
2016-07-01 10:03:42 -07:00
370b515c23 [API] Synchronize view to model 2016-06-17 14:21:37 -07:00
4a50f325cb [API] Allow tasks to be added 2016-06-17 14:18:51 -07:00
dbe6a4efc1 [API] Title dialog 2016-06-17 14:05:00 -07:00
13920d8802 [API] Resolve/reject from dialog 2016-06-17 14:00:45 -07:00
b6a8c514aa [API] Show dialog from toolbar 2016-06-17 13:51:15 -07:00
e4a4704baa [API] Listen to add/remove buttons 2016-06-17 13:41:59 -07:00
be0029e59a [API] Get todo toolbar to look right 2016-06-17 13:38:54 -07:00
9cb273ef0a [API] Get registered toolbar to appear 2016-06-17 13:30:08 -07:00
eec9b1cf4c [API] Support distinct region registration 2016-06-17 13:10:24 -07:00
1f96e84542 [API] Override template to allow toolbar injection 2016-06-17 12:16:46 -07:00
c289a27305 [API] Begin adding toolbar 2016-06-17 11:43:49 -07:00
c944080790 [API] Remove stylesheet from example
No need to provide custom API for this.
2016-06-17 11:27:04 -07:00
96316de6e4 [API] Update view API 2016-06-17 11:16:08 -07:00
2240a87ddc [API] Move view off of type 2016-06-17 10:53:56 -07:00
d891affe48 [API] Move view off of type 2016-06-17 10:21:00 -07:00
21a618d1ce Merge branch 'api-type-proto' into api-view 2016-06-17 10:19:44 -07:00
580a4e52b5 Merge branch 'api-tutorials' into api-type-driven 2016-06-07 14:01:08 -07:00
4ca2f51d5e [API] Use subclass style 2016-05-27 17:08:25 -07:00
86ac80ddbd [API] Persist mutations 2016-05-27 16:56:08 -07:00
0525ba6b0b [API] Check/uncheck todos 2016-05-27 16:55:10 -07:00
a79e958ffc [API] Show tasks from todo 2016-05-27 16:46:06 -07:00
03cb0ccb57 [API] Get View to render 2016-05-27 16:36:55 -07:00
7205faa6bb [API] Add adapter bundle 2016-05-27 16:27:47 -07:00
136f2ae785 [API] Add MCTView directive as an adapter 2016-05-27 16:19:20 -07:00
a07e2fb8e5 [API] Implement View 2016-05-27 16:08:43 -07:00
55b531bdeb [API] Sketch in view instantiation 2016-05-27 15:49:16 -07:00
7ece5897e8 [API] Begin adding View 2016-05-27 15:31:54 -07:00
a29c7a6eab [API] Deangularize todo templates 2016-05-27 13:46:30 -07:00
71 changed files with 3756 additions and 427 deletions

114
API.md Normal file
View 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.

View File

@ -20,6 +20,7 @@
"FileSaver.js": "^0.0.2",
"zepto": "^1.1.6",
"eventemitter3": "^1.2.0",
"lodash": "3.10.1"
"lodash": "3.10.1",
"almond": "~0.3.2"
}
}

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

View File

@ -36,7 +36,7 @@ define([
legacyRegistry
) {
"use strict";
legacyRegistry.register("example/notifications", {
legacyRegistry.register("example/msl-adapter", {
"name" : "Mars Science Laboratory Data Adapter",
"extensions" : {
"types": [

View File

@ -21,40 +21,33 @@
*****************************************************************************/
/*global require,__dirname*/
var gulp = require('gulp'),
requirejsOptimize = require('gulp-requirejs-optimize'),
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'),
fs = require('fs'),
git = require('git-rev-sync'),
moment = require('moment'),
merge = require('merge-stream'),
project = require('./package.json'),
_ = require('lodash'),
paths = {
main: 'main.js',
dist: 'dist',
assets: 'dist/assets',
scss: ['./platform/**/*.scss', './example/**/*.scss'],
assets: [
'./{example,platform}/**/*.{css,css.map,png,svg,ico,woff,eot,ttf}'
],
scripts: [ 'main.js', 'platform/**/*.js', 'src/**/*.js' ],
specs: [ 'platform/**/*Spec.js', 'src/**/*Spec.js' ],
static: [
'index.html',
'platform/**/*',
'example/**/*',
'bower_components/**/*'
]
},
options = {
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,
wrapShim: true
},
@ -63,7 +56,6 @@ var gulp = require('gulp'),
singleRun: true
},
sass: {
includePaths: bourbon.includePaths,
sourceComments: true
},
replace: {
@ -77,6 +69,8 @@ var gulp = require('gulp'),
};
gulp.task('scripts', function () {
var requirejsOptimize = require('gulp-requirejs-optimize');
var replace = require('gulp-replace-task');
return gulp.src(paths.main)
.pipe(sourcemaps.init())
.pipe(requirejsOptimize(options.requirejsOptimize))
@ -86,10 +80,16 @@ gulp.task('scripts', function () {
});
gulp.task('test', function (done) {
var karma = require('karma');
new karma.Server(options.karma, done).start();
});
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: '.'})
.pipe(sourcemaps.init())
.pipe(sass(options.sass).on('error', sass.logError))
@ -103,6 +103,9 @@ gulp.task('stylesheets', function () {
});
gulp.task('lint', function () {
var jshint = require('gulp-jshint');
var merge = require('merge-stream');
var nonspecs = paths.specs.map(function (glob) {
return "!" + glob;
}),
@ -117,6 +120,8 @@ gulp.task('lint', function () {
});
gulp.task('checkstyle', function () {
var jscs = require('gulp-jscs');
return gulp.src(paths.scripts)
.pipe(jscs())
.pipe(jscs.reporter())
@ -124,18 +129,35 @@ gulp.task('checkstyle', function () {
});
gulp.task('fixstyle', function () {
var jscs = require('gulp-jscs');
return gulp.src(paths.scripts, { base: '.' })
.pipe(jscs({ fix: true }))
.pipe(gulp.dest('.'));
});
gulp.task('static', ['stylesheets'], function () {
return gulp.src(paths.static, { base: '.' })
gulp.task('assets', ['stylesheets'], function () {
return gulp.src(paths.assets)
.pipe(gulp.dest(paths.dist));
});
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 () {
@ -143,9 +165,9 @@ gulp.task('serve', function () {
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' ]);

View File

@ -31,16 +31,13 @@
<script type="text/javascript">
require(['main'], function (mct) {
require([
'./tutorials/grootprovider/groots',
'./tutorials/todo/todo',
'./tutorials/todo/bundle',
'./example/imagery/bundle',
'./example/eventGenerator/bundle',
'./example/generator/bundle',
], function (grootify, todoPlugin) {
grootify(mct);
todoPlugin(mct);
mct.start();
'./example/generator/bundle'
], function (todoPlugin) {
mct.install(todoPlugin);
mct.run();
})
});
</script>
@ -55,7 +52,5 @@
<div class="l-splash-holder s-splash-holder">
<div class="l-splash s-splash"></div>
</div>
<div ng-view></div>
</body>
</html>

View File

@ -1,12 +1,13 @@
{
"source": {
"include": [
"platform/"
"src/"
],
"includePattern": "platform/.+\\.js$",
"includePattern": "src/.+\\.js$",
"excludePattern": ".+\\Spec\\.js$|lib/.+"
},
"plugins": [
"plugins/markdown"
"plugins/markdown",
"build/jsdoc/plugins/mct-only"
]
}

57
main.js
View File

@ -56,56 +56,31 @@ requirejs.config({
},
"zepto": {
"exports": "Zepto"
},
"lodash": {
"exports": "lodash"
}
}
});
define([
'./platform/framework/src/Main',
'legacyRegistry',
'./src/MCT',
'./platform/framework/bundle',
'./platform/core/bundle',
'./platform/representation/bundle',
'./platform/commonUI/about/bundle',
'./platform/commonUI/browse/bundle',
'./platform/commonUI/edit/bundle',
'./platform/commonUI/dialog/bundle',
'./platform/commonUI/formats/bundle',
'./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, MCT) {
'./src/defaultRegistry',
'./src/MCT'
], function (Main, defaultRegistry, MCT) {
var mct = new MCT();
mct.legacyRegistry = legacyRegistry;
mct.run = mct.start;
mct.legacyRegistry = defaultRegistry;
mct.run = function (domElement) {
if (!domElement) { domElement = document.body; }
var appDiv = document.createElement('div');
appDiv.setAttribute('ng-view', '');
appDiv.className = 'user-environ';
domElement.appendChild(appDiv);
mct.start();
};
mct.on('start', function () {
return new Main().run(legacyRegistry);
return new Main().run(defaultRegistry);
});
return mct;

64
object-provider.html Normal file
View 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>

View File

@ -12,8 +12,11 @@
"git-rev-sync": "^1.4.0",
"glob": ">= 3.0.0",
"gulp": "^3.9.0",
"gulp-concat": "^2.6.0",
"gulp-jscs": "^3.0.2",
"gulp-jsdoc-to-markdown": "^1.2.2",
"gulp-jshint": "^2.0.0",
"gulp-markdown": "^1.2.0",
"gulp-rename": "^1.2.2",
"gulp-replace-task": "^0.11.0",
"gulp-requirejs-optimize": "^0.3.1",
@ -47,7 +50,7 @@
"test": "karma start --single-run",
"jshint": "jshint platform example",
"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'",
"docs": "npm run jsdoc ; npm run otherdoc",
"prepublish": "node ./node_modules/bower/bin/bower install && node ./node_modules/gulp/bin/gulp.js install"

View File

@ -39,6 +39,7 @@ define([
"text!./res/templates/items/items.html",
"text!./res/templates/browse/object-properties.html",
"text!./res/templates/browse/inspector-region.html",
"text!./res/templates/view-object.html",
'legacyRegistry'
], function (
BrowseController,
@ -59,6 +60,7 @@ define([
itemsTemplate,
objectPropertiesTemplate,
inspectorRegionTemplate,
viewObjectTemplate,
legacyRegistry
) {
@ -129,7 +131,7 @@ define([
"representations": [
{
"key": "view-object",
"templateUrl": "templates/view-object.html"
"template": viewObjectTemplate
},
{
"key": "browse-object",

View File

@ -48,6 +48,7 @@ define([
"./src/directives/MCTSplitPane",
"./src/directives/MCTSplitter",
"./src/directives/MCTTree",
"./src/filters/ReverseFilter.js",
"text!./res/templates/bottombar.html",
"text!./res/templates/controls/action-button.html",
"text!./res/templates/controls/input-filter.html",
@ -96,6 +97,7 @@ define([
MCTSplitPane,
MCTSplitter,
MCTTree,
ReverseFilter,
bottombarTemplate,
actionButtonTemplate,
inputFilterTemplate,
@ -146,7 +148,8 @@ define([
"depends": [
"stylesheets[]",
"$document",
"THEME"
"THEME",
"ASSETS_PATH"
]
},
{
@ -158,7 +161,7 @@ define([
],
"filters": [
{
"implementation": "filters/ReverseFilter.js",
"implementation": ReverseFilter,
"key": "reverse"
}
],
@ -404,6 +407,11 @@ define([
"key": "THEME",
"value": "unspecified",
"priority": "fallback"
},
{
"key": "ASSETS_PATH",
"value": ".",
"priority": "fallback"
}
],
"containers": [

View File

@ -38,7 +38,7 @@ define(
* @param $document Angular's jqLite-wrapped document element
* @param {string} activeTheme the theme in use
*/
function StyleSheetLoader(stylesheets, $document, activeTheme) {
function StyleSheetLoader(stylesheets, $document, activeTheme, assetPath) {
var head = $document.find('head'),
document = $document[0];
@ -47,6 +47,7 @@ define(
// Create a link element, and construct full path
var link = document.createElement('link'),
path = [
assetPath,
stylesheet.bundle.path,
stylesheet.bundle.resources,
stylesheet.stylesheetUrl

View File

@ -42,11 +42,19 @@ define(
function addWorker(worker) {
var key = worker.key;
if (!workerUrls[key]) {
workerUrls[key] = [
worker.bundle.path,
worker.bundle.sources,
worker.scriptUrl
].join("/");
if (worker.scriptUrl) {
workerUrls[key] = [
worker.bundle.path,
worker.bundle.sources,
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;
}
}

View File

@ -27,6 +27,9 @@ define([
"./src/controllers/TableOptionsController",
'../../commonUI/regions/src/Region',
'../../commonUI/browse/src/InspectorRegion',
"text!./res/templates/table-options-edit.html",
"text!./res/templates/rt-table.html",
"text!./res/templates/historical-table.html",
"legacyRegistry"
], function (
MCTTable,
@ -35,6 +38,9 @@ define([
TableOptionsController,
Region,
InspectorRegion,
tableOptionsEditTemplate,
rtTableTemplate,
historicalTableTemplate,
legacyRegistry
) {
/**
@ -128,7 +134,7 @@ define([
"name": "Historical Table",
"key": "table",
"glyph": "\ue604",
"templateUrl": "templates/historical-table.html",
"template": historicalTableTemplate,
"needs": [
"telemetry"
],
@ -139,7 +145,7 @@ define([
"name": "Real-time Table",
"key": "rt-table",
"glyph": "\ue620",
"templateUrl": "templates/rt-table.html",
"template": rtTableTemplate,
"needs": [
"telemetry"
],
@ -157,7 +163,7 @@ define([
"representations": [
{
"key": "table-options-edit",
"templateUrl": "templates/table-options-edit.html"
"template": tableOptionsEditTemplate
}
],
"stylesheets": [

View File

@ -28,6 +28,7 @@ define([
"text!./res/templates/search-item.html",
"text!./res/templates/search.html",
"text!./res/templates/search-menu.html",
"text!./src/services/GenericSearchWorker.js",
'legacyRegistry'
], function (
SearchController,
@ -37,6 +38,7 @@ define([
searchItemTemplate,
searchTemplate,
searchMenuTemplate,
searchWorkerText,
legacyRegistry
) {
@ -114,7 +116,7 @@ define([
"workers": [
{
"key": "genericSearchWorker",
"scriptUrl": "services/GenericSearchWorker.js"
"scriptText": searchWorkerText
}
]
}

50
plugin-example.html Normal file
View 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>

View File

@ -24,10 +24,27 @@ define(function () {
function BundleRegistry() {
this.bundles = {};
this.knownBundles = {};
}
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) {
@ -42,8 +59,14 @@ define(function () {
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.knownBundles[path];
};
return BundleRegistry;

View File

@ -1,16 +1,61 @@
define([
'EventEmitter',
'legacyRegistry',
'uuid',
'./api/api',
'./api/objects/bundle'
'text!./adapter/templates/edit-object-replacement.html',
'./Selection',
'./api/objects/object-utils',
'./api/TimeConductor'
], function (
EventEmitter,
legacyRegistry,
api
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: {} };
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);
@ -18,19 +63,160 @@ define([
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;
this.legacyBundle.extensions.types =
this.legacyBundle.extensions.types || [];
this.legacyBundle.extensions.types.push(legacyDef);
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
View 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;
});

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

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

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

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

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

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

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

View File

@ -0,0 +1,49 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
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;
}
);

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

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

View File

@ -1,6 +1,7 @@
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
@ -9,14 +10,29 @@ define(function () {
*/
/**
* A Type describes a kind of domain object that may appear or be
* created within Open MCT.
*
* @param {TypeDefinition} definition
* @constructor
* @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.
@ -27,6 +43,7 @@ define(function () {
def.name = this.definition.metadata.label;
def.glyph = this.definition.metadata.glyph;
def.description = this.definition.metadata.description;
def.properties = this.definition.form;
if (this.definition.initialize) {
def.model = {};

91
src/api/View.js Normal file
View 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
View 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;
});

View File

@ -1,12 +1,23 @@
define([
'./Type',
'./objects/ObjectAPI'
'./TimeConductor',
'./View',
'./objects/ObjectAPI',
'./composition/CompositionAPI',
'./ui/Dialog'
], function (
Type,
ObjectAPI
TimeConductor,
View,
ObjectAPI,
CompositionAPI,
Dialog
) {
return {
Type: Type,
Objects: ObjectAPI
View: View,
Objects: ObjectAPI,
Composition: CompositionAPI,
Dialog: Dialog
};
});

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

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

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

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

View File

@ -1,15 +1,54 @@
define([
'./object-utils',
'./ObjectAPI'
'./ObjectAPI',
'./objectEventEmitter'
], function (
utils,
ObjectAPI
ObjectAPI,
objectEventEmitter
) {
function ObjectServiceProvider(objectService, instantiate) {
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),
@ -30,14 +69,14 @@ define([
var keyString = utils.makeKeyString(key);
return this.objectService.getObjects([keyString])
.then(function (results) {
var model = JSON.parse(JSON.stringify(results[keyString].getModel()));
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, objectService) {
function LegacyObjectAPIInterceptor(ROOTS, instantiate, topic, objectService) {
this.getObjects = function (keys) {
var results = {},
promises = keys.map(function (keyString) {
@ -56,7 +95,7 @@ define([
};
ObjectAPI._supersecretSetFallbackProvider(
new ObjectServiceProvider(objectService, instantiate)
new ObjectServiceProvider(objectService, instantiate, topic)
);
ROOTS.forEach(function (r) {

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

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

View File

@ -1,25 +1,24 @@
define([
'lodash',
'./object-utils'
'./object-utils',
'./MutableObject'
], function (
_,
utils
utils,
MutableObject
) {
/**
Object API. Intercepts the existing object API while also exposing
A new Object API.
MCT.objects.get('mine')
.then(function (root) {
console.log(root);
MCT.objects.getComposition(root)
.then(function (composition) {
console.log(composition)
})
});
*/
/**
* Utilities for loading, saving, and manipulating domain objects.
* @interface ObjectAPI
* @memberof module:openmct
* @implements {module:openmct.ObjectProvider}
*/
var Objects = {},
ROOT_REGISTRY = [],
PROVIDER_REGISTRY = {},
@ -29,8 +28,6 @@ define([
FALLBACK_PROVIDER = p;
};
// Root provider is hardcoded in; can't be skipped.
var RootProvider = {
'get': function () {
@ -48,12 +45,63 @@ define([
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',
@ -75,10 +123,24 @@ define([
};
});
/**
* 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 (
@ -88,5 +150,28 @@ define([
});
};
/**
* 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;
});

View File

@ -16,85 +16,39 @@ 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.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.
* `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.
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
* MCT.objects.save(object) -> returns promise that is resolved when object has been saved
* MCT.objects.delete(object) -> returns promise that is resolved when object has been deleted
## Configuration Example: Adding a groot
The following example adds a new root object for groot and populates it with
some pieces of groot.
```javascript
var ROOT_KEY = {
namespace: 'groot',
identifier: 'groot'
};
var GROOT_ROOT = {
name: 'I am groot',
type: 'folder',
composition: [
{
namespace: 'groot',
identifier: 'arms'
},
{
namespace: 'groot',
identifier: 'legs'
},
{
namespace: 'groot',
identifier: 'torso'
}
]
};
var GrootProvider = {
get: function (key) {
if (key.identifier === 'groot') {
return Promise.resolve(GROOT_ROOT);
}
return Promise.resolve({
name: 'Groot\'s ' + key.identifier
});
}
};
mct.Objects.addRoot(ROOT_KEY);
mct.Objects.addProvider('groot', GrootProvider);
```
See the tutorial in [object-provider.html](object-provider.html).
### Making a custom provider:
All methods on the provider interface are optional, so you do not need
to modify them.
All methods on the provider interface are optional.
* `provider.get(key)` -> promise for a domain object.
* `provider.save(domainObject)` -> returns promise that is fulfilled when object
has been saved.
* `provider.delete(domainObject)` -> returns promise that is fulfilled when
object has been deleted.
* `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.

View File

@ -40,7 +40,8 @@ define([
implementation: LegacyObjectAPIInterceptor,
depends: [
"roots[]",
"instantiate"
"instantiate",
"topic"
]
}
]

View File

@ -12,13 +12,27 @@ define([
}
var namespace = '',
identifier = key;
for (var i = 0, escaped = false, len=key.length; i < len; i++) {
if (key[i] === ":" && !escaped) {
namespace = key.slice(0, i);
identifier = key.slice(i + 1);
break;
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
@ -42,6 +56,7 @@ define([
// 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);
@ -51,6 +66,7 @@ define([
// 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);
@ -58,10 +74,15 @@ define([
return model;
};
var equals = function (a, b) {
return makeKeyString(a.key) === makeKeyString(b.key);
};
return {
toOldFormat: toOldFormat,
toNewFormat: toNewFormat,
makeKeyString: makeKeyString,
parseKeyString: parseKeyString
parseKeyString: parseKeyString,
equals: equals
};
});

View File

@ -0,0 +1,10 @@
define([
"EventEmitter"
], function (
EventEmitter
) {
/**
* Provides a singleton event bus for sharing between objects.
*/
return new EventEmitter();
});

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

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

View 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
View 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
View 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
View 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
View File

@ -0,0 +1,3 @@
return require('main');
}));

16
src/openmct.js Normal file
View 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
View 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() {

View File

@ -55,7 +55,8 @@ requirejs.config({
"screenfull": "bower_components/screenfull/dist/screenfull.min",
"text": "bower_components/text/text",
"uuid": "bower_components/node-uuid/uuid",
"zepto": "bower_components/zepto/zepto.min"
"zepto": "bower_components/zepto/zepto.min",
"lodash": "bower_components/lodash/lodash"
},
"shim": {
@ -76,6 +77,9 @@ requirejs.config({
},
"zepto": {
"exports": "Zepto"
},
"lodash": {
"exports": "lodash"
}
},

View File

@ -1,44 +0,0 @@
define(function () {
return function grootPlugin(mct) {
var ROOT_KEY = {
namespace: 'groot',
identifier: 'groot'
};
var GROOT_ROOT = {
name: 'I am groot',
type: 'folder',
composition: [
{
namespace: 'groot',
identifier: 'arms'
},
{
namespace: 'groot',
identifier: 'legs'
},
{
namespace: 'groot',
identifier: 'torso'
}
]
};
var GrootProvider = {
get: function (key) {
if (key.identifier === 'groot') {
return Promise.resolve(GROOT_ROOT);
}
return Promise.resolve({
name: 'Groot\'s ' + key.identifier
});
}
};
mct.Objects.addRoot(ROOT_KEY);
mct.Objects.addProvider('groot', GrootProvider);
return mct;
};
});

View File

@ -1,59 +0,0 @@
define([
'legacyRegistry',
'./src/controllers/TodoController'
], function (
legacyRegistry,
TodoController
) {
legacyRegistry.register("tutorials/todo", {
"name": "To-do Plugin",
"description": "Allows creating and editing to-do lists.",
"extensions": {
"views": [
{
"key": "example.todo",
"type": "example.todo",
"glyph": "2",
"name": "List",
"templateUrl": "templates/todo.html",
"editable": true,
"toolbar": {
"sections": [
{
"items": [
{
"text": "Add Task",
"glyph": "+",
"method": "addTask",
"control": "button"
}
]
},
{
"items": [
{
"glyph": "Z",
"method": "removeTask",
"control": "button"
}
]
}
]
}
}
],
"controllers": [
{
"key": "TodoController",
"implementation": TodoController,
"depends": [ "$scope", "dialogService" ]
}
],
"stylesheets": [
{
"stylesheetUrl": "css/todo.css"
}
]
}
});
});

View File

@ -1,29 +0,0 @@
<div ng-controller="TodoController" class="example-todo">
<div class="example-button-group">
<a ng-class="{ selected: checkVisibility(true) }"
ng-click="setVisibility(true)">All</a>
<a ng-class="{ selected: checkVisibility(false, false) }"
ng-click="setVisibility(false, false)">Incomplete</a>
<a ng-class="{ selected: checkVisibility(false, true) }"
ng-click="setVisibility(false, true)">Complete</a>
</div>
<ul>
<li ng-repeat="task in model.tasks"
ng-class="{ 'example-task-completed': task.completed }"
ng-if="showTask(task)">
<input type="checkbox"
ng-checked="task.completed"
ng-click="toggleCompletion($index)">
<span ng-click="selectTask($index)"
ng-class="{ selected: isSelected($index) }"
class="example-task-description">
{{task.description}}
</span>
</li>
</ul>
<div ng-if="model.tasks.length < 1" class="example-message">
There are no tasks to show.
</div>
</div>

View File

@ -1,97 +0,0 @@
define(function () {
// Form to display when adding new tasks
var NEW_TASK_FORM = {
name: "Add a Task",
sections: [{
rows: [{
name: 'Description',
key: 'description',
control: 'textfield',
required: true
}]
}]
};
function TodoController($scope, dialogService) {
var showAll = true,
showCompleted;
// Persist changes made to a domain object's model
function persist() {
var persistence =
$scope.domainObject.getCapability('persistence');
return persistence && persistence.persist();
}
// Remove a task
function removeTaskAtIndex(taskIndex) {
$scope.domainObject.useCapability('mutation', function (model) {
model.tasks.splice(taskIndex, 1);
});
persist();
}
// Add a task
function addNewTask(task) {
$scope.domainObject.useCapability('mutation', function (model) {
model.tasks.push(task);
});
persist();
}
// Change which tasks are visible
$scope.setVisibility = function (all, completed) {
showAll = all;
showCompleted = completed;
};
// Check if current visibility settings match
$scope.checkVisibility = function (all, completed) {
return showAll ? all : (completed === showCompleted);
};
// Toggle the completion state of a task
$scope.toggleCompletion = function (taskIndex) {
$scope.domainObject.useCapability('mutation', function (model) {
var task = model.tasks[taskIndex];
task.completed = !task.completed;
});
persist();
};
// Check whether a task should be visible
$scope.showTask = function (task) {
return showAll || (showCompleted === !!(task.completed));
};
// Handle selection state in edit mode
if ($scope.selection) {
// Expose the ability to select tasks
$scope.selectTask = function (taskIndex) {
$scope.selection.select({
removeTask: function () {
removeTaskAtIndex(taskIndex);
$scope.selection.deselect();
},
taskIndex: taskIndex
});
};
// Expose a check for current selection state
$scope.isSelected = function (taskIndex) {
return ($scope.selection.get() || {}).taskIndex ===
taskIndex;
};
// Expose a view-level selection proxy
$scope.selection.proxy({
addTask: function () {
dialogService.getUserInput(NEW_TASK_FORM, {})
.then(addNewTask);
}
});
}
}
return TodoController;
});

View File

@ -0,0 +1,3 @@
<label>Description:
<input type="text" class="description">
</label>

View File

@ -0,0 +1,5 @@
<li>
<input type="checkbox" class="example-task-checked">
<span class="example-task-description">
</span>
</li>

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

View File

@ -1,4 +1,14 @@
define(function () {
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: {
@ -7,12 +17,256 @@ define(function () {
description: "A list of things that need to be done."
},
initialize: function (model) {
model.tasks = [];
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;
};