Compare commits
	
		
			3 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					9d9d1b1de2 | ||
| 
						 | 
					311e221af5 | ||
| 
						 | 
					6281b67e47 | 
@@ -14,8 +14,7 @@
 | 
			
		||||
    "nonew": true,
 | 
			
		||||
    "predef": [
 | 
			
		||||
        "define",
 | 
			
		||||
        "Promise",
 | 
			
		||||
        "WeakMap"
 | 
			
		||||
        "Promise"
 | 
			
		||||
    ],
 | 
			
		||||
    "shadow": "outer",
 | 
			
		||||
    "strict": "implied",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										562
									
								
								API.md
									
									
									
									
									
								
							
							
						
						
									
										562
									
								
								API.md
									
									
									
									
									
								
							@@ -1,150 +1,76 @@
 | 
			
		||||
# Building Applications With Open MCT
 | 
			
		||||
# Open MCT API
 | 
			
		||||
 | 
			
		||||
## Scope and purpose of this document
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
This document is intended to serve as a reference for developing an application 
 | 
			
		||||
based on Open MCT. It will provide details of the API functions necessary to extend the 
 | 
			
		||||
Open MCT platform meet common use cases such as integrating with a telemetry source. 
 | 
			
		||||
Open MCT supports AMD, CommonJS, and loading via a script tag; it's easy to use
 | 
			
		||||
in your project. The [`openmct`]{@link module:openmct} module is exported
 | 
			
		||||
via AMD and CommonJS, and is also exposed as `openmct` in the global scope
 | 
			
		||||
if loaded via a script tag.
 | 
			
		||||
 | 
			
		||||
The best place to start is with the [Open MCT Tutorials](https://github.com/nasa/openmct-tutorial). 
 | 
			
		||||
These will walk you through the process of getting up and running with Open MCT, 
 | 
			
		||||
as well as addressing some common developer use cases.
 | 
			
		||||
## Overview
 | 
			
		||||
 | 
			
		||||
## Building From Source 
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
The latest version of Open MCT is available from [our GitHub repository](https://github.com/nasa/openmct). 
 | 
			
		||||
If you have `git`, and `node` installed, you can build Open MCT with the commands 
 | 
			
		||||
```
 | 
			
		||||
git clone https://github.com/nasa/openmct.git
 | 
			
		||||
cd openmct
 | 
			
		||||
npm install
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
These commands will fetch the Open MCT source from our GitHub repository, and build 
 | 
			
		||||
a minified version that can be included in your application. The output of the 
 | 
			
		||||
build process is placed in a `dist` folder under the openmct source directory, 
 | 
			
		||||
which can be copied out to another location as needed. The contents of this 
 | 
			
		||||
folder will include a minified javascript file named `openmct.js` as well as 
 | 
			
		||||
assets such as html, css, and images necessary for the UI. 
 | 
			
		||||
 | 
			
		||||
## Starting an Open MCT application
 | 
			
		||||
 | 
			
		||||
To start a minimally functional Open MCT application, it is necessary to include 
 | 
			
		||||
the Open MCT distributable, enable some basic plugins, and bootstrap the application. 
 | 
			
		||||
The tutorials walk through the process of getting Open MCT up and running from scratch,
 | 
			
		||||
but provided below is a minimal HTML template that includes Open MCT, installs 
 | 
			
		||||
some basic plugins, and bootstraps the application. It assumes that Open MCT is 
 | 
			
		||||
installed under an `openmct` subdirectory, as described in [Building From Source](#building-from-source). 
 | 
			
		||||
 | 
			
		||||
This approach includes openmct using a simple script tag, resulting in a global 
 | 
			
		||||
variable named `openmct`. This `openmct` object is used subsequently to make API 
 | 
			
		||||
calls. 
 | 
			
		||||
 | 
			
		||||
Open MCT is packaged as a UMD (Universal Module Definition) module, so common 
 | 
			
		||||
script loaders are also supported.
 | 
			
		||||
 | 
			
		||||
```html
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
<head>
 | 
			
		||||
    <title>Open MCT</title>
 | 
			
		||||
    <script src="openmct.js"></script>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
    <script>
 | 
			
		||||
        openmct.setAssetPath('openmct/dist');
 | 
			
		||||
        openmct.install(openmct.plugins.LocalStorage());
 | 
			
		||||
        openmct.install(openmct.plugins.MyItems());
 | 
			
		||||
        openmct.install(openmct.plugins.UTCTimeSystem());
 | 
			
		||||
        openmct.install(openmct.plugins.Espresso());
 | 
			
		||||
        openmct.start();
 | 
			
		||||
    </script>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The Open MCT library included above requires certain assets such as html templates, 
 | 
			
		||||
images, and css. If you installed Open MCT from GitHub as described in the section 
 | 
			
		||||
on [Building from Source](#building-from-source) then these assets will have been 
 | 
			
		||||
downloaded along with the Open MCT javascript library. You can specify the 
 | 
			
		||||
location of these assets by calling `openmct.setAssetPath()`. Typically this will 
 | 
			
		||||
be the same location as the `openmct.js` library is included from.
 | 
			
		||||
 | 
			
		||||
There are some plugins bundled with the application that provide UI, persistence, 
 | 
			
		||||
and other default configuration which are necessary to be able to do anything with 
 | 
			
		||||
the application initially. Any of these plugins can, in principle, be replaced with a custom 
 | 
			
		||||
plugin. The included plugins are documented in the [Included Plugins](#included-plugins) 
 | 
			
		||||
section.  
 | 
			
		||||
 | 
			
		||||
## Plugins
 | 
			
		||||
 | 
			
		||||
### Defining and Installing a New Plugin
 | 
			
		||||
 | 
			
		||||
```javascript
 | 
			
		||||
openmct.install(function install(openmctAPI) {
 | 
			
		||||
    // Do things here
 | 
			
		||||
    // ...
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
New plugins are installed in Open MCT by calling `openmct.install`, and providing 
 | 
			
		||||
a plugin installation function. This function will be invoked on application 
 | 
			
		||||
startup with one parameter - the openmct API object. A common approach used in 
 | 
			
		||||
the Open MCT codebase is to define a plugin as a function that returns this 
 | 
			
		||||
installation function. This allows configuration to be specified when the plugin is included.
 | 
			
		||||
 | 
			
		||||
eg.
 | 
			
		||||
```javascript
 | 
			
		||||
openmct.install(openmct.plugins.Elasticsearch("http://localhost:8002/openmct"));
 | 
			
		||||
```
 | 
			
		||||
This approach can be seen in all of the [plugins provided with Open MCT](https://github.com/nasa/openmct/blob/master/src/plugins/plugins.js).
 | 
			
		||||
 | 
			
		||||
## Domain Objects and Identifiers
 | 
			
		||||
 | 
			
		||||
_Domain Objects_ are the basic entities that represent domain knowledge in Open MCT.
 | 
			
		||||
The temperature sensor on a solar panel, an overlay plot comparing 
 | 
			
		||||
the results of all temperature sensors, the command dictionary for a spacecraft,
 | 
			
		||||
the individual commands in that dictionary, the "My Items" folder:
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
A _Domain Object_ is simply a javascript object with some standard attributes.  
 | 
			
		||||
An example of a _Domain Object_ is the "My Items" object which is a folder in 
 | 
			
		||||
which a user can persist any objects that they create. The My Items object 
 | 
			
		||||
looks like this: 
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
```javascript
 | 
			
		||||
{
 | 
			
		||||
    identifier: {
 | 
			
		||||
        namespace: ""
 | 
			
		||||
        key: "mine"
 | 
			
		||||
    }
 | 
			
		||||
    name:"My Items",
 | 
			
		||||
    type:"folder",
 | 
			
		||||
    location:"ROOT",
 | 
			
		||||
    composition: []
 | 
			
		||||
}
 | 
			
		||||
And then we have Views. Views allow you to visualize domain objects. 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!
 | 
			
		||||
 | 
			
		||||
## Running Open MCT
 | 
			
		||||
 | 
			
		||||
Once the [`openmct`](@link module:openmct) module has been loaded, you can
 | 
			
		||||
simply invoke [`start`]{@link module:openmct.MCT#start} to run Open MCT:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
openmct.start();
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Object Attributes
 | 
			
		||||
Generally, however, you will want to configure Open MCT by adding plugins
 | 
			
		||||
before starting it. It is important to install plugins and configure Open MCT
 | 
			
		||||
_before_ calling [`start`]{@link module:openmct.MCT#start}; Open MCT is not
 | 
			
		||||
designed to be reconfigured once started.
 | 
			
		||||
 | 
			
		||||
The main attributes to note are the `identifier`, and `type` attributes.
 | 
			
		||||
* `identifier`: A composite key that provides a universally unique identifier for 
 | 
			
		||||
this object. The `namespace` and `key` are used to identify the object. The `key` 
 | 
			
		||||
must be unique within the namespace. 
 | 
			
		||||
* `type`: All objects in Open MCT have a type. Types allow you to form an 
 | 
			
		||||
ontology of knowledge and provide an abstraction for grouping, visualizing, and 
 | 
			
		||||
interpreting data. Details on how to define a new object type are provided below. 
 | 
			
		||||
## Configuring Open MCT
 | 
			
		||||
 | 
			
		||||
Open MCT uses a number of builtin types. Typically you are going to want to 
 | 
			
		||||
define your own if extending Open MCT.
 | 
			
		||||
The [`openmct`]{@link module:openmct} module (more specifically, the
 | 
			
		||||
[`MCT`]{@link module:openmct.MCT} class, of which `openmct` is an instance)
 | 
			
		||||
exposes a variety of methods to allow the application to be configured,
 | 
			
		||||
extended, and customized before running.
 | 
			
		||||
 | 
			
		||||
### Domain Object Types
 | 
			
		||||
Short examples follow; see the linked documentation for further details.
 | 
			
		||||
 | 
			
		||||
Custom types may be registered via the `addType` function on the opencmt Type 
 | 
			
		||||
registry.
 | 
			
		||||
### Adding Domain Object Types
 | 
			
		||||
 | 
			
		||||
eg.
 | 
			
		||||
```javascript
 | 
			
		||||
Custom types may be registered via
 | 
			
		||||
[`openmct.types`]{@link module:openmct.MCT#types}:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
openmct.types.addType('my-type', {
 | 
			
		||||
    label: "My Type",
 | 
			
		||||
    description: "This is a type that I added!",
 | 
			
		||||
@@ -152,98 +78,64 @@ openmct.types.addType('my-type', {
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The `addType` function accepts two arguments:
 | 
			
		||||
* A `string` key identifying the type. This key is used when specifying a type 
 | 
			
		||||
for an object.
 | 
			
		||||
* An object type specification. An object type definition supports the following 
 | 
			
		||||
attributes      
 | 
			
		||||
    * `label`: a `string` naming this object type
 | 
			
		||||
    * `description`: a `string` specifying a longer-form description of this type
 | 
			
		||||
    * `initialize`: a `function` which initializes the model for new domain objects 
 | 
			
		||||
    of this type. This can be used for setting default values on an object when 
 | 
			
		||||
    it is instantiated.
 | 
			
		||||
    * `creatable`: A `boolean` indicating whether users should be allowed to create 
 | 
			
		||||
    this type (default: `false`). This will determine whether the type appears 
 | 
			
		||||
    in the `Create` menu.
 | 
			
		||||
    * `cssClass`: A `string` specifying a CSS class to apply to each representation 
 | 
			
		||||
    of this object. This is used for specifying an icon to appear next to each 
 | 
			
		||||
    object of this type.
 | 
			
		||||
### Adding Views
 | 
			
		||||
 | 
			
		||||
The [Open MCT Tutorials](https://github.com/openmct/openmct-tutorial) provide a 
 | 
			
		||||
step-by-step examples of writing code for Open MCT that includes a [section on 
 | 
			
		||||
defining a new object type](https://github.com/nasa/openmct-tutorial#step-3---providing-objects).
 | 
			
		||||
Custom views may be registered based on the region in the application
 | 
			
		||||
where they should appear:
 | 
			
		||||
 | 
			
		||||
## Root Objects
 | 
			
		||||
* [`openmct.mainViews`]{@link module:openmct.MCT#mainViews} is a registry
 | 
			
		||||
  of views of domain objects which should appear in the main viewing area.
 | 
			
		||||
* [`openmct.inspectors`]{@link module:openmct.MCT#inspectors} is a registry
 | 
			
		||||
  of views of domain objects and/or active selections, which should appear in
 | 
			
		||||
  the Inspector.
 | 
			
		||||
* [`openmct.toolbars`]{@link module:openmct.MCT#toolbars} is a registry
 | 
			
		||||
  of views of domain objects and/or active selections, which should appear in
 | 
			
		||||
  the toolbar area while editing.
 | 
			
		||||
* [`openmct.indicators`]{@link module:openmct.MCT#inspectors} is a registry
 | 
			
		||||
  of views which should appear in the status area of the application.
 | 
			
		||||
 | 
			
		||||
In many cases, you'd like a certain object (or a certain hierarchy of objects) 
 | 
			
		||||
to be accessible from the top level of the application (the tree on the left-hand 
 | 
			
		||||
side of Open MCT.) For example, it is typical to expose a telemetry dictionary 
 | 
			
		||||
as a hierarchy of telemetry-providing domain objects in this fashion.
 | 
			
		||||
Example:
 | 
			
		||||
 | 
			
		||||
To do so, use the `addRoot` method of the object API.
 | 
			
		||||
 | 
			
		||||
eg.
 | 
			
		||||
```javascript
 | 
			
		||||
openmct.objects.addRoot({
 | 
			
		||||
        namespace: "my-namespace",
 | 
			
		||||
        key: "my-key"
 | 
			
		||||
    });
 | 
			
		||||
```
 | 
			
		||||
openmct.mainViews.addProvider({
 | 
			
		||||
    canView: function (domainObject) {
 | 
			
		||||
        return domainObject.type === 'my-type';
 | 
			
		||||
    },
 | 
			
		||||
    view: function (domainObject) {
 | 
			
		||||
        return new MyView(domainObject);
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The `addRoot` function takes a single [object identifier](#domain-objects-and-identifiers) 
 | 
			
		||||
as an argument. 
 | 
			
		||||
### Adding a Root-level Object
 | 
			
		||||
 | 
			
		||||
In many cases, you'd like a certain object (or a certain hierarchy of
 | 
			
		||||
objects) to be accessible from the top level of the application (the
 | 
			
		||||
tree on the left-hand side of Open MCT.) It is typical to expose a telemetry
 | 
			
		||||
dictionary as a hierarchy of telemetry-providing domain objects in this
 | 
			
		||||
fashion.
 | 
			
		||||
 | 
			
		||||
To do so, use the [`addRoot`]{@link module:openmct.ObjectAPI#addRoot} method
 | 
			
		||||
of the [object API]{@link module:openmct.ObjectAPI}:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
openmct.objects.addRoot({ key: "my-key", namespace: "my-namespace" });
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Root objects are loaded just like any other objects, i.e. via an object
 | 
			
		||||
provider.
 | 
			
		||||
 | 
			
		||||
## Object Providers
 | 
			
		||||
 | 
			
		||||
An Object Provider is used to build _Domain Objects_, typically retrieved from 
 | 
			
		||||
some source such as a persistence store or telemetry dictionary. In order to 
 | 
			
		||||
integrate telemetry from a new source an object provider will need to be created 
 | 
			
		||||
that can build objects representing telemetry points exposed by the telemetry 
 | 
			
		||||
source. The API call to define a new object provider is fairly straightforward. 
 | 
			
		||||
Here's a very simple example:
 | 
			
		||||
 | 
			
		||||
```javascript
 | 
			
		||||
openmct.objects.addProvider('example.namespace', {
 | 
			
		||||
    get: function (identifier) {
 | 
			
		||||
        return Promise.resolve({
 | 
			
		||||
            identifier: identifier,
 | 
			
		||||
            name: 'Example Object',
 | 
			
		||||
            type: 'example-object-type'
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
The `addProvider` function takes two arguments:
 | 
			
		||||
 | 
			
		||||
* `namespace`: A `string` representing the namespace that this object provider 
 | 
			
		||||
will provide objects for.
 | 
			
		||||
* `provider`: An `object` with a single function, `get`. This function accepts an 
 | 
			
		||||
[Identifier](#domain-objects-and-identifiers) for the object to be provided. 
 | 
			
		||||
It is expected that the `get` function will return a 
 | 
			
		||||
[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) 
 | 
			
		||||
that resolves with the object being requested.
 | 
			
		||||
 | 
			
		||||
In future, object providers will support other methods to enable other operations 
 | 
			
		||||
with persistence stores, such as creating, updating, and deleting objects.
 | 
			
		||||
 | 
			
		||||
## Composition Providers
 | 
			
		||||
 | 
			
		||||
The _composition_ of a domain object is the list of objects it contains, as shown 
 | 
			
		||||
(for example) in the tree for browsing. Open MCT provides a
 | 
			
		||||
[default solution](#default-composition-provider) for composition, but there
 | 
			
		||||
may be cases where you want to provide the composition of a certain object
 | 
			
		||||
(or type of object) dynamically.
 | 
			
		||||
 | 
			
		||||
### Adding Composition Providers
 | 
			
		||||
 | 
			
		||||
You may want to populate a hierarchy under a custom root-level object based on 
 | 
			
		||||
the contents of a telemetry dictionary. To do this, you can add a new 
 | 
			
		||||
Composition Provider:
 | 
			
		||||
The "composition" of a domain object is the list of objects it contains,
 | 
			
		||||
as shown (for example) in the tree for browsing. Open MCT provides a
 | 
			
		||||
default solution for composition, but there may be cases where you want
 | 
			
		||||
to provide the composition of a certain object (or type of object) dynamically.
 | 
			
		||||
For instance, you may want to populate a hierarchy under a custom root-level
 | 
			
		||||
object based on the contents of a telemetry dictionary.
 | 
			
		||||
To do this, you can add a new CompositionProvider:
 | 
			
		||||
 | 
			
		||||
```javascript
 | 
			
		||||
```
 | 
			
		||||
openmct.composition.addProvider({
 | 
			
		||||
    appliesTo: function (domainObject) {
 | 
			
		||||
        return domainObject.type === 'my-type';
 | 
			
		||||
@@ -253,177 +145,135 @@ openmct.composition.addProvider({
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
The `addProvider` function accepts a Composition Provider object as its sole 
 | 
			
		||||
argument. A Composition Provider is a javascript object exposing two functions:
 | 
			
		||||
* `appliesTo`: A `function` that accepts a `domainObject` argument, and returns 
 | 
			
		||||
a `boolean` value indicating whether this composition provider applies to the 
 | 
			
		||||
given object.
 | 
			
		||||
* `load`: A `function` that accepts a `domainObject` as an argument, and returns
 | 
			
		||||
a `Promise` that resolves with an array of [Identifier](#domain-objects-and-identifiers).
 | 
			
		||||
These identifiers will be used to fetch Domain Objects from an [Object Provider](#object-provider)
 | 
			
		||||
 | 
			
		||||
### Default Composition Provider
 | 
			
		||||
### Adding Telemetry Providers
 | 
			
		||||
 | 
			
		||||
The default composition provider applies to any domain object with a `composition` 
 | 
			
		||||
property. The value of `composition` should be an array of identifiers, e.g.:
 | 
			
		||||
When connecting to a new telemetry source, you will want to register a new
 | 
			
		||||
[telemetry provider]{@link module:openmct.TelemetryAPI~TelemetryProvider}
 | 
			
		||||
with the [telemetry API]{@link module:openmct.TelemetryAPI#addProvider}:
 | 
			
		||||
 | 
			
		||||
```javascript
 | 
			
		||||
var domainObject = {
 | 
			
		||||
    name: "My Object",
 | 
			
		||||
    type: 'folder',
 | 
			
		||||
    composition: [
 | 
			
		||||
        {
 | 
			
		||||
            id: '412229c3-922c-444b-8624-736d85516247',
 | 
			
		||||
            namespace: 'foo'
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            key: 'd6e0ce02-5b85-4e55-8006-a8a505b64c75',
 | 
			
		||||
            namespace: 'foo'
 | 
			
		||||
        }
 | 
			
		||||
    ]
 | 
			
		||||
};
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Telemetry Providers
 | 
			
		||||
 | 
			
		||||
When connecting to a new telemetry source, you will need to register a new
 | 
			
		||||
_Telemetry Provider_. A _Telemetry Provider_ retrieves telemetry data from some telemetry 
 | 
			
		||||
source, and exposes them in a way that can be used by Open MCT. A telemetry 
 | 
			
		||||
provider typically can support a one off __request__ for a batch of telemetry data, 
 | 
			
		||||
or it can provide the ability to __subscribe__ to receive new telemetry data when 
 | 
			
		||||
it becomes available, or both.
 | 
			
		||||
 | 
			
		||||
```javascript
 | 
			
		||||
openmct.telemetry.addProvider({
 | 
			
		||||
    supportsRequest: function (domainObject) {
 | 
			
		||||
        //...
 | 
			
		||||
    canProvideTelemetry: function (domainObject) {
 | 
			
		||||
        return domainObject.type === 'my-type';
 | 
			
		||||
    },
 | 
			
		||||
    supportsSubscribe: function (domainObject) {
 | 
			
		||||
        //...
 | 
			
		||||
    properties: function (domainObject) {
 | 
			
		||||
        return [
 | 
			
		||||
            { key: 'value', name: "Temperature", units: "degC" },
 | 
			
		||||
            { key: 'time', name: "UTC" }
 | 
			
		||||
        ];
 | 
			
		||||
    },
 | 
			
		||||
    request: function (domainObject, options) {    
 | 
			
		||||
        //...
 | 
			
		||||
    request: function (domainObject, options) {
 | 
			
		||||
        var telemetryId = domainObject.myTelemetryId;
 | 
			
		||||
        return myAdapter.request(telemetryId, options.start, options.end);
 | 
			
		||||
    },
 | 
			
		||||
    subscribe: function (domainObject, callback, options) {
 | 
			
		||||
        //...
 | 
			
		||||
    subscribe: function (domainObject, callback) {
 | 
			
		||||
        var telemetryId = domainObject.myTelemetryId;
 | 
			
		||||
        myAdapter.subscribe(telemetryId, callback);
 | 
			
		||||
        return myAdapter.unsubscribe.bind(myAdapter, telemetryId, callback);
 | 
			
		||||
    }
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
A telemetry provider is an object with the following functions defined:
 | 
			
		||||
 | 
			
		||||
* `supportsRequest`: An __optional__ `function` that accepts a 
 | 
			
		||||
[Domain Object](#domain-objects-and-identifiers) and returns a `boolean` value
 | 
			
		||||
indicating whether or not this provider supports telemetry requests for the 
 | 
			
		||||
given object. If this returns `true` then a `request` function must be defined. 
 | 
			
		||||
* `supportsSubscribe`: An __optional__ `function` that accepts a 
 | 
			
		||||
[Domain Object](#domain-objects-and-identifiers) and returns a `boolean` value 
 | 
			
		||||
indicating whether or not this provider supports telemetry subscriptions. If this 
 | 
			
		||||
returns `true` then a `subscribe` function must also be defined. As with `request`, 
 | 
			
		||||
the return value will typically be conditional, and based on attributes of 
 | 
			
		||||
`domainObject` such as its identifier.
 | 
			
		||||
* `request`: A `function` that returns a `Promise` that will resolve with an `Array` 
 | 
			
		||||
of telemetry in a single query. This function accepts as arguments a 
 | 
			
		||||
[Domain Object](#domain-objects-and-identifiers) and an object containing some 
 | 
			
		||||
[request options](#telemetry-requests). 
 | 
			
		||||
* `subscribe`:  A `function` that accepts a [Domain Object](#domain-objects-and-identifiers),
 | 
			
		||||
a callback `function`, and a [telemetry request](#telemetry-requests). The 
 | 
			
		||||
callback is invoked whenever telemetry is available, and 
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
The implementations for `request` and `subscribe` can vary depending on the
 | 
			
		||||
nature of the endpoint which will provide telemetry. In the example above,
 | 
			
		||||
it is assumed that `myAdapter` contains the implementation details
 | 
			
		||||
(such as HTTP requests, WebSocket connections, etc.) associated with some telemetry
 | 
			
		||||
it is assumed that `myAdapter` contains the specific implementations
 | 
			
		||||
(HTTP requests, WebSocket connections, etc.) associated with some telemetry
 | 
			
		||||
source.
 | 
			
		||||
 | 
			
		||||
For a step-by-step guide to building a telemetry adapter, please see the 
 | 
			
		||||
[Open MCT Tutorials](https://github.com/larkin/openmct-tutorial).
 | 
			
		||||
## Using Open MCT
 | 
			
		||||
 | 
			
		||||
### Telemetry Requests
 | 
			
		||||
Telemetry requests support time bounded queries. A call to a _Telemetry Provider_'s 
 | 
			
		||||
`request` function will include an `options` argument. These are simply javascript
 | 
			
		||||
objects with attributes for the request parameters. An example of a telemetry 
 | 
			
		||||
request object with a start and end time is included below:
 | 
			
		||||
```javascript
 | 
			
		||||
{
 | 
			
		||||
    start: 1487981997240,
 | 
			
		||||
    end: 1487982897240
 | 
			
		||||
}
 | 
			
		||||
When implementing new features, it is useful and sometimes necessary to
 | 
			
		||||
utilize functionality exposed by Open MCT.
 | 
			
		||||
 | 
			
		||||
### Retrieving Composition
 | 
			
		||||
 | 
			
		||||
To limit which objects are loaded at any given time, the composition of
 | 
			
		||||
a domain object must be requested asynchronously:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
openmct.composition(myObject).load().then(function (childObjects) {
 | 
			
		||||
    childObjects.forEach(doSomething);
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Telemetry Data
 | 
			
		||||
### Support Common Gestures
 | 
			
		||||
 | 
			
		||||
Telemetry data is provided to Open MCT by _[Telemetry Providers](#telemetry-providers)_
 | 
			
		||||
in the form of javascript objects. A collection of telemetry values (for example, 
 | 
			
		||||
retrieved in response to a `request`) is represented by an `Array` of javascript 
 | 
			
		||||
objects. These telemetry javascript objects are simply key value pairs.
 | 
			
		||||
Custom views may also want to support common gestures using the
 | 
			
		||||
[gesture API]{@link module:openmct.GestureAPI}. For instance, to make
 | 
			
		||||
a view (or part of a view) selectable:
 | 
			
		||||
 | 
			
		||||
Typically a telemetry datum will have some timestamp associated with it. This 
 | 
			
		||||
time stamp should have a key that corresponds to some time system supported by 
 | 
			
		||||
Open MCT. If the `UTCTimeSystem` plugin is installed, then the key `utc` can be used.
 | 
			
		||||
   
 | 
			
		||||
An example of a telemetry provider request function that returns a collection of
 | 
			
		||||
mock telemtry data is below:
 | 
			
		||||
 | 
			
		||||
```javascript
 | 
			
		||||
openmct.telemetry.addProvider({
 | 
			
		||||
    supportsRequest: function (domainObject) {
 | 
			
		||||
        return true
 | 
			
		||||
    },
 | 
			
		||||
    request: function (domainObject, options) {    
 | 
			
		||||
        return Promise.resolve([
 | 
			
		||||
            {
 | 
			
		||||
                'utc': Date.now() - 2000,
 | 
			
		||||
                'value': 1,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                'utc': Date.now() - 1000,
 | 
			
		||||
                'value': 2,
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                'utc': Date.now(),
 | 
			
		||||
                'value': 3,
 | 
			
		||||
            }
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
})
 | 
			
		||||
```
 | 
			
		||||
openmct.gestures.selectable(myHtmlElement, myDomainObject);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Included Plugins
 | 
			
		||||
### Working with Domain Objects
 | 
			
		||||
 | 
			
		||||
Open MCT is packaged along with a few general-purpose plugins:
 | 
			
		||||
The [object API]{@link module:openmct.ObjectAPI} provides useful methods
 | 
			
		||||
for working with domain objects.
 | 
			
		||||
 | 
			
		||||
To make changes to a domain object, use the
 | 
			
		||||
[`mutate`]{@link module:openmct.ObjectAPI#mutate} method:
 | 
			
		||||
 | 
			
		||||
* `openmct.plugins.CouchDB` is an adapter for using CouchDB for persistence
 | 
			
		||||
  of user-created objects. This is a constructor that takes the URL for the
 | 
			
		||||
  CouchDB database as a parameter, e.g.
 | 
			
		||||
```javascript
 | 
			
		||||
openmct.install(openmct.plugins.CouchDB('http://localhost:5984/openmct'))
 | 
			
		||||
```
 | 
			
		||||
* `openmct.plugins.Elasticsearch` is an adapter for using Elasticsearch for
 | 
			
		||||
  persistence of user-created objects. This is a
 | 
			
		||||
  constructor that takes the URL for the Elasticsearch instance as a
 | 
			
		||||
  parameter. eg.
 | 
			
		||||
```javascript
 | 
			
		||||
openmct.install(openmct.plugins.CouchDB('http://localhost:9200'))
 | 
			
		||||
openmct.objects.mutate(myDomainObject, "name", "New name!");
 | 
			
		||||
```
 | 
			
		||||
* `openmct.plugins.Espresso` and `openmct.plugins.Snow` are two different
 | 
			
		||||
  themes (dark and light) available for Open MCT. Note that at least one
 | 
			
		||||
  of these themes must be installed for Open MCT to appear correctly.
 | 
			
		||||
* `openmct.plugins.LocalStorage` provides persistence of user-created
 | 
			
		||||
  objects in browser-local storage. This is particularly useful in
 | 
			
		||||
  development environments.
 | 
			
		||||
* `openmct.plugins.MyItems` adds a top-level folder named "My Items"
 | 
			
		||||
  when the application is first started, providing a place for a
 | 
			
		||||
  user to store created items.
 | 
			
		||||
* `openmct.plugins.UTCTimeSystem` provides a default time system for Open MCT.
 | 
			
		||||
 | 
			
		||||
Generally, you will want to either install these plugins, or install
 | 
			
		||||
different plugins that provide persistence and an initial folder
 | 
			
		||||
hierarchy.
 | 
			
		||||
Making modifications in this fashion allows other usages of the domain
 | 
			
		||||
object to remain up to date using the
 | 
			
		||||
[`observe`]{@link module:openmct.ObjectAPI#observe} method:
 | 
			
		||||
 | 
			
		||||
eg.
 | 
			
		||||
```javascript
 | 
			
		||||
openmct.install(openmct.plugins.LocalStorage());
 | 
			
		||||
openmct.install(openmct.plugins.MyItems());
 | 
			
		||||
```
 | 
			
		||||
openmct.objects.observe(myDomainObject, "name", function (newName) {
 | 
			
		||||
    myLabel.textContent = newName;
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Using Telemetry
 | 
			
		||||
 | 
			
		||||
Very often in Open MCT, you wish to work with telemetry data (for instance,
 | 
			
		||||
to display it in a custom visualization.)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Synchronizing with the Time Conductor
 | 
			
		||||
 | 
			
		||||
Views which wish to remain synchronized with the state of Open MCT's
 | 
			
		||||
time conductor should utilize
 | 
			
		||||
[`openmct.conductor`]{@link module:openmct.TimeConductor}:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
openmct.conductor.on('bounds', function (newBounds) {
 | 
			
		||||
    requestTelemetry(newBounds.start, newBounds.end).then(displayTelemetry);
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Plugins
 | 
			
		||||
 | 
			
		||||
While you can register new features with Open MCT directly, it is generally
 | 
			
		||||
more useful to package these as a plugin. A plugin is a function that takes
 | 
			
		||||
[`openmct`]{@link module:openmct} as an argument, and performs configuration
 | 
			
		||||
upon `openmct` when invoked.
 | 
			
		||||
 | 
			
		||||
### Installing Plugins
 | 
			
		||||
 | 
			
		||||
To install plugins, use the [`install`]{@link module:openmct.MCT#install}
 | 
			
		||||
method:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
openmct.install(myPlugin);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The plugin will be invoked to configure Open MCT before it is started.
 | 
			
		||||
 | 
			
		||||
### Writing Plugins
 | 
			
		||||
 | 
			
		||||
Plugins configure Open MCT, and should utilize the
 | 
			
		||||
[`openmct`]{@link module:openmct} module to do so, as summarized above in
 | 
			
		||||
"Configuring Open MCT" and "Using Open MCT" above.
 | 
			
		||||
 | 
			
		||||
### Distributing Plugins
 | 
			
		||||
 | 
			
		||||
Hosting or downloading plugins is outside of the scope of this documentation.
 | 
			
		||||
We recommend distributing plugins as UMD modules which export a single
 | 
			
		||||
function.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
# Open MCT [](http://www.apache.org/licenses/LICENSE-2.0)
 | 
			
		||||
 | 
			
		||||
Open MCT (Open Mission Control Technologies) is a next-generation mission control framework for visualization of data on desktop and mobile devices. It is developed at NASA's Ames Research Center, and is being used by NASA for data analysis of spacecraft missions, as well as planning and operation of experimental rover systems. As a generalizable and open source framework, Open MCT could be used as the basis for building applications for planning, operation, and analysis of any systems producing telemetry data.
 | 
			
		||||
Open MCT is a next-generation mission control framework for visualization of data on desktop and mobile devices. It is developed at NASA's Ames Research Center, and is being used by NASA for data analysis of spacecraft missions, as well as planning and operation of experimental rover systems. As a generalizable and open source framework, Open MCT could be used as the basis for building applications for planning, operation, and analysis of any systems producing telemetry data.
 | 
			
		||||
 | 
			
		||||
Please visit our [Official Site](https://nasa.github.io/openmct/) and [Getting Started Guide](https://nasa.github.io/openmct/getting-started/)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -131,7 +131,7 @@ Keeping that in mind, there are a few useful patterns supported by the
 | 
			
		||||
framework that are useful to keep in mind.
 | 
			
		||||
 | 
			
		||||
The specific service infrastructure provided by the platform is described
 | 
			
		||||
in the [Platform Architecture](platform.md).
 | 
			
		||||
in the [Platform Architecture](Platform.md).
 | 
			
		||||
 | 
			
		||||
## Extension Categories
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1338,6 +1338,55 @@ are supported:
 | 
			
		||||
 | 
			
		||||
Open MCT defines several Angular directives that are intended for use both 
 | 
			
		||||
internally within the platform, and by plugins. 
 | 
			
		||||
 | 
			
		||||
## Before Unload 
 | 
			
		||||
 | 
			
		||||
The `mct-before-unload` directive is used to listen for (and prompt for user 
 | 
			
		||||
confirmation) of navigation changes in the browser. This includes reloading, 
 | 
			
		||||
following links out of Open MCT, or changing routes. It is used to hook into 
 | 
			
		||||
both `onbeforeunload` event handling as well as route changes from within 
 | 
			
		||||
Angular.
 | 
			
		||||
 | 
			
		||||
This directive is useable as an attribute. Its value should be an Angular 
 | 
			
		||||
expression. When an action that would trigger an unload and/or route change 
 | 
			
		||||
occurs, this Angular expression is evaluated. Its result should be a message to 
 | 
			
		||||
display to the user to confirm their navigation change; if this expression 
 | 
			
		||||
evaluates to a falsy value, no message will be displayed. 
 | 
			
		||||
 
 | 
			
		||||
## Chart 
 | 
			
		||||
 | 
			
		||||
The `mct-chart` directive is used to support drawing of simple charts. It is 
 | 
			
		||||
present to support the Plot view, and its functionality is limited to the 
 | 
			
		||||
functionality that is relevant for that view.
 | 
			
		||||
 | 
			
		||||
This directive is used at the element level and takes one attribute, `draw` 
 | 
			
		||||
which is an Angular expression which will should evaluate to a drawing object. 
 | 
			
		||||
This drawing object should contain the following properties:
 | 
			
		||||
 | 
			
		||||
* `dimensions`: The size, in logical coordinates, of the chart area. A 
 | 
			
		||||
two-element array or numbers. 
 | 
			
		||||
* `origin`: The position, in logical coordinates, of the lower-left corner of 
 | 
			
		||||
the chart area. A two-element array or numbers. 
 | 
			
		||||
* `lines`: An array of lines (e.g. as a plot line) to draw, where each line is 
 | 
			
		||||
expressed as an object containing: 
 | 
			
		||||
    * `buffer`: A Float32Array containing points in the line, in logical 
 | 
			
		||||
    coordinates, in sequential x,y pairs. 
 | 
			
		||||
    * `color`: The color of the line, as a four-element RGBA array, where 
 | 
			
		||||
    each element is a number in the range of 0.0-1.0. 
 | 
			
		||||
    * `points`: The number of points in the line. 
 | 
			
		||||
* `boxes`: An array of rectangles to draw in the chart area. Each is an object 
 | 
			
		||||
containing: 
 | 
			
		||||
    * `start`: The first corner of the rectangle, as a two-element array of 
 | 
			
		||||
    numbers, in logical coordinates. 
 | 
			
		||||
    * `end`: The opposite corner of the rectangle, as a two-element array of 
 | 
			
		||||
    numbers, in logical coordinates. color : The color of the line, as a 
 | 
			
		||||
    four-element RGBA array, where each element is a number in the range of 
 | 
			
		||||
    0.0-1.0. 
 | 
			
		||||
 | 
			
		||||
While `mct-chart` is intended to support plots specifically, it does perform 
 | 
			
		||||
some useful management of canvas objects (e.g. choosing between WebGL and Canvas 
 | 
			
		||||
2D APIs for drawing based on browser support) so its usage is recommended when 
 | 
			
		||||
its supported drawing primitives are sufficient for other charting tasks. 
 | 
			
		||||
 
 | 
			
		||||
## Container 
 | 
			
		||||
 | 
			
		||||
@@ -2261,7 +2310,10 @@ The platform understands the following policy categories (specifiable as the
 | 
			
		||||
 | 
			
		||||
* `action`: Determines whether or not a given action is allowable. The candidate 
 | 
			
		||||
argument here is an Action; the context is its action context object.
 | 
			
		||||
* `composition`: Determines whether or not domain objects of a given type (first argument, `parentType`) can contain a given object (second argument, `child`).
 | 
			
		||||
* `composition`: Determines whether or not domain objects of a given type are 
 | 
			
		||||
allowed to contain domain objects of another type. The candidate argument here 
 | 
			
		||||
is the container's `Type`; the context argument is the `Type` of the object to be 
 | 
			
		||||
contained.
 | 
			
		||||
* `view`: Determines whether or not a view is applicable for a domain object. 
 | 
			
		||||
The candidate argument is the view's extension definition; the context argument 
 | 
			
		||||
is the `DomainObject` to be viewed.
 | 
			
		||||
 
 | 
			
		||||
@@ -129,7 +129,7 @@ We will create this file in the directory tutorials/todo (we can hereafter refer
 | 
			
		||||
to this plugin as tutorials/todo as well.) We will start with an "empty bundle", 
 | 
			
		||||
one which exposes no extensions - which looks like:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```diff
 | 
			
		||||
define([
 | 
			
		||||
    'openmct'
 | 
			
		||||
], function (
 | 
			
		||||
@@ -154,7 +154,7 @@ The tutorials will be updated with the new bundle registration mechanism once it
 | 
			
		||||
has been finalized. 
 | 
			
		||||
 | 
			
		||||
#### Before
 | 
			
		||||
```html
 | 
			
		||||
```diff
 | 
			
		||||
<!--
 | 
			
		||||
 Open MCT, Copyright (c) 2014-2016, United States Government
 | 
			
		||||
 as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
@@ -219,7 +219,7 @@ __index.html__
 | 
			
		||||
 | 
			
		||||
#### After
 | 
			
		||||
 | 
			
		||||
```html
 | 
			
		||||
```diff
 | 
			
		||||
<!--
 | 
			
		||||
 Open MCT, Copyright (c) 2014-2016, United States Government
 | 
			
		||||
 as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
@@ -305,7 +305,7 @@ In the case of our to-do list feature, the to-do list itself is the thing we'll
 | 
			
		||||
want users to be able to create and edit. So, we will add that as a new type in 
 | 
			
		||||
our bundle definition:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```diff
 | 
			
		||||
define([
 | 
			
		||||
    'openmct'
 | 
			
		||||
], function (
 | 
			
		||||
@@ -320,7 +320,7 @@ define([
 | 
			
		||||
+          {
 | 
			
		||||
+              "key": "example.todo",
 | 
			
		||||
+              "name": "To-Do List",
 | 
			
		||||
+              "cssClass": "icon-check",
 | 
			
		||||
+              "cssclass": "icon-check",
 | 
			
		||||
+              "description": "A list of things that need to be done.",
 | 
			
		||||
+              "features": ["creation"]
 | 
			
		||||
+          }
 | 
			
		||||
@@ -340,9 +340,8 @@ Going through the properties we've defined:
 | 
			
		||||
domain objects of this type.
 | 
			
		||||
* The `name` of "To-Do List" is the human-readable name for this type, and will 
 | 
			
		||||
be shown to users.
 | 
			
		||||
* The `cssClass` maps to an icon that will be shown for each To-Do List. The icons 
 | 
			
		||||
are defined in our [custom open MCT icon set](https://github.com/nasa/openmct/blob/master/platform/commonUI/general/res/sass/_glyphs.scss). 
 | 
			
		||||
A complete list of available icons will be provided in the future.
 | 
			
		||||
* The `glyph` refers to a special character in Open MCT's custom font set; 
 | 
			
		||||
this will be used as an icon.
 | 
			
		||||
* The `description` is also human-readable, and will be used whenever a longer 
 | 
			
		||||
explanation of what this type is should be shown.
 | 
			
		||||
* Finally, the `features` property describes some special features of objects of 
 | 
			
		||||
@@ -370,7 +369,7 @@ directory `tutorials/todo/res/templates` (`res` is, by default, the directory
 | 
			
		||||
where bundle-related resources are kept, and `templates` is where HTML templates 
 | 
			
		||||
are stored by convention.)
 | 
			
		||||
 | 
			
		||||
```html
 | 
			
		||||
```diff
 | 
			
		||||
<div>
 | 
			
		||||
    <a href="">All</a>
 | 
			
		||||
    <a href="">Incomplete</a>
 | 
			
		||||
@@ -402,7 +401,7 @@ boolean `completed` flag.
 | 
			
		||||
To expose this view in Open MCT, we need to declare it in our bundle 
 | 
			
		||||
definition:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```diff
 | 
			
		||||
define([
 | 
			
		||||
    'openmct'
 | 
			
		||||
], function (
 | 
			
		||||
@@ -416,7 +415,7 @@ define([
 | 
			
		||||
            {
 | 
			
		||||
                "key": "example.todo",
 | 
			
		||||
                "name": "To-Do List",
 | 
			
		||||
                "cssClass": "icon-check",
 | 
			
		||||
                "cssclass": "icon-check",
 | 
			
		||||
                "description": "A list of things that need to be done.",
 | 
			
		||||
                "features": ["creation"]
 | 
			
		||||
            }
 | 
			
		||||
@@ -425,7 +424,7 @@ define([
 | 
			
		||||
+           {
 | 
			
		||||
+               "key": "example.todo",
 | 
			
		||||
+               "type": "example.todo",
 | 
			
		||||
+               "cssClass": "icon-check",
 | 
			
		||||
+               "cssclass": "icon-check",
 | 
			
		||||
+               "name": "List",
 | 
			
		||||
+               "templateUrl": "templates/todo.html",
 | 
			
		||||
+               "editable": true
 | 
			
		||||
@@ -447,7 +446,7 @@ the domain object type, but could have chosen any unique name.
 | 
			
		||||
domain objects of that type. This means that we'll see this view for To-do Lists 
 | 
			
		||||
that we create, but not for other domain objects (such as Folders.)
 | 
			
		||||
 | 
			
		||||
* The `cssClass` and `name` properties describe the icon and human-readable name 
 | 
			
		||||
* The `glyph` and `name` properties describe the icon and human-readable name 
 | 
			
		||||
for this view to display in the UI where needed (if multiple views are available 
 | 
			
		||||
for To-do Lists, the user will be able to choose one.)
 | 
			
		||||
 | 
			
		||||
@@ -459,7 +458,7 @@ the user to create these yet. As a temporary workaround to test the view, we
 | 
			
		||||
will specify an initial state for To-do List domain object models in the 
 | 
			
		||||
definition of that type.
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```diff
 | 
			
		||||
define([
 | 
			
		||||
    'openmct'
 | 
			
		||||
], function (
 | 
			
		||||
@@ -473,7 +472,7 @@ define([
 | 
			
		||||
            {
 | 
			
		||||
                "key": "example.todo",
 | 
			
		||||
                "name": "To-Do List",
 | 
			
		||||
                "cssClass": "icon-check",
 | 
			
		||||
                "cssclass": "icon-check",
 | 
			
		||||
                "description": "A list of things that need to be done.",
 | 
			
		||||
                "features": ["creation"],
 | 
			
		||||
+               "model": {
 | 
			
		||||
@@ -488,7 +487,7 @@ define([
 | 
			
		||||
            {
 | 
			
		||||
                "key": "example.todo",
 | 
			
		||||
                "type": "example.todo",
 | 
			
		||||
                "cssClass": "icon-check",
 | 
			
		||||
                "cssclass": "icon-check",
 | 
			
		||||
                "name": "List",
 | 
			
		||||
                "templateUrl": "templates/todo.html",
 | 
			
		||||
                "editable": true
 | 
			
		||||
@@ -530,7 +529,7 @@ in the directory `tutorials/todo/src/controllers` (`src` is, by default, the
 | 
			
		||||
directory where bundle-related source code is kept, and controllers is where 
 | 
			
		||||
Angular controllers are stored by convention.)
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```diff
 | 
			
		||||
define(function () {
 | 
			
		||||
    function TodoController($scope) {
 | 
			
		||||
        var showAll = true,
 | 
			
		||||
@@ -595,7 +594,7 @@ prior to our template being utilized.
 | 
			
		||||
On its own, this controller merely exposes these functions; the next step is to 
 | 
			
		||||
use them from our template:
 | 
			
		||||
 | 
			
		||||
```html
 | 
			
		||||
```diff
 | 
			
		||||
+  <div ng-controller="TodoController">
 | 
			
		||||
        <div>
 | 
			
		||||
+          <a ng-click="setVisibility(true)">All</a>
 | 
			
		||||
@@ -631,7 +630,7 @@ If we were to try to run at this point, we'd run into problems because the
 | 
			
		||||
`TodoController` has not been registered with Angular. We need to first declare 
 | 
			
		||||
it in our bundle definition, as an extension of category `controllers`:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```diff
 | 
			
		||||
define([
 | 
			
		||||
    'openmct',
 | 
			
		||||
+    './src/controllers/TodoController'
 | 
			
		||||
@@ -647,7 +646,7 @@ define([
 | 
			
		||||
            {
 | 
			
		||||
                "key": "example.todo",
 | 
			
		||||
                "name": "To-Do List",
 | 
			
		||||
                "cssClass": "icon-check",
 | 
			
		||||
                "cssclass": "icon-check",
 | 
			
		||||
                "description": "A list of things that need to be done.",
 | 
			
		||||
                "features": ["creation"],
 | 
			
		||||
                "model": {
 | 
			
		||||
@@ -662,7 +661,7 @@ define([
 | 
			
		||||
            {
 | 
			
		||||
                "key": "example.todo",
 | 
			
		||||
                "type": "example.todo",
 | 
			
		||||
                "cssClass": "icon-check",
 | 
			
		||||
                "cssclass": "icon-check",
 | 
			
		||||
                "name": "List",
 | 
			
		||||
                "templateUrl": "templates/todo.html",
 | 
			
		||||
                "editable": true
 | 
			
		||||
@@ -725,7 +724,7 @@ An Editing user interface is typically handled in a tool bar associated with a
 | 
			
		||||
view. The contents of this tool bar are defined declaratively in a view's 
 | 
			
		||||
extension definition.
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```diff
 | 
			
		||||
define([
 | 
			
		||||
    'openmct',
 | 
			
		||||
    './src/controllers/TodoController'
 | 
			
		||||
@@ -741,7 +740,7 @@ define([
 | 
			
		||||
            {
 | 
			
		||||
                "key": "example.todo",
 | 
			
		||||
                "name": "To-Do List",
 | 
			
		||||
                "cssClass": "icon-check",
 | 
			
		||||
                "cssclass": "icon-check",
 | 
			
		||||
                "description": "A list of things that need to be done.",
 | 
			
		||||
                "features": ["creation"],
 | 
			
		||||
                "model": {
 | 
			
		||||
@@ -756,7 +755,7 @@ define([
 | 
			
		||||
            {
 | 
			
		||||
                "key": "example.todo",
 | 
			
		||||
                "type": "example.todo",
 | 
			
		||||
                "cssClass": "icon-check",
 | 
			
		||||
                "cssclass": "icon-check",
 | 
			
		||||
                "name": "List",
 | 
			
		||||
                "templateUrl": "templates/todo.html",
 | 
			
		||||
                "editable": true,
 | 
			
		||||
@@ -766,7 +765,7 @@ define([
 | 
			
		||||
+                           "items": [
 | 
			
		||||
+                               {
 | 
			
		||||
+                                   "text": "Add Task",
 | 
			
		||||
+                                   "cssClass": "icon-plus",
 | 
			
		||||
+                                   "cssclass": "icon-plus",
 | 
			
		||||
+                                   "method": "addTask",
 | 
			
		||||
+                                   "control": "button"
 | 
			
		||||
+                               }
 | 
			
		||||
@@ -775,7 +774,7 @@ define([
 | 
			
		||||
+                       {
 | 
			
		||||
+                           "items": [
 | 
			
		||||
+                               {
 | 
			
		||||
+                                   "cssClass": "icon-trash",
 | 
			
		||||
+                                   "cssclass": "icon-trash",
 | 
			
		||||
+                                   "method": "removeTask",
 | 
			
		||||
+                                   "control": "button"
 | 
			
		||||
+                               }
 | 
			
		||||
@@ -814,7 +813,7 @@ all the applicable controls, which means no controls at all.
 | 
			
		||||
 | 
			
		||||
To support selection, we will need to make some changes to our controller:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```diff
 | 
			
		||||
define(function () {
 | 
			
		||||
+    // Form to display when adding new tasks
 | 
			
		||||
+    var NEW_TASK_FORM = {
 | 
			
		||||
@@ -929,7 +928,7 @@ Additionally, we need to make changes to our template to select specific tasks
 | 
			
		||||
in response to some user gesture. Here, we will select tasks when a user clicks 
 | 
			
		||||
the description.
 | 
			
		||||
 | 
			
		||||
```html
 | 
			
		||||
```diff
 | 
			
		||||
<div ng-controller="TodoController">
 | 
			
		||||
    <div>
 | 
			
		||||
        <a ng-click="setVisibility(true)">All</a>
 | 
			
		||||
@@ -955,7 +954,7 @@ __tutorials/todo/res/templates/todo.html__
 | 
			
		||||
Finally, the `TodoController` uses the `dialogService` now, so we need to 
 | 
			
		||||
declare that dependency in its extension definition:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```diff
 | 
			
		||||
define([
 | 
			
		||||
    'openmct',
 | 
			
		||||
    './src/controllers/TodoController'
 | 
			
		||||
@@ -971,7 +970,7 @@ define([
 | 
			
		||||
            {
 | 
			
		||||
                "key": "example.todo",
 | 
			
		||||
                "name": "To-Do List",
 | 
			
		||||
                "cssClass": "icon-check",
 | 
			
		||||
                "cssclass": "icon-check",
 | 
			
		||||
                "description": "A list of things that need to be done.",
 | 
			
		||||
                "features": ["creation"],
 | 
			
		||||
                "model": {
 | 
			
		||||
@@ -986,7 +985,7 @@ define([
 | 
			
		||||
            {
 | 
			
		||||
                "key": "example.todo",
 | 
			
		||||
                "type": "example.todo",
 | 
			
		||||
                "cssClass": "icon-check",
 | 
			
		||||
                "cssclass": "icon-check",
 | 
			
		||||
                "name": "List",
 | 
			
		||||
                "templateUrl": "templates/todo.html",
 | 
			
		||||
                "editable": true,
 | 
			
		||||
@@ -996,7 +995,7 @@ define([
 | 
			
		||||
                            "items": [
 | 
			
		||||
                                {
 | 
			
		||||
                                    "text": "Add Task",
 | 
			
		||||
                                    "cssClass": "icon-plus",
 | 
			
		||||
                                    "cssclass": "icon-plus",
 | 
			
		||||
                                    "method": "addTask",
 | 
			
		||||
                                    "control": "button"
 | 
			
		||||
                                }
 | 
			
		||||
@@ -1005,7 +1004,7 @@ define([
 | 
			
		||||
                        {
 | 
			
		||||
                            "items": [
 | 
			
		||||
                                {
 | 
			
		||||
                                    "cssClass": "icon-trash",
 | 
			
		||||
                                    "cssclass": "icon-trash",
 | 
			
		||||
                                    "method": "removeTask",
 | 
			
		||||
                                    "control": "button"
 | 
			
		||||
                                }
 | 
			
		||||
@@ -1059,7 +1058,7 @@ In this section, our goal is to:
 | 
			
		||||
To support the first two, we'll need to expose some methods for checking these 
 | 
			
		||||
states in the controller:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```diff
 | 
			
		||||
define(function () {
 | 
			
		||||
    // Form to display when adding new tasks
 | 
			
		||||
    var NEW_TASK_FORM = {
 | 
			
		||||
@@ -1176,7 +1175,7 @@ states visually, and to generally improve the appearance of our view. We add
 | 
			
		||||
another file to the res directory of our bundle; this time, it is `css/todo.css` 
 | 
			
		||||
(with the `css` directory again being a convention.)
 | 
			
		||||
 | 
			
		||||
```css
 | 
			
		||||
```diff
 | 
			
		||||
.example-todo div.example-button-group {
 | 
			
		||||
    margin-top: 12px;
 | 
			
		||||
    margin-bottom: 12px;
 | 
			
		||||
@@ -1220,7 +1219,7 @@ To include this CSS file in our running instance of Open MCT, we need to
 | 
			
		||||
declare it in our bundle definition, this time as an extension of category 
 | 
			
		||||
`stylesheets`:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```diff
 | 
			
		||||
define([
 | 
			
		||||
    'openmct',
 | 
			
		||||
    './src/controllers/TodoController'
 | 
			
		||||
@@ -1236,7 +1235,7 @@ define([
 | 
			
		||||
            {
 | 
			
		||||
                "key": "example.todo",
 | 
			
		||||
                "name": "To-Do List",
 | 
			
		||||
                "cssClass": "icon-check",
 | 
			
		||||
                "cssclass": "icon-check",
 | 
			
		||||
                "description": "A list of things that need to be done.",
 | 
			
		||||
                "features": ["creation"],
 | 
			
		||||
                "model": {
 | 
			
		||||
@@ -1248,7 +1247,7 @@ define([
 | 
			
		||||
            {
 | 
			
		||||
                "key": "example.todo",
 | 
			
		||||
                "type": "example.todo",
 | 
			
		||||
                "cssClass": "icon-check",
 | 
			
		||||
                "cssclass": "icon-check",
 | 
			
		||||
                "name": "List",
 | 
			
		||||
                "templateUrl": "templates/todo.html",
 | 
			
		||||
                "editable": true,
 | 
			
		||||
@@ -1258,7 +1257,7 @@ define([
 | 
			
		||||
                            "items": [
 | 
			
		||||
                                {
 | 
			
		||||
                                    "text": "Add Task",
 | 
			
		||||
                                    "cssClass": "icon-plus",
 | 
			
		||||
                                    "cssclass": "icon-plus",
 | 
			
		||||
                                    "method": "addTask",
 | 
			
		||||
                                    "control": "button"
 | 
			
		||||
                                }
 | 
			
		||||
@@ -1267,7 +1266,7 @@ define([
 | 
			
		||||
                        {
 | 
			
		||||
                            "items": [
 | 
			
		||||
                                {
 | 
			
		||||
                                    "cssClass": "icon-trash",
 | 
			
		||||
                                    "cssclass": "icon-trash",
 | 
			
		||||
                                    "method": "removeTask",
 | 
			
		||||
                                    "control": "button"
 | 
			
		||||
                                }
 | 
			
		||||
@@ -1300,7 +1299,7 @@ To-Do List's type above; now To-Do Lists will start off empty.
 | 
			
		||||
 | 
			
		||||
Finally, let's utilize these changes from our view's template:
 | 
			
		||||
 | 
			
		||||
```html
 | 
			
		||||
```diff
 | 
			
		||||
+ <div ng-controller="TodoController" class="example-todo">
 | 
			
		||||
+     <div class="example-button-group">
 | 
			
		||||
+         <a ng-class="{ selected: checkVisibility(true) }"
 | 
			
		||||
@@ -1360,7 +1359,7 @@ We'll also be defining some custom styles, so we'll include that extension as
 | 
			
		||||
well. We'll be creating this plugin in `tutorials/bargraph`, so our initial 
 | 
			
		||||
bundle definition looks like:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```diff
 | 
			
		||||
define([
 | 
			
		||||
    'openmct'
 | 
			
		||||
], function (
 | 
			
		||||
@@ -1374,7 +1373,7 @@ define([
 | 
			
		||||
            {
 | 
			
		||||
                "name": "Bar Graph",
 | 
			
		||||
                "key": "example.bargraph",
 | 
			
		||||
                "cssClass": "icon-autoflow-tabular",
 | 
			
		||||
                "cssclass": "icon-autoflow-tabular",
 | 
			
		||||
                "templateUrl": "templates/bargraph.html",
 | 
			
		||||
                "needs": [ "telemetry" ],
 | 
			
		||||
                "delegation": true
 | 
			
		||||
@@ -1407,7 +1406,7 @@ For this tutorial, we'll assume that we've sketched out our template and CSS
 | 
			
		||||
file ahead of time to describe the general look we want for the view. These 
 | 
			
		||||
look like:
 | 
			
		||||
 | 
			
		||||
```html
 | 
			
		||||
```diff
 | 
			
		||||
<div class="example-bargraph">
 | 
			
		||||
    <div class="example-tick-labels">
 | 
			
		||||
        <div class="example-tick-label" style="bottom: 0%">High</div>
 | 
			
		||||
@@ -1458,7 +1457,7 @@ bar corresponds to which telemetry point. Inline `style` attributes are used
 | 
			
		||||
wherever dynamic positioning (handled by a script) is anticipated.
 | 
			
		||||
The corresponding CSS file which styles and positions these elements:
 | 
			
		||||
 | 
			
		||||
```css
 | 
			
		||||
```diff
 | 
			
		||||
.example-bargraph {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 0;
 | 
			
		||||
@@ -1556,7 +1555,7 @@ Notably, we will not try to show telemetry data after this step.
 | 
			
		||||
 | 
			
		||||
To support this, we will add a new controller which supports our Bar Graph view:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```diff
 | 
			
		||||
define(function () {
 | 
			
		||||
    function BarGraphController($scope, telemetryHandler) {
 | 
			
		||||
        var handle;
 | 
			
		||||
@@ -1608,7 +1607,7 @@ telemetry objects in view, as well as the width for each bar.
 | 
			
		||||
 | 
			
		||||
We will also utilize this from our template:
 | 
			
		||||
 | 
			
		||||
```html
 | 
			
		||||
```diff
 | 
			
		||||
+ <div class="example-bargraph" ng-controller="BarGraphController">
 | 
			
		||||
    <div class="example-tick-labels">
 | 
			
		||||
+       <div ng-repeat="value in [low, middle, high] track by $index"
 | 
			
		||||
@@ -1661,7 +1660,7 @@ Finally, we expose our controller from our bundle definition. Note that the
 | 
			
		||||
depends declaration includes both `$scope` as well as the `telemetryHandler` 
 | 
			
		||||
service we made use of.
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```diff
 | 
			
		||||
define([
 | 
			
		||||
    'openmct',
 | 
			
		||||
    './src/controllers/BarGraphController'
 | 
			
		||||
@@ -1677,7 +1676,7 @@ define([
 | 
			
		||||
            {
 | 
			
		||||
                "name": "Bar Graph",
 | 
			
		||||
                "key": "example.bargraph",
 | 
			
		||||
                "cssClass": "icon-autoflow-tabular",
 | 
			
		||||
                "cssclass": "icon-autoflow-tabular",
 | 
			
		||||
                "templateUrl": "templates/bargraph.html",
 | 
			
		||||
                "needs": [ "telemetry" ],
 | 
			
		||||
                "delegation": true
 | 
			
		||||
@@ -1716,7 +1715,7 @@ First, let's add expose some more functionality from our controller. To make it
 | 
			
		||||
simple, we'll expose the top and bottom for a bar graph for a given 
 | 
			
		||||
telemetry-providing domain object, as percentages.
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```diff
 | 
			
		||||
define(function () {
 | 
			
		||||
    function BarGraphController($scope, telemetryHandler) {
 | 
			
		||||
        var handle;
 | 
			
		||||
@@ -1768,7 +1767,7 @@ decide this.
 | 
			
		||||
 | 
			
		||||
Next, we utilize this functionality from the template:
 | 
			
		||||
 | 
			
		||||
```html
 | 
			
		||||
```diff
 | 
			
		||||
<div class="example-bargraph" ng-controller="BarGraphController">
 | 
			
		||||
    <div class="example-tick-labels">
 | 
			
		||||
        <div ng-repeat="value in [low, middle, high] track by $index"
 | 
			
		||||
@@ -1827,7 +1826,7 @@ when we return to our view later, those changes will be persisted.
 | 
			
		||||
 | 
			
		||||
First, let's add a tool bar for changing these three values in Edit mode:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```diff
 | 
			
		||||
define([
 | 
			
		||||
    'openmct',
 | 
			
		||||
    './src/controllers/BarGraphController'
 | 
			
		||||
@@ -1843,7 +1842,7 @@ define([
 | 
			
		||||
            {
 | 
			
		||||
                "name": "Bar Graph",
 | 
			
		||||
                "key": "example.bargraph",
 | 
			
		||||
                "cssClass": "icon-autoflow-tabular",
 | 
			
		||||
                "cssclass": "icon-autoflow-tabular",
 | 
			
		||||
                "templateUrl": "templates/bargraph.html",
 | 
			
		||||
                "needs": [ "telemetry" ],
 | 
			
		||||
                "delegation": true,
 | 
			
		||||
@@ -1901,7 +1900,7 @@ a view proxy to work from. We will add this to our controller, and additionally
 | 
			
		||||
will start reading/writing those properties to the view's `configuration` 
 | 
			
		||||
object.
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```diff
 | 
			
		||||
define(function () {
 | 
			
		||||
    function BarGraphController($scope, telemetryHandler) {
 | 
			
		||||
        var handle;
 | 
			
		||||
@@ -2024,7 +2023,7 @@ For purposes of this tutorial, a simple node server is provided to stand
 | 
			
		||||
in place of this existing telemetry system. It generates real-time data 
 | 
			
		||||
and exposes it over a WebSocket connection.
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```diff
 | 
			
		||||
/*global require,process,console*/
 | 
			
		||||
 | 
			
		||||
var CONFIG = {
 | 
			
		||||
@@ -2206,7 +2205,7 @@ used by the server. It uses a custom format and, for purposes of example,
 | 
			
		||||
contains three "subsystems" containing a mix of numeric and string-based 
 | 
			
		||||
telemetry.
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
```diff
 | 
			
		||||
{
 | 
			
		||||
    "name": "Example Spacecraft",
 | 
			
		||||
    "identifier": "sc",
 | 
			
		||||
@@ -2320,7 +2319,7 @@ define([
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "Spacecraft",
 | 
			
		||||
                    "key": "example.spacecraft",
 | 
			
		||||
                    "cssClass": "icon-object"
 | 
			
		||||
                    "cssclass": "icon-object"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "roots": [
 | 
			
		||||
@@ -2433,7 +2432,7 @@ server. Our first step will be to add a service that will handle interactions
 | 
			
		||||
with the server; this will not be used by Open MCT directly, but will be 
 | 
			
		||||
used by subsequent components we add.
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```diff
 | 
			
		||||
/*global define,WebSocket*/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
@@ -2488,7 +2487,7 @@ subsystems. This means that we need to convert the data from the dictionary
 | 
			
		||||
into domain object models, and expose these to Open MCT via a 
 | 
			
		||||
`modelService`.
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```diff
 | 
			
		||||
/*global define*/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
@@ -2622,7 +2621,7 @@ This allows our telemetry dictionary to be expressed as domain object models
 | 
			
		||||
fix this, we will need another script which will add these subsystems to the 
 | 
			
		||||
root-level object we added in Step 1.
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```diff
 | 
			
		||||
/*global define*/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
@@ -2687,7 +2686,7 @@ Finally, we wire in these changes by modifying our plugin's `bundle.js` to
 | 
			
		||||
provide metadata about how these pieces interact (both with each other, and 
 | 
			
		||||
with the platform):
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```diff
 | 
			
		||||
define([
 | 
			
		||||
    'openmct',
 | 
			
		||||
+   './src/ExampleTelemetryServerAdapter',
 | 
			
		||||
@@ -2706,18 +2705,18 @@ define([
 | 
			
		||||
            {
 | 
			
		||||
                "name": "Spacecraft",
 | 
			
		||||
                "key": "example.spacecraft",
 | 
			
		||||
                "cssClass": "icon-object"
 | 
			
		||||
                "cssclass": "icon-object"
 | 
			
		||||
            },
 | 
			
		||||
+           {
 | 
			
		||||
+               "name": "Subsystem",
 | 
			
		||||
+               "key": "example.subsystem",
 | 
			
		||||
+               "cssClass": "icon-object",
 | 
			
		||||
+               "cssclass": "icon-object",
 | 
			
		||||
+               "model": { "composition": [] }
 | 
			
		||||
+           },
 | 
			
		||||
+           {
 | 
			
		||||
+               "name": "Measurement",
 | 
			
		||||
+               "key": "example.measurement",
 | 
			
		||||
+               "cssClass": "icon-telemetry",
 | 
			
		||||
+               "cssclass": "icon-telemetry",
 | 
			
		||||
+               "model": { "telemetry": {} },
 | 
			
		||||
+               "telemetry": {
 | 
			
		||||
+                   "source": "example.source",
 | 
			
		||||
@@ -2835,7 +2834,7 @@ will do so for the server's historical telemetry.
 | 
			
		||||
Our first step will be to add a method to our server adapter which allows us to 
 | 
			
		||||
send history requests to the server:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```diff
 | 
			
		||||
/*global define,WebSocket*/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
@@ -2894,7 +2893,7 @@ identifier, the pending promise is resolved.
 | 
			
		||||
This `history` method will be used by a `telemetryService` provider which we 
 | 
			
		||||
will implement:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```diff
 | 
			
		||||
/*global define*/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
@@ -2980,7 +2979,7 @@ Finally, note that we also have a `subscribe` method, to satisfy the interface o
 | 
			
		||||
 | 
			
		||||
This script uses an `ExampleTelemetrySeries` class, which looks like:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```diff
 | 
			
		||||
/*global define*/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
@@ -3012,7 +3011,7 @@ it with the interface expected by the platform (the methods shown.)
 | 
			
		||||
 | 
			
		||||
Finally, we expose this `telemetryService` provider declaratively:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```diff
 | 
			
		||||
define([
 | 
			
		||||
    'openmct',
 | 
			
		||||
    './src/ExampleTelemetryServerAdapter',
 | 
			
		||||
@@ -3031,18 +3030,18 @@ define([
 | 
			
		||||
            {
 | 
			
		||||
                "name": "Spacecraft",
 | 
			
		||||
                "key": "example.spacecraft",
 | 
			
		||||
                "cssClass": "icon-object"
 | 
			
		||||
                "cssclass": "icon-object"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "name": "Subsystem",
 | 
			
		||||
                "key": "example.subsystem",
 | 
			
		||||
                "cssClass": "icon-object",
 | 
			
		||||
                "cssclass": "icon-object",
 | 
			
		||||
                "model": { "composition": [] }
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "name": "Measurement",
 | 
			
		||||
                "key": "example.measurement",
 | 
			
		||||
                "cssClass": "icon-telemetry",
 | 
			
		||||
                "cssclass": "icon-telemetry",
 | 
			
		||||
                "model": { "telemetry": {} },
 | 
			
		||||
                "telemetry": {
 | 
			
		||||
                    "source": "example.source",
 | 
			
		||||
@@ -3127,7 +3126,7 @@ Finally, we want to utilize the server's ability to subscribe to telemetry
 | 
			
		||||
from Open MCT. To do this, first we want to expose some new methods for 
 | 
			
		||||
this from our server adapter:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```diff
 | 
			
		||||
/*global define,WebSocket*/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
@@ -3200,7 +3199,7 @@ with these subscriptions.
 | 
			
		||||
 | 
			
		||||
We then need only to utilize these methods from our `telemetryService`:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```diff
 | 
			
		||||
/*global define*/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
@@ -3306,4 +3305,4 @@ server can handle this.)
 | 
			
		||||
Running Open MCT again, we can still plot our historical telemetry - but 
 | 
			
		||||
now we also see that it updates in real-time as more data comes in from the 
 | 
			
		||||
server.
 | 
			
		||||
 
 | 
			
		||||
 
 | 
			
		||||
@@ -49,7 +49,7 @@ define([
 | 
			
		||||
                {
 | 
			
		||||
                    "key": "eventGenerator",
 | 
			
		||||
                    "name": "Event Message Generator",
 | 
			
		||||
                    "cssClass": "icon-folder-new",
 | 
			
		||||
                    "cssclass": "icon-folder-new",
 | 
			
		||||
                    "description": "For development use. Creates sample event message data that mimics a live data stream.",
 | 
			
		||||
                    "priority": 10,
 | 
			
		||||
                    "features": "creation",
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@ define([
 | 
			
		||||
                    "name": "Export Telemetry as CSV",
 | 
			
		||||
                    "implementation": ExportTelemetryAsCSVAction,
 | 
			
		||||
                    "category": "contextual",
 | 
			
		||||
                    "cssClass": "icon-download",
 | 
			
		||||
                    "cssclass": "icon-download",
 | 
			
		||||
                    "depends": [ "exportService" ]
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										183
									
								
								example/generator/bundle.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								example/generator/bundle.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,183 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2016, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT 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 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([
 | 
			
		||||
    "./src/SinewaveTelemetryProvider",
 | 
			
		||||
    "./src/SinewaveLimitCapability",
 | 
			
		||||
    "./src/SinewaveDeltaFormat",
 | 
			
		||||
    'legacyRegistry'
 | 
			
		||||
], function (
 | 
			
		||||
    SinewaveTelemetryProvider,
 | 
			
		||||
    SinewaveLimitCapability,
 | 
			
		||||
    SinewaveDeltaFormat,
 | 
			
		||||
    legacyRegistry
 | 
			
		||||
) {
 | 
			
		||||
    "use strict";
 | 
			
		||||
 | 
			
		||||
    legacyRegistry.register("example/generator", {
 | 
			
		||||
        "name": "Sine Wave Generator",
 | 
			
		||||
        "description": "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
 | 
			
		||||
        "extensions": {
 | 
			
		||||
            "components": [
 | 
			
		||||
                {
 | 
			
		||||
                    "implementation": SinewaveTelemetryProvider,
 | 
			
		||||
                    "type": "provider",
 | 
			
		||||
                    "provides": "telemetryService",
 | 
			
		||||
                    "depends": [
 | 
			
		||||
                        "$q",
 | 
			
		||||
                        "$timeout"
 | 
			
		||||
                    ]
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "capabilities": [
 | 
			
		||||
                {
 | 
			
		||||
                    "key": "limit",
 | 
			
		||||
                    "implementation": SinewaveLimitCapability
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "formats": [
 | 
			
		||||
                {
 | 
			
		||||
                    "key": "example.delta",
 | 
			
		||||
                    "implementation": SinewaveDeltaFormat
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "constants": [
 | 
			
		||||
                {
 | 
			
		||||
                    "key": "TIME_CONDUCTOR_DOMAINS",
 | 
			
		||||
                    "value": [
 | 
			
		||||
                        {
 | 
			
		||||
                            "key": "time",
 | 
			
		||||
                            "name": "Time"
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            "key": "yesterday",
 | 
			
		||||
                            "name": "Yesterday"
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            "key": "delta",
 | 
			
		||||
                            "name": "Delta",
 | 
			
		||||
                            "format": "example.delta"
 | 
			
		||||
                        }
 | 
			
		||||
                    ],
 | 
			
		||||
                    "priority": -1
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "types": [
 | 
			
		||||
                {
 | 
			
		||||
                    "key": "generator",
 | 
			
		||||
                    "name": "Sine Wave Generator",
 | 
			
		||||
                    "cssclass": "icon-telemetry",
 | 
			
		||||
                    "description": "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
 | 
			
		||||
                    "priority": 10,
 | 
			
		||||
                    "features": "creation",
 | 
			
		||||
                    "model": {
 | 
			
		||||
                        "telemetry": {
 | 
			
		||||
                            "period": 10,
 | 
			
		||||
                            "amplitude": 1,
 | 
			
		||||
                            "offset": 0,
 | 
			
		||||
                            "dataRateInHz": 1
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    "telemetry": {
 | 
			
		||||
                        "source": "generator",
 | 
			
		||||
                        "domains": [
 | 
			
		||||
                            {
 | 
			
		||||
                                "key": "utc",
 | 
			
		||||
                                "name": "Time"
 | 
			
		||||
                            },
 | 
			
		||||
                            {
 | 
			
		||||
                                "key": "yesterday",
 | 
			
		||||
                                "name": "Yesterday"
 | 
			
		||||
                            },
 | 
			
		||||
                            {
 | 
			
		||||
                                "key": "delta",
 | 
			
		||||
                                "name": "Delta",
 | 
			
		||||
                                "format": "example.delta"
 | 
			
		||||
                            }
 | 
			
		||||
                        ],
 | 
			
		||||
                        "ranges": [
 | 
			
		||||
                            {
 | 
			
		||||
                                "key": "sin",
 | 
			
		||||
                                "name": "Sine"
 | 
			
		||||
                            },
 | 
			
		||||
                            {
 | 
			
		||||
                                "key": "cos",
 | 
			
		||||
                                "name": "Cosine"
 | 
			
		||||
                            }
 | 
			
		||||
                        ]
 | 
			
		||||
                    },
 | 
			
		||||
                    "properties": [
 | 
			
		||||
                        {
 | 
			
		||||
                            "name": "Period",
 | 
			
		||||
                            "control": "textfield",
 | 
			
		||||
                            "cssclass": "l-input-sm l-numeric",
 | 
			
		||||
                            "key": "period",
 | 
			
		||||
                            "required": true,
 | 
			
		||||
                            "property": [
 | 
			
		||||
                                "telemetry",
 | 
			
		||||
                                "period"
 | 
			
		||||
                            ],
 | 
			
		||||
                            "pattern": "^\\d*(\\.\\d*)?$"
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            "name": "Amplitude",
 | 
			
		||||
                            "control": "textfield",
 | 
			
		||||
                            "cssclass": "l-input-sm l-numeric",
 | 
			
		||||
                            "key": "amplitude",
 | 
			
		||||
                            "required": true,
 | 
			
		||||
                            "property": [
 | 
			
		||||
                                "telemetry",
 | 
			
		||||
                                "amplitude"
 | 
			
		||||
                            ],
 | 
			
		||||
                            "pattern": "^\\d*(\\.\\d*)?$"
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            "name": "Offset",
 | 
			
		||||
                            "control": "textfield",
 | 
			
		||||
                            "cssclass": "l-input-sm l-numeric",
 | 
			
		||||
                            "key": "offset",
 | 
			
		||||
                            "required": true,
 | 
			
		||||
                            "property": [
 | 
			
		||||
                                "telemetry",
 | 
			
		||||
                                "offset"
 | 
			
		||||
                            ],
 | 
			
		||||
                            "pattern": "^\\d*(\\.\\d*)?$"
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            "name": "Data Rate (hz)",
 | 
			
		||||
                            "control": "textfield",
 | 
			
		||||
                            "cssclass": "l-input-sm l-numeric",
 | 
			
		||||
                            "key": "dataRateInHz",
 | 
			
		||||
                            "required": true,
 | 
			
		||||
                            "property": [
 | 
			
		||||
                                "telemetry",
 | 
			
		||||
                                "dataRateInHz"
 | 
			
		||||
                            ],
 | 
			
		||||
                            "pattern": "^\\d*(\\.\\d*)?$"
 | 
			
		||||
                        }
 | 
			
		||||
                    ]
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -1,171 +0,0 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2016, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT 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 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([
 | 
			
		||||
    "./GeneratorProvider",
 | 
			
		||||
    "./SinewaveLimitCapability",
 | 
			
		||||
    "./SinewaveDeltaFormat"
 | 
			
		||||
], function (
 | 
			
		||||
    GeneratorProvider,
 | 
			
		||||
    SinewaveLimitCapability,
 | 
			
		||||
    SinewaveDeltaFormat
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    var legacyExtensions = {
 | 
			
		||||
        "capabilities": [
 | 
			
		||||
            {
 | 
			
		||||
                "key": "limit",
 | 
			
		||||
                "implementation": SinewaveLimitCapability
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "formats": [
 | 
			
		||||
            {
 | 
			
		||||
                "key": "example.delta",
 | 
			
		||||
                "implementation": SinewaveDeltaFormat
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "constants": [
 | 
			
		||||
            {
 | 
			
		||||
                "key": "TIME_CONDUCTOR_DOMAINS",
 | 
			
		||||
                "value": [
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "time",
 | 
			
		||||
                        "name": "Time"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "yesterday",
 | 
			
		||||
                        "name": "Yesterday"
 | 
			
		||||
                    },
 | 
			
		||||
                    {
 | 
			
		||||
                        "key": "delta",
 | 
			
		||||
                        "name": "Delta"
 | 
			
		||||
                    }
 | 
			
		||||
                ],
 | 
			
		||||
                "priority": -1
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return function(openmct){
 | 
			
		||||
        //Register legacy extensions for things not yet supported by the new API
 | 
			
		||||
        Object.keys(legacyExtensions).forEach(function (type){
 | 
			
		||||
            var extensionsOfType = legacyExtensions[type];
 | 
			
		||||
            extensionsOfType.forEach(function (extension) {
 | 
			
		||||
                openmct.legacyExtension(type, extension)
 | 
			
		||||
            })
 | 
			
		||||
        });
 | 
			
		||||
        openmct.types.addType("generator", {
 | 
			
		||||
            label: "Sine Wave Generator",
 | 
			
		||||
            description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
 | 
			
		||||
            cssClass: "icon-telemetry",
 | 
			
		||||
            creatable: true,
 | 
			
		||||
            form: [
 | 
			
		||||
                {
 | 
			
		||||
                    name: "Period",
 | 
			
		||||
                    control: "textfield",
 | 
			
		||||
                    cssClass: "l-input-sm l-numeric",
 | 
			
		||||
                    key: "period",
 | 
			
		||||
                    required: true,
 | 
			
		||||
                    property: [
 | 
			
		||||
                        "telemetry",
 | 
			
		||||
                        "period"
 | 
			
		||||
                    ],
 | 
			
		||||
                    pattern: "^\\d*(\\.\\d*)?$"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: "Amplitude",
 | 
			
		||||
                    control: "textfield",
 | 
			
		||||
                    cssClass: "l-input-sm l-numeric",
 | 
			
		||||
                    key: "amplitude",
 | 
			
		||||
                    required: true,
 | 
			
		||||
                    property: [
 | 
			
		||||
                        "telemetry",
 | 
			
		||||
                        "amplitude"
 | 
			
		||||
                    ],
 | 
			
		||||
                    pattern: "^\\d*(\\.\\d*)?$"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: "Offset",
 | 
			
		||||
                    control: "textfield",
 | 
			
		||||
                    cssClass: "l-input-sm l-numeric",
 | 
			
		||||
                    key: "offset",
 | 
			
		||||
                    required: true,
 | 
			
		||||
                    property: [
 | 
			
		||||
                        "telemetry",
 | 
			
		||||
                        "offset"
 | 
			
		||||
                    ],
 | 
			
		||||
                    pattern: "^\\d*(\\.\\d*)?$"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: "Data Rate (hz)",
 | 
			
		||||
                    control: "textfield",
 | 
			
		||||
                    cssClass: "l-input-sm l-numeric",
 | 
			
		||||
                    key: "dataRateInHz",
 | 
			
		||||
                    required: true,
 | 
			
		||||
                    property: [
 | 
			
		||||
                        "telemetry",
 | 
			
		||||
                        "dataRateInHz"
 | 
			
		||||
                    ],
 | 
			
		||||
                    pattern: "^\\d*(\\.\\d*)?$"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            initialize: function (object) {
 | 
			
		||||
                object.telemetry = {
 | 
			
		||||
                    period: 10,
 | 
			
		||||
                    amplitude: 1,
 | 
			
		||||
                    offset: 0,
 | 
			
		||||
                    dataRateInHz: 1,
 | 
			
		||||
                    domains: [
 | 
			
		||||
                        {
 | 
			
		||||
                            key: "utc",
 | 
			
		||||
                            name: "Time",
 | 
			
		||||
                            format: "utc"
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            key: "yesterday",
 | 
			
		||||
                            name: "Yesterday",
 | 
			
		||||
                            format: "utc"
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            key: "delta",
 | 
			
		||||
                            name: "Delta",
 | 
			
		||||
                            format: "example.delta"
 | 
			
		||||
                        }
 | 
			
		||||
                    ],
 | 
			
		||||
                    ranges: [
 | 
			
		||||
                        {
 | 
			
		||||
                            key: "sin",
 | 
			
		||||
                            name: "Sine"
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                            key: "cos",
 | 
			
		||||
                            name: "Cosine"
 | 
			
		||||
                        }
 | 
			
		||||
                    ]
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        openmct.telemetry.addProvider(new GeneratorProvider());
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
@@ -41,10 +41,6 @@ define([
 | 
			
		||||
        return domainObject.type === 'generator';
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    GeneratorProvider.prototype.supportsRequest =
 | 
			
		||||
        GeneratorProvider.prototype.supportsSubscribe =
 | 
			
		||||
            GeneratorProvider.prototype.canProvideTelemetry;
 | 
			
		||||
 | 
			
		||||
    GeneratorProvider.prototype.makeWorkerRequest = function (domainObject, request) {
 | 
			
		||||
        var props = [
 | 
			
		||||
            'amplitude',
 | 
			
		||||
@@ -19,7 +19,7 @@
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
/*global define*/
 | 
			
		||||
/*global define,Promise*/
 | 
			
		||||
 | 
			
		||||
define({
 | 
			
		||||
    START_TIME: Date.now() - 24 * 60 * 60 * 1000 // Now minus a day.
 | 
			
		||||
@@ -19,11 +19,12 @@
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
/*global define*/
 | 
			
		||||
/*global define,Promise*/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ['./SinewaveConstants', 'moment'],
 | 
			
		||||
    function (SinewaveConstants, moment) {
 | 
			
		||||
        "use strict";
 | 
			
		||||
 | 
			
		||||
        var START_TIME = SinewaveConstants.START_TIME,
 | 
			
		||||
            FORMAT_REGEX = /^-?\d+:\d+:\d+$/,
 | 
			
		||||
@@ -24,7 +24,6 @@
 | 
			
		||||
 | 
			
		||||
(function () {
 | 
			
		||||
 | 
			
		||||
    var FIFTEEN_MINUTES = 15 * 60 * 1000;
 | 
			
		||||
 | 
			
		||||
    var handlers = {
 | 
			
		||||
        subscribe: onSubscribe,
 | 
			
		||||
@@ -52,7 +51,6 @@
 | 
			
		||||
    function onSubscribe(message) {
 | 
			
		||||
        var data = message.data;
 | 
			
		||||
 | 
			
		||||
        // Keep
 | 
			
		||||
        var start = Date.now();
 | 
			
		||||
        var step = 1000 / data.dataRateInHz;
 | 
			
		||||
        var nextStep = start - (start % step) + step;
 | 
			
		||||
@@ -84,11 +82,8 @@
 | 
			
		||||
 | 
			
		||||
    function onRequest(message) {
 | 
			
		||||
        var data = message.data;
 | 
			
		||||
        if (data.end == undefined) {
 | 
			
		||||
            data.end = Date.now();
 | 
			
		||||
        }
 | 
			
		||||
        if (data.start == undefined){
 | 
			
		||||
            data.start = data.end - FIFTEEN_MINUTES;
 | 
			
		||||
        if (!data.start || !data.end) {
 | 
			
		||||
            throw new Error('missing start and end!');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var now = Date.now();
 | 
			
		||||
@@ -49,7 +49,7 @@ define([
 | 
			
		||||
                {
 | 
			
		||||
                    "key": "imagery",
 | 
			
		||||
                    "name": "Example Imagery",
 | 
			
		||||
                    "cssClass": "icon-image",
 | 
			
		||||
                    "cssclass": "icon-image",
 | 
			
		||||
                    "features": "creation",
 | 
			
		||||
                    "description": "For development use. Creates example imagery data that mimics a live imagery stream.",
 | 
			
		||||
                    "priority": 10,
 | 
			
		||||
 
 | 
			
		||||
@@ -31,25 +31,10 @@ define(
 | 
			
		||||
 | 
			
		||||
        var firstObservedTime = Date.now(),
 | 
			
		||||
            images = [
 | 
			
		||||
                "https://www.hq.nasa.gov/alsj/a16/AS16-117-18731.jpg",
 | 
			
		||||
                "https://www.hq.nasa.gov/alsj/a16/AS16-117-18732.jpg",
 | 
			
		||||
                "https://www.hq.nasa.gov/alsj/a16/AS16-117-18733.jpg",
 | 
			
		||||
                "https://www.hq.nasa.gov/alsj/a16/AS16-117-18734.jpg",
 | 
			
		||||
                "https://www.hq.nasa.gov/alsj/a16/AS16-117-18735.jpg",
 | 
			
		||||
                "https://www.hq.nasa.gov/alsj/a16/AS16-117-18736.jpg",
 | 
			
		||||
                "https://www.hq.nasa.gov/alsj/a16/AS16-117-18737.jpg",
 | 
			
		||||
                "https://www.hq.nasa.gov/alsj/a16/AS16-117-18738.jpg",
 | 
			
		||||
                "https://www.hq.nasa.gov/alsj/a16/AS16-117-18739.jpg",
 | 
			
		||||
                "https://www.hq.nasa.gov/alsj/a16/AS16-117-18740.jpg",
 | 
			
		||||
                "https://www.hq.nasa.gov/alsj/a16/AS16-117-18741.jpg",
 | 
			
		||||
                "https://www.hq.nasa.gov/alsj/a16/AS16-117-18742.jpg",
 | 
			
		||||
                "https://www.hq.nasa.gov/alsj/a16/AS16-117-18743.jpg",
 | 
			
		||||
                "https://www.hq.nasa.gov/alsj/a16/AS16-117-18744.jpg",
 | 
			
		||||
                "https://www.hq.nasa.gov/alsj/a16/AS16-117-18745.jpg",
 | 
			
		||||
                "https://www.hq.nasa.gov/alsj/a16/AS16-117-18746.jpg",
 | 
			
		||||
                "https://www.hq.nasa.gov/alsj/a16/AS16-117-18747.jpg",
 | 
			
		||||
                "https://www.hq.nasa.gov/alsj/a16/AS16-117-18748.jpg"
 | 
			
		||||
 | 
			
		||||
                "http://www.nasa.gov/393811main_Palomar_ao_bouchez_10s_after_impact_4x3_946-710.png",
 | 
			
		||||
                "http://www.nasa.gov/393821main_Palomar_ao_bouchez_15s_after_impact_4x3_946-710.png",
 | 
			
		||||
                "http://www.nasa.gov/images/content/393801main_CfhtVeillet2_4x3_516-387.jpg",
 | 
			
		||||
                "http://www.nasa.gov/images/content/392790main_1024_768_GeminiNorth_NightBeforeImpact_946-710.jpg"
 | 
			
		||||
            ].map(function (url, index) {
 | 
			
		||||
                return {
 | 
			
		||||
                    timestamp: firstObservedTime + 1000 * index,
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,7 @@ define(['../../../platform/features/conductor/core/src/timeSystems/LocalClock'],
 | 
			
		||||
        this.metadata = {
 | 
			
		||||
            key: 'test-lad',
 | 
			
		||||
            mode: 'lad',
 | 
			
		||||
            cssClass: 'icon-clock',
 | 
			
		||||
            cssclass: 'icon-clock',
 | 
			
		||||
            label: 'Latest Available Data',
 | 
			
		||||
            name: 'Latest available data',
 | 
			
		||||
            description: 'Monitor real-time streaming data as it comes in. The Time Conductor and displays will automatically advance themselves based on a UTC clock.'
 | 
			
		||||
 
 | 
			
		||||
@@ -37,79 +37,74 @@ define([
 | 
			
		||||
    legacyRegistry.register("example/msl", {
 | 
			
		||||
        "name" : "Mars Science Laboratory Data Adapter",
 | 
			
		||||
        "extensions" : {
 | 
			
		||||
            "types": [
 | 
			
		||||
                {
 | 
			
		||||
                    "name":"Mars Science Laboratory",
 | 
			
		||||
                    "key": "msl.curiosity",
 | 
			
		||||
                    "cssClass": "icon-object"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "Instrument",
 | 
			
		||||
                    "key": "msl.instrument",
 | 
			
		||||
                    "cssClass": "icon-object",
 | 
			
		||||
                    "model": {"composition": []}
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "Measurement",
 | 
			
		||||
                    "key": "msl.measurement",
 | 
			
		||||
                    "cssClass": "icon-telemetry",
 | 
			
		||||
                    "model": {"telemetry": {}},
 | 
			
		||||
                    "telemetry": {
 | 
			
		||||
                        "source": "rems.source",
 | 
			
		||||
                        "domains": [
 | 
			
		||||
                            {
 | 
			
		||||
                                "name": "Time",
 | 
			
		||||
                                "key": "utc",
 | 
			
		||||
                                "format": "utc"
 | 
			
		||||
                            }
 | 
			
		||||
                        ]
 | 
			
		||||
                    }
 | 
			
		||||
        "types": [
 | 
			
		||||
            {
 | 
			
		||||
                "name":"Mars Science Laboratory",
 | 
			
		||||
                "key": "msl.curiosity",
 | 
			
		||||
                "cssclass": "icon-object"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "name": "Instrument",
 | 
			
		||||
                "key": "msl.instrument",
 | 
			
		||||
                "cssclass": "icon-object",
 | 
			
		||||
                "model": {"composition": []}
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "name": "Measurement",
 | 
			
		||||
                "key": "msl.measurement",
 | 
			
		||||
                "cssclass": "icon-telemetry",
 | 
			
		||||
                "model": {"telemetry": {}},
 | 
			
		||||
                "telemetry": {
 | 
			
		||||
                    "source": "rems.source",
 | 
			
		||||
                    "domains": [
 | 
			
		||||
                        {
 | 
			
		||||
                            "name": "Time",
 | 
			
		||||
                            "key": "utc",
 | 
			
		||||
                            "format": "utc"
 | 
			
		||||
                        }
 | 
			
		||||
                    ]
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "constants": [
 | 
			
		||||
                {
 | 
			
		||||
                    "key": "REMS_WS_URL",
 | 
			
		||||
                    "value": "/proxyUrl?url=http://cab.inta-csic.es/rems/wp-content/plugins/marsweather-widget/api.php"
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "constants": [
 | 
			
		||||
            {
 | 
			
		||||
                "key": "REMS_WS_URL",
 | 
			
		||||
                "value": "/proxyUrl?url=http://cab.inta-csic.es/rems/wp-content/plugins/marsweather-widget/api.php"
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "roots": [
 | 
			
		||||
            {
 | 
			
		||||
                "id": "msl:curiosity",
 | 
			
		||||
                "priority" : "preferred",
 | 
			
		||||
                "model": {
 | 
			
		||||
                    "type": "msl.curiosity",
 | 
			
		||||
                    "name": "Mars Science Laboratory",
 | 
			
		||||
                    "composition": ["msl_tlm:rems"]
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "roots": [
 | 
			
		||||
                {
 | 
			
		||||
                    "id": "msl:curiosity"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "models": [
 | 
			
		||||
                {
 | 
			
		||||
                    "id": "msl:curiosity",
 | 
			
		||||
                    "priority": "preferred",
 | 
			
		||||
                    "model": {
 | 
			
		||||
                        "type": "msl.curiosity",
 | 
			
		||||
                        "name": "Mars Science Laboratory",
 | 
			
		||||
                        "composition": ["msl_tlm:rems"]
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "services": [
 | 
			
		||||
                {
 | 
			
		||||
                    "key":"rems.adapter",
 | 
			
		||||
                    "implementation": RemsTelemetryServerAdapter,
 | 
			
		||||
                    "depends": ["$q", "$http", "$log", "REMS_WS_URL"]
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "components": [
 | 
			
		||||
                {
 | 
			
		||||
                    "provides": "modelService",
 | 
			
		||||
                    "type": "provider",
 | 
			
		||||
                    "implementation": RemsTelemetryModelProvider,
 | 
			
		||||
                    "depends": ["rems.adapter"]
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "provides": "telemetryService",
 | 
			
		||||
                    "type": "provider",
 | 
			
		||||
                    "implementation": RemsTelemetryProvider,
 | 
			
		||||
                    "depends": ["rems.adapter", "$q"]
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        }
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "services": [
 | 
			
		||||
            {
 | 
			
		||||
                "key":"rems.adapter",
 | 
			
		||||
                "implementation": RemsTelemetryServerAdapter,
 | 
			
		||||
                "depends": ["$q", "$http", "$log", "REMS_WS_URL"]
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "components": [
 | 
			
		||||
            {
 | 
			
		||||
                "provides": "modelService",
 | 
			
		||||
                "type": "provider",
 | 
			
		||||
                "implementation": RemsTelemetryModelProvider,
 | 
			
		||||
                "depends": ["rems.adapter"]
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "provides": "telemetryService",
 | 
			
		||||
                "type": "provider",
 | 
			
		||||
                "implementation": RemsTelemetryProvider,
 | 
			
		||||
                "depends": ["rems.adapter", "$q"]
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    }
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -81,7 +81,7 @@ define([
 | 
			
		||||
                {
 | 
			
		||||
                    "key": "plot",
 | 
			
		||||
                    "name": "Example Telemetry Plot",
 | 
			
		||||
                    "cssClass": "icon-telemetry-panel",
 | 
			
		||||
                    "cssclass": "icon-telemetry-panel",
 | 
			
		||||
                    "description": "For development use. A plot for displaying telemetry.",
 | 
			
		||||
                    "priority": 10,
 | 
			
		||||
                    "delegates": [
 | 
			
		||||
@@ -129,7 +129,7 @@ define([
 | 
			
		||||
                        {
 | 
			
		||||
                            "name": "Period",
 | 
			
		||||
                            "control": "textfield",
 | 
			
		||||
                            "cssClass": "l-input-sm l-numeric",
 | 
			
		||||
                            "cssclass": "l-input-sm l-numeric",
 | 
			
		||||
                            "key": "period",
 | 
			
		||||
                            "required": true,
 | 
			
		||||
                            "property": [
 | 
			
		||||
 
 | 
			
		||||
@@ -63,7 +63,7 @@ define(
 | 
			
		||||
                 * Get the CSS class that defines the icon
 | 
			
		||||
                 * to display in this indicator. This will appear
 | 
			
		||||
                 * as a dataflow icon.
 | 
			
		||||
                 * @returns {string} the cssClass of the dataflow icon
 | 
			
		||||
                 * @returns {string} the cssclass of the dataflow icon
 | 
			
		||||
                 */
 | 
			
		||||
                getCssClass: function () {
 | 
			
		||||
                    return "icon-connectivity";
 | 
			
		||||
 
 | 
			
		||||
@@ -33,11 +33,6 @@ define([
 | 
			
		||||
    legacyRegistry.register("example/scratchpad", {
 | 
			
		||||
        "extensions": {
 | 
			
		||||
            "roots": [
 | 
			
		||||
                {
 | 
			
		||||
                    "id": "scratch:root"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "models": [
 | 
			
		||||
                {
 | 
			
		||||
                    "id": "scratch:root",
 | 
			
		||||
                    "model": {
 | 
			
		||||
 
 | 
			
		||||
@@ -35,11 +35,6 @@ define([
 | 
			
		||||
        "description": "Example illustrating the addition of a static top-level hierarchy",
 | 
			
		||||
        "extensions": {
 | 
			
		||||
            "roots": [
 | 
			
		||||
                {
 | 
			
		||||
                    "id": "exampleTaxonomy"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "models": [
 | 
			
		||||
                {
 | 
			
		||||
                    "id": "exampleTaxonomy",
 | 
			
		||||
                    "model": {
 | 
			
		||||
 
 | 
			
		||||
@@ -69,11 +69,6 @@ var gulp = require('gulp'),
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
if (process.env.NODE_ENV === 'development') {
 | 
			
		||||
    options.requirejsOptimize.optimize = 'none';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
gulp.task('scripts', function () {
 | 
			
		||||
    var requirejsOptimize = require('gulp-requirejs-optimize');
 | 
			
		||||
    var replace = require('gulp-replace-task');
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								index.html
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								index.html
									
									
									
									
									
								
							@@ -31,15 +31,13 @@
 | 
			
		||||
        require(['openmct'], function (openmct) {
 | 
			
		||||
            [
 | 
			
		||||
                'example/imagery',
 | 
			
		||||
                'example/eventGenerator'
 | 
			
		||||
                'example/eventGenerator',
 | 
			
		||||
                'example/generator',
 | 
			
		||||
                'platform/features/my-items',
 | 
			
		||||
                'platform/persistence/local'
 | 
			
		||||
            ].forEach(
 | 
			
		||||
                openmct.legacyRegistry.enable.bind(openmct.legacyRegistry)
 | 
			
		||||
            );
 | 
			
		||||
            openmct.install(openmct.plugins.MyItems());
 | 
			
		||||
            openmct.install(openmct.plugins.LocalStorage());
 | 
			
		||||
            openmct.install(openmct.plugins.Espresso());
 | 
			
		||||
            openmct.install(openmct.plugins.Generator());
 | 
			
		||||
            openmct.install(openmct.plugins.UTCTimeSystem());
 | 
			
		||||
            openmct.start();
 | 
			
		||||
        });
 | 
			
		||||
    </script>
 | 
			
		||||
 
 | 
			
		||||
@@ -76,7 +76,7 @@ module.exports = function(config) {
 | 
			
		||||
        // Specify browsers to run tests in.
 | 
			
		||||
        // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
 | 
			
		||||
        browsers: [
 | 
			
		||||
            'Chrome'
 | 
			
		||||
            'PhantomJS'
 | 
			
		||||
        ],
 | 
			
		||||
 | 
			
		||||
        // Code coverage reporting.
 | 
			
		||||
 
 | 
			
		||||
@@ -84,11 +84,5 @@ define([
 | 
			
		||||
        return new Main().run(defaultRegistry);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // For now, install conductor by default
 | 
			
		||||
    openmct.install(openmct.plugins.Conductor({
 | 
			
		||||
        showConductor: false
 | 
			
		||||
    }));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    return openmct;
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -25,12 +25,13 @@
 | 
			
		||||
    "jsdoc": "^3.3.2",
 | 
			
		||||
    "jshint": "^2.7.0",
 | 
			
		||||
    "karma": "^0.13.3",
 | 
			
		||||
    "karma-chrome-launcher": "^0.1.12",
 | 
			
		||||
    "karma-chrome-launcher": "^0.1.8",
 | 
			
		||||
    "karma-cli": "0.0.4",
 | 
			
		||||
    "karma-coverage": "^0.5.3",
 | 
			
		||||
    "karma-html-reporter": "^0.2.7",
 | 
			
		||||
    "karma-jasmine": "^0.1.5",
 | 
			
		||||
    "karma-junit-reporter": "^0.3.8",
 | 
			
		||||
    "karma-phantomjs-launcher": "^1.0.0",
 | 
			
		||||
    "karma-requirejs": "^0.2.2",
 | 
			
		||||
    "lodash": "^3.10.1",
 | 
			
		||||
    "markdown-toc": "^0.11.7",
 | 
			
		||||
@@ -39,6 +40,7 @@
 | 
			
		||||
    "mkdirp": "^0.5.1",
 | 
			
		||||
    "moment": "^2.11.1",
 | 
			
		||||
    "node-bourbon": "^4.2.3",
 | 
			
		||||
    "phantomjs-prebuilt": "2.1.11 || >2.1.12 <3.0.0",
 | 
			
		||||
    "requirejs": "2.1.x",
 | 
			
		||||
    "split": "^1.0.0"
 | 
			
		||||
  },
 | 
			
		||||
 
 | 
			
		||||
@@ -41,6 +41,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,
 | 
			
		||||
@@ -63,6 +64,7 @@ define([
 | 
			
		||||
    itemsTemplate,
 | 
			
		||||
    objectPropertiesTemplate,
 | 
			
		||||
    inspectorRegionTemplate,
 | 
			
		||||
    viewObjectTemplate,
 | 
			
		||||
    legacyRegistry
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
@@ -139,6 +141,10 @@ define([
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "representations": [
 | 
			
		||||
                {
 | 
			
		||||
                    "key": "view-object",
 | 
			
		||||
                    "template": viewObjectTemplate
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "key": "browse-object",
 | 
			
		||||
                    "template": browseObjectTemplate,
 | 
			
		||||
@@ -198,10 +204,7 @@ define([
 | 
			
		||||
            "services": [
 | 
			
		||||
                {
 | 
			
		||||
                    "key": "navigationService",
 | 
			
		||||
                    "implementation": NavigationService,
 | 
			
		||||
                    "depends": [
 | 
			
		||||
                        "$window"
 | 
			
		||||
                    ]
 | 
			
		||||
                    "implementation": NavigationService
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "actions": [
 | 
			
		||||
@@ -209,7 +212,10 @@ define([
 | 
			
		||||
                    "key": "navigate",
 | 
			
		||||
                    "implementation": NavigateAction,
 | 
			
		||||
                    "depends": [
 | 
			
		||||
                        "navigationService"
 | 
			
		||||
                        "navigationService",
 | 
			
		||||
                        "$q",
 | 
			
		||||
                        "policyService",
 | 
			
		||||
                        "$window"
 | 
			
		||||
                    ]
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
@@ -226,7 +232,7 @@ define([
 | 
			
		||||
                        "$window"
 | 
			
		||||
                    ],
 | 
			
		||||
                    "group": "windowing",
 | 
			
		||||
                    "cssClass": "icon-new-window",
 | 
			
		||||
                    "cssclass": "icon-new-window",
 | 
			
		||||
                    "priority": "preferred"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
@@ -241,7 +247,7 @@ define([
 | 
			
		||||
                {
 | 
			
		||||
                    "key": "items",
 | 
			
		||||
                    "name": "Items",
 | 
			
		||||
                    "cssClass": "icon-thumbs-strip",
 | 
			
		||||
                    "cssclass": "icon-thumbs-strip",
 | 
			
		||||
                    "description": "Grid of available items",
 | 
			
		||||
                    "template": itemsTemplate,
 | 
			
		||||
                    "uses": [
 | 
			
		||||
 
 | 
			
		||||
@@ -63,7 +63,7 @@
 | 
			
		||||
                    <mct-split-pane class='l-object-and-inspector contents abs' anchor='right'>
 | 
			
		||||
                        <div class='split-pane-component t-object pane primary-pane left'>
 | 
			
		||||
                            <mct-representation mct-object="navigatedObject"
 | 
			
		||||
                                                key="navigatedObject.getCapability('status').get('editing') ? 'edit-object' : 'browse-object'"
 | 
			
		||||
                                                key="'view-object'"
 | 
			
		||||
                                                class="abs holder holder-object">
 | 
			
		||||
                            </mct-representation>
 | 
			
		||||
                            <a class="mini-tab-icon anchor-right mobile-hide toggle-pane toggle-inspect flush-right"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										33
									
								
								platform/commonUI/browse/res/templates/view-object.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								platform/commonUI/browse/res/templates/view-object.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
<!--
 | 
			
		||||
 Open MCT, Copyright (c) 2014-2016, United States Government
 | 
			
		||||
 as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 Administration. All rights reserved.
 | 
			
		||||
 | 
			
		||||
 Open MCT 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 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.
 | 
			
		||||
-->
 | 
			
		||||
<!--
 | 
			
		||||
 A representation that allows the 'View' region of an object view to change
 | 
			
		||||
 dynamically (eg. between browse and edit modes). Values correspond to a
 | 
			
		||||
 representation key, and currently defaults to 'browse-object'.
 | 
			
		||||
 | 
			
		||||
 In the case of edit, the EditRepresenter will change this to editable
 | 
			
		||||
 representation of the object as needed.
 | 
			
		||||
 -->
 | 
			
		||||
<mct-representation mct-object="domainObject"
 | 
			
		||||
                    key="viewObjectTemplate || 'browse-object'"
 | 
			
		||||
                    class="abs holder">
 | 
			
		||||
</mct-representation>
 | 
			
		||||
@@ -48,16 +48,11 @@ define(
 | 
			
		||||
            defaultPath
 | 
			
		||||
        ) {
 | 
			
		||||
            var initialPath = ($route.current.params.ids || defaultPath).split("/");
 | 
			
		||||
            var currentIds;
 | 
			
		||||
 | 
			
		||||
            var currentIds = $route.current.params.ids;
 | 
			
		||||
 | 
			
		||||
            $scope.treeModel = {
 | 
			
		||||
                selectedObject: undefined,
 | 
			
		||||
                onSelection: function (object) {
 | 
			
		||||
                    navigationService.setNavigation(object, true);
 | 
			
		||||
                },
 | 
			
		||||
                allowSelection: function (object) {
 | 
			
		||||
                    return navigationService.shouldNavigate();
 | 
			
		||||
                }
 | 
			
		||||
                selectedObject: undefined
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            function idsForObject(domainObject) {
 | 
			
		||||
@@ -108,6 +103,7 @@ define(
 | 
			
		||||
            function navigateToObject(desiredObject) {
 | 
			
		||||
                $scope.navigatedObject = desiredObject;
 | 
			
		||||
                $scope.treeModel.selectedObject = desiredObject;
 | 
			
		||||
                navigationService.setNavigation(desiredObject);
 | 
			
		||||
                currentIds = idsForObject(desiredObject);
 | 
			
		||||
                $route.current.pathParams.ids = currentIds;
 | 
			
		||||
                $location.path('/browse/' + currentIds);
 | 
			
		||||
@@ -118,11 +114,10 @@ define(
 | 
			
		||||
                    .then(function (root) {
 | 
			
		||||
                        return findViaComposition(root, path);
 | 
			
		||||
                    })
 | 
			
		||||
                    .then(function (object) {
 | 
			
		||||
                        navigationService.setNavigation(object);
 | 
			
		||||
                    });
 | 
			
		||||
                    .then(navigateToObject);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            getObject('ROOT')
 | 
			
		||||
                .then(function (root) {
 | 
			
		||||
                    $scope.domainObject = root;
 | 
			
		||||
@@ -142,6 +137,15 @@ define(
 | 
			
		||||
            // Listen for changes in navigation state.
 | 
			
		||||
            navigationService.addListener(navigateDirectlyToModel);
 | 
			
		||||
 | 
			
		||||
            // Also listen for changes which come from the tree. Changes in
 | 
			
		||||
            // the tree will trigger a change in browse navigation state.
 | 
			
		||||
            $scope.$watch("treeModel.selectedObject", function (newObject, oldObject) {
 | 
			
		||||
                if (oldObject !== newObject) {
 | 
			
		||||
                    navigateDirectlyToModel(newObject);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            // Listen for route changes which are caused by browser events
 | 
			
		||||
            // (e.g. bookmarks to pages in OpenMCT) and prevent them.  Instead,
 | 
			
		||||
            // navigate to the path ourselves, which results in it being
 | 
			
		||||
 
 | 
			
		||||
@@ -33,9 +33,12 @@ define(
 | 
			
		||||
         * @constructor
 | 
			
		||||
         * @implements {Action}
 | 
			
		||||
         */
 | 
			
		||||
        function NavigateAction(navigationService, context) {
 | 
			
		||||
        function NavigateAction(navigationService, $q, policyService, $window, context) {
 | 
			
		||||
            this.domainObject = context.domainObject;
 | 
			
		||||
            this.$q = $q;
 | 
			
		||||
            this.navigationService = navigationService;
 | 
			
		||||
            this.policyService = policyService;
 | 
			
		||||
            this.$window = $window;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
@@ -44,12 +47,36 @@ define(
 | 
			
		||||
         *          navigation has been updated
 | 
			
		||||
         */
 | 
			
		||||
        NavigateAction.prototype.perform = function () {
 | 
			
		||||
            if (this.navigationService.shouldNavigate()) {
 | 
			
		||||
                this.navigationService.setNavigation(this.domainObject, true);
 | 
			
		||||
                return Promise.resolve({});
 | 
			
		||||
            var self = this,
 | 
			
		||||
                navigateTo = this.domainObject,
 | 
			
		||||
                currentObject = self.navigationService.getNavigation();
 | 
			
		||||
 | 
			
		||||
            function allow() {
 | 
			
		||||
                var navigationAllowed = true;
 | 
			
		||||
                self.policyService.allow("navigation", currentObject, navigateTo, function (message) {
 | 
			
		||||
                    navigationAllowed = self.$window.confirm(message + "\r\n\r\n" +
 | 
			
		||||
                        " Are you sure you want to continue?");
 | 
			
		||||
                });
 | 
			
		||||
                return navigationAllowed;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function cancelIfEditing() {
 | 
			
		||||
                var editing = currentObject.hasCapability('editor') &&
 | 
			
		||||
                    currentObject.getCapability('editor').isEditContextRoot();
 | 
			
		||||
 | 
			
		||||
                return self.$q.when(editing && currentObject.getCapability("editor").finish());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function navigate() {
 | 
			
		||||
                return self.navigationService.setNavigation(navigateTo);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (allow()) {
 | 
			
		||||
                return cancelIfEditing().then(navigate);
 | 
			
		||||
            } else {
 | 
			
		||||
                return this.$q.when(false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Promise.reject('Navigation Prevented by User');
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
 
 | 
			
		||||
@@ -30,23 +30,16 @@ define(
 | 
			
		||||
        /**
 | 
			
		||||
         * The navigation service maintains the application's current
 | 
			
		||||
         * navigation state, and allows listening for changes thereto.
 | 
			
		||||
         *
 | 
			
		||||
         * @memberof platform/commonUI/browse
 | 
			
		||||
         * @constructor
 | 
			
		||||
         */
 | 
			
		||||
        function NavigationService($window) {
 | 
			
		||||
        function NavigationService() {
 | 
			
		||||
            this.navigated = undefined;
 | 
			
		||||
            this.callbacks = [];
 | 
			
		||||
            this.checks = [];
 | 
			
		||||
            this.$window = $window;
 | 
			
		||||
 | 
			
		||||
            this.oldUnload = $window.onbeforeunload;
 | 
			
		||||
            $window.onbeforeunload = this.onBeforeUnload.bind(this);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Get the current navigation state.
 | 
			
		||||
         *
 | 
			
		||||
         * @returns {DomainObject} the object that is navigated-to
 | 
			
		||||
         */
 | 
			
		||||
        NavigationService.prototype.getNavigation = function () {
 | 
			
		||||
@@ -54,33 +47,16 @@ define(
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Navigate to a specified object.  If navigation checks exist and
 | 
			
		||||
         * return reasons to prevent navigation, it will prompt the user before
 | 
			
		||||
         * continuing. Trying to navigate to the currently navigated object will
 | 
			
		||||
         * do nothing.
 | 
			
		||||
         *
 | 
			
		||||
         * If a truthy value is passed for `force`, it will skip navigation
 | 
			
		||||
         * and will not prevent navigation to an already selected object.
 | 
			
		||||
         *
 | 
			
		||||
         * Set the current navigation state. This will invoke listeners.
 | 
			
		||||
         * @param {DomainObject} domainObject the domain object to navigate to
 | 
			
		||||
         * @param {Boolean} force if true, force navigation to occur.
 | 
			
		||||
         * @returns {Boolean} true if navigation occured, otherwise false.
 | 
			
		||||
         */
 | 
			
		||||
        NavigationService.prototype.setNavigation = function (domainObject, force) {
 | 
			
		||||
            if (force) {
 | 
			
		||||
                this.doNavigation(domainObject);
 | 
			
		||||
                return true;
 | 
			
		||||
        NavigationService.prototype.setNavigation = function (value) {
 | 
			
		||||
            if (this.navigated !== value) {
 | 
			
		||||
                this.navigated = value;
 | 
			
		||||
                this.callbacks.forEach(function (callback) {
 | 
			
		||||
                    callback(value);
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            if (this.navigated === domainObject) {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var doNotNavigate = this.shouldWarnBeforeNavigate();
 | 
			
		||||
            if (doNotNavigate && !this.$window.confirm(doNotNavigate)) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.doNavigation(domainObject);
 | 
			
		||||
            return true;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
@@ -88,7 +64,6 @@ define(
 | 
			
		||||
         * Listen for changes in navigation. The passed callback will
 | 
			
		||||
         * be invoked with the new domain object of navigation when
 | 
			
		||||
         * this changes.
 | 
			
		||||
         *
 | 
			
		||||
         * @param {function} callback the callback to invoke when
 | 
			
		||||
         *        navigation state changes
 | 
			
		||||
         */
 | 
			
		||||
@@ -98,7 +73,6 @@ define(
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Stop listening for changes in navigation state.
 | 
			
		||||
         *
 | 
			
		||||
         * @param {function} callback the callback which should
 | 
			
		||||
         *        no longer be invoked when navigation state
 | 
			
		||||
         *        changes
 | 
			
		||||
@@ -109,89 +83,6 @@ define(
 | 
			
		||||
            });
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Check if navigation should proceed.  May prompt a user for input
 | 
			
		||||
         * if any checkFns return messages.  Returns true if the user wishes to
 | 
			
		||||
         * navigate, otherwise false.  If using this prior to calling
 | 
			
		||||
         * `setNavigation`, you should call `setNavigation` with `force=true`
 | 
			
		||||
         * to prevent duplicate dialogs being displayed to the user.
 | 
			
		||||
         *
 | 
			
		||||
         * @returns {Boolean} true if the user wishes to navigate, otherwise false.
 | 
			
		||||
         */
 | 
			
		||||
        NavigationService.prototype.shouldNavigate = function () {
 | 
			
		||||
            var doNotNavigate = this.shouldWarnBeforeNavigate();
 | 
			
		||||
            return !doNotNavigate || this.$window.confirm(doNotNavigate);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Register a check function to be called before any navigation occurs.
 | 
			
		||||
         * Check functions should return a human readable "message" if
 | 
			
		||||
         * there are any reasons to prevent navigation.  Otherwise, they should
 | 
			
		||||
         * return falsy.  Returns a function which can be called to remove the
 | 
			
		||||
         * check function.
 | 
			
		||||
         *
 | 
			
		||||
         * @param {Function} checkFn a function to call before navigation occurs.
 | 
			
		||||
         * @returns {Function} removeCheck call to remove check
 | 
			
		||||
         */
 | 
			
		||||
        NavigationService.prototype.checkBeforeNavigation = function (checkFn) {
 | 
			
		||||
            this.checks.push(checkFn);
 | 
			
		||||
            return function removeCheck() {
 | 
			
		||||
                this.checks = this.checks.filter(function (fn) {
 | 
			
		||||
                    return checkFn !== fn;
 | 
			
		||||
                });
 | 
			
		||||
            }.bind(this);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Private method to actually perform navigation.
 | 
			
		||||
         *
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        NavigationService.prototype.doNavigation = function (value) {
 | 
			
		||||
            this.navigated = value;
 | 
			
		||||
            this.callbacks.forEach(function (callback) {
 | 
			
		||||
                callback(value);
 | 
			
		||||
            });
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Returns either a false value, or a string that should be displayed
 | 
			
		||||
         * to the user before navigation is allowed.
 | 
			
		||||
         *
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        NavigationService.prototype.shouldWarnBeforeNavigate = function () {
 | 
			
		||||
            var reasons = [];
 | 
			
		||||
 | 
			
		||||
            this.checks.forEach(function (checkFn) {
 | 
			
		||||
                var reason = checkFn();
 | 
			
		||||
                if (reason) {
 | 
			
		||||
                    reasons.push(reason);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (reasons.length) {
 | 
			
		||||
                return reasons.join('\n');
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Listener for window on before unload event-- will warn before
 | 
			
		||||
         * navigation is allowed.
 | 
			
		||||
         *
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        NavigationService.prototype.onBeforeUnload = function () {
 | 
			
		||||
            var shouldWarnBeforeNavigate = this.shouldWarnBeforeNavigate();
 | 
			
		||||
            if (shouldWarnBeforeNavigate) {
 | 
			
		||||
                return shouldWarnBeforeNavigate;
 | 
			
		||||
            }
 | 
			
		||||
            if (this.oldUnload) {
 | 
			
		||||
                return this.oldUnload.apply(undefined, [].slice.apply(arguments));
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return NavigationService;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -43,24 +43,24 @@ define([], function () {
 | 
			
		||||
            return context.getParent();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function preventOrphanNavigation(domainObject) {
 | 
			
		||||
        function isOrphan(domainObject) {
 | 
			
		||||
            var parent = getParent(domainObject),
 | 
			
		||||
                composition = parent.getModel().composition,
 | 
			
		||||
                id = domainObject.getId();
 | 
			
		||||
            return !composition || (composition.indexOf(id) === -1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function navigateToParent(domainObject) {
 | 
			
		||||
            var parent = getParent(domainObject);
 | 
			
		||||
            parent.useCapability('composition')
 | 
			
		||||
                .then(function (composees) {
 | 
			
		||||
                    var isOrphan = composees.every(function (c) {
 | 
			
		||||
                        return c.getId() !== domainObject.getId();
 | 
			
		||||
                    });
 | 
			
		||||
                    if (isOrphan) {
 | 
			
		||||
                        parent.getCapability('action').perform('navigate');
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            return parent.getCapability('action').perform('navigate');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function checkNavigation() {
 | 
			
		||||
            var navigatedObject = navigationService.getNavigation();
 | 
			
		||||
            if (navigatedObject.hasCapability('context')) {
 | 
			
		||||
            if (navigatedObject.hasCapability('context') &&
 | 
			
		||||
                isOrphan(navigatedObject)) {
 | 
			
		||||
                if (!navigatedObject.getCapability('editor').isEditContextRoot()) {
 | 
			
		||||
                    preventOrphanNavigation(navigatedObject);
 | 
			
		||||
                    navigateToParent(navigatedObject);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -46,12 +46,12 @@ define(
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        FullscreenAction.prototype.getMetadata = function () {
 | 
			
		||||
            // We override getMetadata, because the icon cssClass and
 | 
			
		||||
            // We override getMetadata, because the icon cssclass and
 | 
			
		||||
            // description need to be determined at run-time
 | 
			
		||||
            // based on whether or not we are currently
 | 
			
		||||
            // full screen.
 | 
			
		||||
            var metadata = Object.create(FullscreenAction);
 | 
			
		||||
            metadata.cssClass = screenfull.isFullscreen ? "icon-fullscreen-expand" : "icon-fullscreen-collapse";
 | 
			
		||||
            metadata.cssclass = screenfull.isFullscreen ? "icon-fullscreen-expand" : "icon-fullscreen-collapse";
 | 
			
		||||
            metadata.description = screenfull.isFullscreen ?
 | 
			
		||||
                EXIT_FULLSCREEN : ENTER_FULLSCREEN;
 | 
			
		||||
            metadata.group = "windowing";
 | 
			
		||||
 
 | 
			
		||||
@@ -24,14 +24,8 @@
 | 
			
		||||
 * MCTRepresentationSpec. Created by vwoeltje on 11/6/14.
 | 
			
		||||
 */
 | 
			
		||||
define(
 | 
			
		||||
    [
 | 
			
		||||
        "../src/BrowseController",
 | 
			
		||||
        "../src/navigation/NavigationService"
 | 
			
		||||
    ],
 | 
			
		||||
    function (
 | 
			
		||||
        BrowseController,
 | 
			
		||||
        NavigationService
 | 
			
		||||
    ) {
 | 
			
		||||
    ["../src/BrowseController"],
 | 
			
		||||
    function (BrowseController) {
 | 
			
		||||
 | 
			
		||||
        describe("The browse controller", function () {
 | 
			
		||||
            var mockScope,
 | 
			
		||||
@@ -50,7 +44,7 @@ define(
 | 
			
		||||
            function waitsForNavigation() {
 | 
			
		||||
                var calls = mockNavigationService.setNavigation.calls.length;
 | 
			
		||||
                waitsFor(function () {
 | 
			
		||||
                    return mockNavigationService.setNavigation.calls.length > calls;
 | 
			
		||||
                    return mockNavigationService.setNavigation.calls.length > calls ;
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -98,16 +92,15 @@ define(
 | 
			
		||||
                    "objectService",
 | 
			
		||||
                    ["getObjects"]
 | 
			
		||||
                );
 | 
			
		||||
                mockNavigationService = new NavigationService({});
 | 
			
		||||
                [
 | 
			
		||||
                    "getNavigation",
 | 
			
		||||
                    "setNavigation",
 | 
			
		||||
                    "addListener",
 | 
			
		||||
                    "removeListener"
 | 
			
		||||
                ].forEach(function (method) {
 | 
			
		||||
                    spyOn(mockNavigationService, method)
 | 
			
		||||
                        .andCallThrough();
 | 
			
		||||
                });
 | 
			
		||||
                mockNavigationService = jasmine.createSpyObj(
 | 
			
		||||
                    "navigationService",
 | 
			
		||||
                    [
 | 
			
		||||
                        "getNavigation",
 | 
			
		||||
                        "setNavigation",
 | 
			
		||||
                        "addListener",
 | 
			
		||||
                        "removeListener"
 | 
			
		||||
                    ]
 | 
			
		||||
                );
 | 
			
		||||
                mockRootObject = jasmine.createSpyObj(
 | 
			
		||||
                    "rootObjectContainer",
 | 
			
		||||
                    ["getId", "getCapability", "getModel", "useCapability", "hasCapability"]
 | 
			
		||||
 
 | 
			
		||||
@@ -23,74 +23,145 @@
 | 
			
		||||
/**
 | 
			
		||||
 * MCTRepresentationSpec. Created by vwoeltje on 11/6/14.
 | 
			
		||||
 */
 | 
			
		||||
define([
 | 
			
		||||
    "../../src/navigation/NavigateAction"
 | 
			
		||||
], function (
 | 
			
		||||
    NavigateAction
 | 
			
		||||
) {
 | 
			
		||||
define(
 | 
			
		||||
    ["../../src/navigation/NavigateAction"],
 | 
			
		||||
    function (NavigateAction) {
 | 
			
		||||
 | 
			
		||||
    describe("The navigate action", function () {
 | 
			
		||||
        var mockNavigationService,
 | 
			
		||||
            mockDomainObject,
 | 
			
		||||
            action;
 | 
			
		||||
        describe("The navigate action", function () {
 | 
			
		||||
            var mockNavigationService,
 | 
			
		||||
                mockQ,
 | 
			
		||||
                mockDomainObject,
 | 
			
		||||
                mockPolicyService,
 | 
			
		||||
                mockNavigatedObject,
 | 
			
		||||
                mockWindow,
 | 
			
		||||
                capabilities,
 | 
			
		||||
                action;
 | 
			
		||||
 | 
			
		||||
            function mockPromise(value) {
 | 
			
		||||
                return {
 | 
			
		||||
                    then: function (callback) {
 | 
			
		||||
                        return mockPromise(callback(value));
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        function waitForCall() {
 | 
			
		||||
            var called = false;
 | 
			
		||||
            waitsFor(function () {
 | 
			
		||||
                return called;
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                capabilities = {};
 | 
			
		||||
 | 
			
		||||
                mockQ = { when: mockPromise };
 | 
			
		||||
                mockNavigatedObject = jasmine.createSpyObj(
 | 
			
		||||
                    "domainObject",
 | 
			
		||||
                    [
 | 
			
		||||
                        "getId",
 | 
			
		||||
                        "getModel",
 | 
			
		||||
                        "hasCapability",
 | 
			
		||||
                        "getCapability"
 | 
			
		||||
                    ]
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                capabilities.editor = jasmine.createSpyObj("editorCapability", [
 | 
			
		||||
                    "isEditContextRoot",
 | 
			
		||||
                    "finish"
 | 
			
		||||
                ]);
 | 
			
		||||
 | 
			
		||||
                mockNavigatedObject.getCapability.andCallFake(function (capability) {
 | 
			
		||||
                    return capabilities[capability];
 | 
			
		||||
                });
 | 
			
		||||
                mockNavigatedObject.hasCapability.andReturn(false);
 | 
			
		||||
 | 
			
		||||
                mockNavigationService = jasmine.createSpyObj(
 | 
			
		||||
                    "navigationService",
 | 
			
		||||
                    [
 | 
			
		||||
                        "setNavigation",
 | 
			
		||||
                        "getNavigation"
 | 
			
		||||
                    ]
 | 
			
		||||
                );
 | 
			
		||||
                mockNavigationService.getNavigation.andReturn(mockNavigatedObject);
 | 
			
		||||
 | 
			
		||||
                mockDomainObject = jasmine.createSpyObj(
 | 
			
		||||
                    "domainObject",
 | 
			
		||||
                    [
 | 
			
		||||
                        "getId",
 | 
			
		||||
                        "getModel"
 | 
			
		||||
                    ]
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                mockPolicyService = jasmine.createSpyObj("policyService",
 | 
			
		||||
                    [
 | 
			
		||||
                        "allow"
 | 
			
		||||
                    ]);
 | 
			
		||||
                mockWindow = jasmine.createSpyObj("$window",
 | 
			
		||||
                    [
 | 
			
		||||
                        "confirm"
 | 
			
		||||
                    ]);
 | 
			
		||||
 | 
			
		||||
                action = new NavigateAction(
 | 
			
		||||
                    mockNavigationService,
 | 
			
		||||
                    mockQ,
 | 
			
		||||
                    mockPolicyService,
 | 
			
		||||
                    mockWindow,
 | 
			
		||||
                    { domainObject: mockDomainObject }
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
            return function () {
 | 
			
		||||
                called = true;
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        beforeEach(function () {
 | 
			
		||||
            mockNavigationService = jasmine.createSpyObj(
 | 
			
		||||
                "navigationService",
 | 
			
		||||
                [
 | 
			
		||||
                    "shouldNavigate",
 | 
			
		||||
                    "setNavigation"
 | 
			
		||||
                ]
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            mockDomainObject = {};
 | 
			
		||||
 | 
			
		||||
            action = new NavigateAction(
 | 
			
		||||
                mockNavigationService,
 | 
			
		||||
                { domainObject: mockDomainObject }
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("sets navigation if it is allowed", function () {
 | 
			
		||||
            mockNavigationService.shouldNavigate.andReturn(true);
 | 
			
		||||
            action.perform()
 | 
			
		||||
                .then(waitForCall());
 | 
			
		||||
            runs(function () {
 | 
			
		||||
                expect(mockNavigationService.setNavigation)
 | 
			
		||||
                    .toHaveBeenCalledWith(mockDomainObject, true);
 | 
			
		||||
            it("invokes the policy service to determine if navigation" +
 | 
			
		||||
                " allowed", function () {
 | 
			
		||||
                action.perform();
 | 
			
		||||
                expect(mockPolicyService.allow)
 | 
			
		||||
                    .toHaveBeenCalledWith("navigation", jasmine.any(Object), jasmine.any(Object), jasmine.any(Function));
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("does not set navigation if it is not allowed", function () {
 | 
			
		||||
            mockNavigationService.shouldNavigate.andReturn(false);
 | 
			
		||||
            var onSuccess = jasmine.createSpy('onSuccess');
 | 
			
		||||
            action.perform()
 | 
			
		||||
                .then(onSuccess, waitForCall());
 | 
			
		||||
            runs(function () {
 | 
			
		||||
                expect(onSuccess).not.toHaveBeenCalled();
 | 
			
		||||
                expect(mockNavigationService.setNavigation)
 | 
			
		||||
                    .not
 | 
			
		||||
                    .toHaveBeenCalledWith(mockDomainObject);
 | 
			
		||||
            it("prompts user if policy rejection", function () {
 | 
			
		||||
                action.perform();
 | 
			
		||||
                expect(mockPolicyService.allow).toHaveBeenCalled();
 | 
			
		||||
                mockPolicyService.allow.mostRecentCall.args[3]();
 | 
			
		||||
                expect(mockWindow.confirm).toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("is only applicable when a domain object is in context", function () {
 | 
			
		||||
            expect(NavigateAction.appliesTo({})).toBeFalsy();
 | 
			
		||||
            expect(NavigateAction.appliesTo({
 | 
			
		||||
                domainObject: mockDomainObject
 | 
			
		||||
            })).toBeTruthy();
 | 
			
		||||
        });
 | 
			
		||||
            describe("shows a prompt", function () {
 | 
			
		||||
                beforeEach(function () {
 | 
			
		||||
                    // Ensure the allow callback is called synchronously
 | 
			
		||||
                    mockPolicyService.allow.andCallFake(function () {
 | 
			
		||||
                        return arguments[3]();
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
                it("does not navigate on prompt rejection", function () {
 | 
			
		||||
                    mockWindow.confirm.andReturn(false);
 | 
			
		||||
                    action.perform();
 | 
			
		||||
                    expect(mockNavigationService.setNavigation)
 | 
			
		||||
                        .not.toHaveBeenCalled();
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
                it("does navigate on prompt acceptance", function () {
 | 
			
		||||
                    mockWindow.confirm.andReturn(true);
 | 
			
		||||
                    action.perform();
 | 
			
		||||
                    expect(mockNavigationService.setNavigation)
 | 
			
		||||
                        .toHaveBeenCalled();
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("in edit mode", function () {
 | 
			
		||||
                beforeEach(function () {
 | 
			
		||||
                    mockNavigatedObject.hasCapability.andCallFake(function (capability) {
 | 
			
		||||
                        return capability === "editor";
 | 
			
		||||
                    });
 | 
			
		||||
                    capabilities.editor.isEditContextRoot.andReturn(true);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("finishes editing if in edit mode", function () {
 | 
			
		||||
                    action.perform();
 | 
			
		||||
                    expect(capabilities.editor.finish)
 | 
			
		||||
                        .toHaveBeenCalled();
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("is only applicable when a domain object is in context", function () {
 | 
			
		||||
                expect(NavigateAction.appliesTo({})).toBeFalsy();
 | 
			
		||||
                expect(NavigateAction.appliesTo({
 | 
			
		||||
                    domainObject: mockDomainObject
 | 
			
		||||
                })).toBeTruthy();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -28,12 +28,10 @@ define(
 | 
			
		||||
    function (NavigationService) {
 | 
			
		||||
 | 
			
		||||
        describe("The navigation service", function () {
 | 
			
		||||
            var $window,
 | 
			
		||||
                navigationService;
 | 
			
		||||
            var navigationService;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                $window = jasmine.createSpyObj('$window', ['confirm']);
 | 
			
		||||
                navigationService = new NavigationService($window);
 | 
			
		||||
                navigationService = new NavigationService();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("stores navigation state", function () {
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@ define([
 | 
			
		||||
            mockContext,
 | 
			
		||||
            mockActionCapability,
 | 
			
		||||
            mockEditor,
 | 
			
		||||
            testParentComposition,
 | 
			
		||||
            testParentModel,
 | 
			
		||||
            testId,
 | 
			
		||||
            mockThrottledFns;
 | 
			
		||||
 | 
			
		||||
@@ -41,6 +41,7 @@ define([
 | 
			
		||||
            testId = 'some-identifier';
 | 
			
		||||
 | 
			
		||||
            mockThrottledFns = [];
 | 
			
		||||
            testParentModel = {};
 | 
			
		||||
 | 
			
		||||
            mockTopic = jasmine.createSpy('topic');
 | 
			
		||||
            mockThrottle = jasmine.createSpy('throttle');
 | 
			
		||||
@@ -54,12 +55,14 @@ define([
 | 
			
		||||
            mockDomainObject = jasmine.createSpyObj('domainObject', [
 | 
			
		||||
                'getId',
 | 
			
		||||
                'getCapability',
 | 
			
		||||
                'getModel',
 | 
			
		||||
                'hasCapability'
 | 
			
		||||
            ]);
 | 
			
		||||
            mockParentObject = jasmine.createSpyObj('domainObject', [
 | 
			
		||||
                'getId',
 | 
			
		||||
                'getCapability',
 | 
			
		||||
                'useCapability'
 | 
			
		||||
                'getModel',
 | 
			
		||||
                'hasCapability'
 | 
			
		||||
            ]);
 | 
			
		||||
            mockContext = jasmine.createSpyObj('context', ['getParent']);
 | 
			
		||||
            mockActionCapability = jasmine.createSpyObj('action', ['perform']);
 | 
			
		||||
@@ -72,7 +75,9 @@ define([
 | 
			
		||||
                mockThrottledFns.push(mockThrottledFn);
 | 
			
		||||
                return mockThrottledFn;
 | 
			
		||||
            });
 | 
			
		||||
            mockTopic.andReturn(mockMutationTopic);
 | 
			
		||||
            mockTopic.andCallFake(function (k) {
 | 
			
		||||
                return k === 'mutation' && mockMutationTopic;
 | 
			
		||||
            });
 | 
			
		||||
            mockDomainObject.getId.andReturn(testId);
 | 
			
		||||
            mockDomainObject.getCapability.andCallFake(function (c) {
 | 
			
		||||
                return {
 | 
			
		||||
@@ -83,13 +88,12 @@ define([
 | 
			
		||||
            mockDomainObject.hasCapability.andCallFake(function (c) {
 | 
			
		||||
                return !!mockDomainObject.getCapability(c);
 | 
			
		||||
            });
 | 
			
		||||
            mockParentObject.getModel.andReturn(testParentModel);
 | 
			
		||||
            mockParentObject.getCapability.andCallFake(function (c) {
 | 
			
		||||
                return {
 | 
			
		||||
                    action: mockActionCapability
 | 
			
		||||
                }[c];
 | 
			
		||||
            });
 | 
			
		||||
            testParentComposition = [];
 | 
			
		||||
            mockParentObject.useCapability.andReturn(Promise.resolve(testParentComposition));
 | 
			
		||||
            mockContext.getParent.andReturn(mockParentObject);
 | 
			
		||||
            mockNavigationService.getNavigation.andReturn(mockDomainObject);
 | 
			
		||||
            mockEditor.isEditContextRoot.andReturn(false);
 | 
			
		||||
@@ -122,9 +126,7 @@ define([
 | 
			
		||||
            var prefix = isOrphan ? "" : "non-";
 | 
			
		||||
            describe("for " + prefix + "orphan objects", function () {
 | 
			
		||||
                beforeEach(function () {
 | 
			
		||||
                    if (!isOrphan) {
 | 
			
		||||
                        testParentComposition.push(mockDomainObject);
 | 
			
		||||
                    }
 | 
			
		||||
                    testParentModel.composition = isOrphan ? [] : [testId];
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                [false, true].forEach(function (isEditRoot) {
 | 
			
		||||
@@ -134,31 +136,13 @@ define([
 | 
			
		||||
                    function itNavigatesAsExpected() {
 | 
			
		||||
                        if (isOrphan && !isEditRoot) {
 | 
			
		||||
                            it("navigates to the parent", function () {
 | 
			
		||||
                                var done = false;
 | 
			
		||||
                                waitsFor(function () {
 | 
			
		||||
                                    return done;
 | 
			
		||||
                                });
 | 
			
		||||
                                setTimeout(function () {
 | 
			
		||||
                                    done = true;
 | 
			
		||||
                                }, 5);
 | 
			
		||||
                                runs(function () {
 | 
			
		||||
                                    expect(mockActionCapability.perform)
 | 
			
		||||
                                        .toHaveBeenCalledWith('navigate');
 | 
			
		||||
                                });
 | 
			
		||||
                                expect(mockActionCapability.perform)
 | 
			
		||||
                                    .toHaveBeenCalledWith('navigate');
 | 
			
		||||
                            });
 | 
			
		||||
                        } else {
 | 
			
		||||
                            it("does nothing", function () {
 | 
			
		||||
                                var done = false;
 | 
			
		||||
                                waitsFor(function () {
 | 
			
		||||
                                    return done;
 | 
			
		||||
                                });
 | 
			
		||||
                                setTimeout(function () {
 | 
			
		||||
                                    done = true;
 | 
			
		||||
                                }, 5);
 | 
			
		||||
                                runs(function () {
 | 
			
		||||
                                    expect(mockActionCapability.perform)
 | 
			
		||||
                                        .not.toHaveBeenCalled();
 | 
			
		||||
                                });
 | 
			
		||||
                                expect(mockActionCapability.perform)
 | 
			
		||||
                                    .not.toHaveBeenCalled();
 | 
			
		||||
                            });
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
@@ -173,6 +157,7 @@ define([
 | 
			
		||||
                                mockNavigationService.addListener.mostRecentCall
 | 
			
		||||
                                    .args[0](mockDomainObject);
 | 
			
		||||
                            });
 | 
			
		||||
 | 
			
		||||
                            itNavigatesAsExpected();
 | 
			
		||||
                        });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -51,7 +51,7 @@ define(
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("provides displayable metadata", function () {
 | 
			
		||||
                expect(action.getMetadata().cssClass).toBeDefined();
 | 
			
		||||
                expect(action.getMetadata().cssclass).toBeDefined();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
 
 | 
			
		||||
@@ -53,8 +53,7 @@ define([
 | 
			
		||||
                    "depends": [
 | 
			
		||||
                        "overlayService",
 | 
			
		||||
                        "$q",
 | 
			
		||||
                        "$log",
 | 
			
		||||
                        "$document"
 | 
			
		||||
                        "$log"
 | 
			
		||||
                    ]
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
 
 | 
			
		||||
@@ -35,15 +35,11 @@ define(
 | 
			
		||||
         * @memberof platform/commonUI/dialog
 | 
			
		||||
         * @constructor
 | 
			
		||||
         */
 | 
			
		||||
        function DialogService(overlayService, $q, $log, $document) {
 | 
			
		||||
        function DialogService(overlayService, $q, $log) {
 | 
			
		||||
            this.overlayService = overlayService;
 | 
			
		||||
            this.$q = $q;
 | 
			
		||||
            this.$log = $log;
 | 
			
		||||
            this.activeOverlay = undefined;
 | 
			
		||||
 | 
			
		||||
            this.findBody = function () {
 | 
			
		||||
                return $document.find('body');
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
@@ -64,8 +60,7 @@ define(
 | 
			
		||||
            // input is asynchronous.
 | 
			
		||||
            var deferred = this.$q.defer(),
 | 
			
		||||
                self = this,
 | 
			
		||||
                overlay,
 | 
			
		||||
                handleEscKeydown;
 | 
			
		||||
                overlay;
 | 
			
		||||
 | 
			
		||||
            // Confirm function; this will be passed in to the
 | 
			
		||||
            // overlay-dialog template and associated with a
 | 
			
		||||
@@ -81,22 +76,13 @@ define(
 | 
			
		||||
            // Cancel or X button click
 | 
			
		||||
            function cancel() {
 | 
			
		||||
                deferred.reject();
 | 
			
		||||
                self.findBody().off('keydown', handleEscKeydown);
 | 
			
		||||
                self.dismissOverlay(overlay);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            handleEscKeydown = function (event) {
 | 
			
		||||
                if (event.keyCode === 27) {
 | 
			
		||||
                    cancel();
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            // Add confirm/cancel callbacks
 | 
			
		||||
            model.confirm = confirm;
 | 
			
		||||
            model.cancel = cancel;
 | 
			
		||||
 | 
			
		||||
            this.findBody().on('keydown', handleEscKeydown);
 | 
			
		||||
 | 
			
		||||
            if (this.canShowDialog(model)) {
 | 
			
		||||
                // Add the overlay using the OverlayService, which
 | 
			
		||||
                // will handle actual insertion into the DOM
 | 
			
		||||
 
 | 
			
		||||
@@ -33,8 +33,6 @@ define(
 | 
			
		||||
                mockLog,
 | 
			
		||||
                mockOverlay,
 | 
			
		||||
                mockDeferred,
 | 
			
		||||
                mockDocument,
 | 
			
		||||
                mockBody,
 | 
			
		||||
                dialogService;
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
@@ -58,13 +56,6 @@ define(
 | 
			
		||||
                    "deferred",
 | 
			
		||||
                    ["resolve", "reject"]
 | 
			
		||||
                );
 | 
			
		||||
                mockDocument = jasmine.createSpyObj(
 | 
			
		||||
                  "$document",
 | 
			
		||||
                  ["find"]
 | 
			
		||||
                );
 | 
			
		||||
                mockBody = jasmine.createSpyObj('body', ['on', 'off']);
 | 
			
		||||
                mockDocument.find.andReturn(mockBody);
 | 
			
		||||
 | 
			
		||||
                mockDeferred.promise = "mock promise";
 | 
			
		||||
 | 
			
		||||
                mockQ.defer.andReturn(mockDeferred);
 | 
			
		||||
@@ -73,8 +64,7 @@ define(
 | 
			
		||||
                dialogService = new DialogService(
 | 
			
		||||
                    mockOverlayService,
 | 
			
		||||
                    mockQ,
 | 
			
		||||
                    mockLog,
 | 
			
		||||
                    mockDocument
 | 
			
		||||
                    mockLog
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
@@ -140,36 +130,6 @@ define(
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("adds a keydown event listener to the body", function () {
 | 
			
		||||
                dialogService.getUserInput({}, {});
 | 
			
		||||
                expect(mockDocument.find).toHaveBeenCalledWith("body");
 | 
			
		||||
                expect(mockBody.on).toHaveBeenCalledWith("keydown", jasmine.any(Function));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("destroys the event listener when the dialog is cancelled", function () {
 | 
			
		||||
                dialogService.getUserInput({}, {});
 | 
			
		||||
                mockOverlayService.createOverlay.mostRecentCall.args[1].cancel();
 | 
			
		||||
                expect(mockBody.off).toHaveBeenCalledWith("keydown", jasmine.any(Function));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("cancels the dialog when an escape keydown event is triggered", function () {
 | 
			
		||||
                dialogService.getUserInput({}, {});
 | 
			
		||||
                mockBody.on.mostRecentCall.args[1]({
 | 
			
		||||
                    keyCode: 27
 | 
			
		||||
                });
 | 
			
		||||
                expect(mockDeferred.reject).toHaveBeenCalled();
 | 
			
		||||
                expect(mockDeferred.resolve).not.toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("ignores non escape keydown events", function () {
 | 
			
		||||
                dialogService.getUserInput({}, {});
 | 
			
		||||
                mockBody.on.mostRecentCall.args[1]({
 | 
			
		||||
                    keyCode: 13
 | 
			
		||||
                });
 | 
			
		||||
                expect(mockDeferred.reject).not.toHaveBeenCalled();
 | 
			
		||||
                expect(mockDeferred.resolve).not.toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("the blocking message dialog", function () {
 | 
			
		||||
                var dialogModel = {};
 | 
			
		||||
                var dialogHandle;
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,25 @@ Contains sources and resources associated with Edit mode.
 | 
			
		||||
 | 
			
		||||
# Extensions
 | 
			
		||||
 | 
			
		||||
## Directives
 | 
			
		||||
 | 
			
		||||
This bundle introduces the `mct-before-unload` directive, primarily for
 | 
			
		||||
internal use (to prompt the user to confirm navigation away from unsaved
 | 
			
		||||
changes in Edit mode.)
 | 
			
		||||
 | 
			
		||||
The `mct-before-unload` directive is used as an attribute whose value is
 | 
			
		||||
an Angular expression that is evaluated when navigation changes (either
 | 
			
		||||
via browser-level changes, such as the refresh button, or changes to
 | 
			
		||||
the Angular route, which happens when hitting the back button in Edit
 | 
			
		||||
mode.) The result of this evaluation, when truthy, is shown in a browser
 | 
			
		||||
dialog to allow the user to confirm navigation. When falsy, no prompt is
 | 
			
		||||
shown, allowing these dialogs to be shown conditionally. (For instance, in
 | 
			
		||||
Edit mode, prompts are only shown if user-initiated changes have
 | 
			
		||||
occurred.)
 | 
			
		||||
 | 
			
		||||
This directive may be attached to any element; its behavior will be enforced
 | 
			
		||||
so long as that element remains within the DOM.
 | 
			
		||||
 | 
			
		||||
# Toolbars
 | 
			
		||||
 | 
			
		||||
Views may specify the contents of a toolbar through a `toolbar`
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,7 @@ define([
 | 
			
		||||
    "./src/controllers/EditPanesController",
 | 
			
		||||
    "./src/controllers/ElementsController",
 | 
			
		||||
    "./src/controllers/EditObjectController",
 | 
			
		||||
    "./src/directives/MCTBeforeUnload",
 | 
			
		||||
    "./src/actions/EditAndComposeAction",
 | 
			
		||||
    "./src/actions/EditAction",
 | 
			
		||||
    "./src/actions/PropertiesAction",
 | 
			
		||||
@@ -36,6 +37,7 @@ define([
 | 
			
		||||
    "./src/policies/EditActionPolicy",
 | 
			
		||||
    "./src/policies/EditableLinkPolicy",
 | 
			
		||||
    "./src/policies/EditableMovePolicy",
 | 
			
		||||
    "./src/policies/EditNavigationPolicy",
 | 
			
		||||
    "./src/policies/EditContextualActionPolicy",
 | 
			
		||||
    "./src/representers/EditRepresenter",
 | 
			
		||||
    "./src/representers/EditToolbarRepresenter",
 | 
			
		||||
@@ -63,6 +65,7 @@ define([
 | 
			
		||||
    EditPanesController,
 | 
			
		||||
    ElementsController,
 | 
			
		||||
    EditObjectController,
 | 
			
		||||
    MCTBeforeUnload,
 | 
			
		||||
    EditAndComposeAction,
 | 
			
		||||
    EditAction,
 | 
			
		||||
    PropertiesAction,
 | 
			
		||||
@@ -74,6 +77,7 @@ define([
 | 
			
		||||
    EditActionPolicy,
 | 
			
		||||
    EditableLinkPolicy,
 | 
			
		||||
    EditableMovePolicy,
 | 
			
		||||
    EditNavigationPolicy,
 | 
			
		||||
    EditContextualActionPolicy,
 | 
			
		||||
    EditRepresenter,
 | 
			
		||||
    EditToolbarRepresenter,
 | 
			
		||||
@@ -128,7 +132,7 @@ define([
 | 
			
		||||
                    "depends": [
 | 
			
		||||
                        "$scope",
 | 
			
		||||
                        "$location",
 | 
			
		||||
                        "navigationService"
 | 
			
		||||
                        "policyService"
 | 
			
		||||
                    ]
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
@@ -148,6 +152,15 @@ define([
 | 
			
		||||
                    ]
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "directives": [
 | 
			
		||||
                {
 | 
			
		||||
                    "key": "mctBeforeUnload",
 | 
			
		||||
                    "implementation": MCTBeforeUnload,
 | 
			
		||||
                    "depends": [
 | 
			
		||||
                        "$window"
 | 
			
		||||
                    ]
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "actions": [
 | 
			
		||||
                {
 | 
			
		||||
                    "key": "compose",
 | 
			
		||||
@@ -163,7 +176,7 @@ define([
 | 
			
		||||
                    ],
 | 
			
		||||
                    "description": "Edit",
 | 
			
		||||
                    "category": "view-control",
 | 
			
		||||
                    "cssClass": "major icon-pencil"
 | 
			
		||||
                    "cssclass": "major icon-pencil"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "key": "properties",
 | 
			
		||||
@@ -172,7 +185,7 @@ define([
 | 
			
		||||
                        "view-control"
 | 
			
		||||
                    ],
 | 
			
		||||
                    "implementation": PropertiesAction,
 | 
			
		||||
                    "cssClass": "major icon-pencil",
 | 
			
		||||
                    "cssclass": "major icon-pencil",
 | 
			
		||||
                    "name": "Edit Properties...",
 | 
			
		||||
                    "description": "Edit properties of this object.",
 | 
			
		||||
                    "depends": [
 | 
			
		||||
@@ -183,7 +196,7 @@ define([
 | 
			
		||||
                    "key": "remove",
 | 
			
		||||
                    "category": "contextual",
 | 
			
		||||
                    "implementation": RemoveAction,
 | 
			
		||||
                    "cssClass": "icon-trash",
 | 
			
		||||
                    "cssclass": "icon-trash",
 | 
			
		||||
                    "name": "Remove",
 | 
			
		||||
                    "description": "Remove this object from its containing object.",
 | 
			
		||||
                    "depends": [
 | 
			
		||||
@@ -195,11 +208,10 @@ define([
 | 
			
		||||
                    "category": "save",
 | 
			
		||||
                    "implementation": SaveAndStopEditingAction,
 | 
			
		||||
                    "name": "Save and Finish Editing",
 | 
			
		||||
                    "cssClass": "icon-save labeled",
 | 
			
		||||
                    "cssclass": "icon-save labeled",
 | 
			
		||||
                    "description": "Save changes made to these objects.",
 | 
			
		||||
                    "depends": [
 | 
			
		||||
                        "dialogService",
 | 
			
		||||
                        "notificationService"
 | 
			
		||||
                        "dialogService"
 | 
			
		||||
                    ]
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
@@ -207,11 +219,10 @@ define([
 | 
			
		||||
                    "category": "save",
 | 
			
		||||
                    "implementation": SaveAction,
 | 
			
		||||
                    "name": "Save and Continue Editing",
 | 
			
		||||
                    "cssClass": "icon-save labeled",
 | 
			
		||||
                    "cssclass": "icon-save labeled",
 | 
			
		||||
                    "description": "Save changes made to these objects.",
 | 
			
		||||
                    "depends": [
 | 
			
		||||
                        "dialogService",
 | 
			
		||||
                        "notificationService"
 | 
			
		||||
                        "dialogService"
 | 
			
		||||
                    ]
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
@@ -219,14 +230,13 @@ define([
 | 
			
		||||
                    "category": "save",
 | 
			
		||||
                    "implementation": SaveAsAction,
 | 
			
		||||
                    "name": "Save As...",
 | 
			
		||||
                    "cssClass": "icon-save labeled",
 | 
			
		||||
                    "cssclass": "icon-save labeled",
 | 
			
		||||
                    "description": "Save changes made to these objects.",
 | 
			
		||||
                    "depends": [
 | 
			
		||||
                        "$injector",
 | 
			
		||||
                        "policyService",
 | 
			
		||||
                        "dialogService",
 | 
			
		||||
                        "copyService",
 | 
			
		||||
                        "notificationService"
 | 
			
		||||
                        "copyService"
 | 
			
		||||
                    ],
 | 
			
		||||
                    "priority": "mandatory"
 | 
			
		||||
                },
 | 
			
		||||
@@ -237,7 +247,7 @@ define([
 | 
			
		||||
                    // Because we use the name as label for edit buttons and mct-control buttons need
 | 
			
		||||
                    // the label to be set to undefined in order to not apply the labeled CSS rule.
 | 
			
		||||
                    "name": undefined,
 | 
			
		||||
                    "cssClass": "icon-x no-label",
 | 
			
		||||
                    "cssclass": "icon-x no-label",
 | 
			
		||||
                    "description": "Discard changes made to these objects.",
 | 
			
		||||
                    "depends": []
 | 
			
		||||
                }
 | 
			
		||||
@@ -260,6 +270,11 @@ define([
 | 
			
		||||
                    "category": "action",
 | 
			
		||||
                    "implementation": EditableLinkPolicy
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "category": "navigation",
 | 
			
		||||
                    "message": "Continuing will cause the loss of any unsaved changes.",
 | 
			
		||||
                    "implementation": EditNavigationPolicy
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "implementation": CreationPolicy,
 | 
			
		||||
                    "category": "creation"
 | 
			
		||||
@@ -374,6 +389,7 @@ define([
 | 
			
		||||
                {
 | 
			
		||||
                    "implementation": EditRepresenter,
 | 
			
		||||
                    "depends": [
 | 
			
		||||
                        "$q",
 | 
			
		||||
                        "$log"
 | 
			
		||||
                    ]
 | 
			
		||||
                },
 | 
			
		||||
 
 | 
			
		||||
@@ -25,14 +25,14 @@
 | 
			
		||||
            <li ng-repeat="createAction in createActions" ng-click="createAction.perform()">
 | 
			
		||||
                <a ng-mouseover="representation.activeMetadata = createAction.getMetadata()"
 | 
			
		||||
                   ng-mouseleave="representation.activeMetadata = undefined"
 | 
			
		||||
                   class="menu-item-a {{ createAction.getMetadata().cssClass }}">
 | 
			
		||||
                   class="menu-item-a {{ createAction.getMetadata().cssclass }}">
 | 
			
		||||
                    {{createAction.getMetadata().name}}
 | 
			
		||||
                </a>
 | 
			
		||||
            </li>
 | 
			
		||||
        </ul>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="pane right menu-item-description">
 | 
			
		||||
        <div class="desc-area icon {{ representation.activeMetadata.cssClass }}"></div>
 | 
			
		||||
        <div class="desc-area icon {{ representation.activeMetadata.cssclass }}"></div>
 | 
			
		||||
        <div class="desc-area title">
 | 
			
		||||
            {{representation.activeMetadata.name}}
 | 
			
		||||
        </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,7 @@
 | 
			
		||||
                     structure="{
 | 
			
		||||
                        text: saveActions[0].getMetadata().name,
 | 
			
		||||
                        click: actionPerformer(saveActions[0]),
 | 
			
		||||
                        cssClass: 'major ' + saveActions[0].getMetadata().cssClass
 | 
			
		||||
                        cssclass: 'major ' + saveActions[0].getMetadata().cssclass
 | 
			
		||||
                     }">
 | 
			
		||||
        </mct-control>
 | 
			
		||||
    </span>
 | 
			
		||||
@@ -36,7 +36,7 @@
 | 
			
		||||
                     structure="{
 | 
			
		||||
                        options: saveActionsAsMenuOptions,
 | 
			
		||||
                        click: saveActionMenuClickHandler,
 | 
			
		||||
                        cssClass: 'btn-bar right icon-save no-label major'
 | 
			
		||||
                        cssclass: 'btn-bar right icon-save no-label major'
 | 
			
		||||
                     }">
 | 
			
		||||
        </mct-control>
 | 
			
		||||
    </span>
 | 
			
		||||
@@ -46,7 +46,7 @@
 | 
			
		||||
                     structure="{
 | 
			
		||||
                        text: currentAction.getMetadata().name,
 | 
			
		||||
                        click: actionPerformer(currentAction),
 | 
			
		||||
                        cssClass: currentAction.getMetadata().cssClass
 | 
			
		||||
                        cssclass: currentAction.getMetadata().cssclass
 | 
			
		||||
                     }">
 | 
			
		||||
        </mct-control>
 | 
			
		||||
    </span>
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,8 @@
 | 
			
		||||
 at runtime from the About dialog for additional information.
 | 
			
		||||
-->
 | 
			
		||||
<div class="abs l-flex-col" ng-controller="EditObjectController as EditObjectController">
 | 
			
		||||
    <div class="holder flex-elem l-flex-row object-browse-bar ">
 | 
			
		||||
    <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"
 | 
			
		||||
 
 | 
			
		||||
@@ -56,13 +56,13 @@ define(
 | 
			
		||||
                    //navigate back to parent because nothing to show.
 | 
			
		||||
                    return domainObject.getCapability("location").getOriginal().then(function (original) {
 | 
			
		||||
                        parent = original.getCapability("context").getParent();
 | 
			
		||||
                        return parent.getCapability("action").perform("navigate");
 | 
			
		||||
                        parent.getCapability("action").perform("navigate");
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function cancel() {
 | 
			
		||||
                return domainObject.getCapability("editor").finish();
 | 
			
		||||
            function cancel(allowed) {
 | 
			
		||||
                return allowed && domainObject.getCapability("editor").finish();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //Do navigation first in order to trigger unsaved changes dialog
 | 
			
		||||
 
 | 
			
		||||
@@ -33,12 +33,10 @@ define(
 | 
			
		||||
         */
 | 
			
		||||
        function SaveAction(
 | 
			
		||||
            dialogService,
 | 
			
		||||
            notificationService,
 | 
			
		||||
            context
 | 
			
		||||
        ) {
 | 
			
		||||
            this.domainObject = (context || {}).domainObject;
 | 
			
		||||
            this.dialogService = dialogService;
 | 
			
		||||
            this.notificationService = notificationService;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
@@ -49,8 +47,7 @@ define(
 | 
			
		||||
         * @memberof platform/commonUI/edit.SaveAction#
 | 
			
		||||
         */
 | 
			
		||||
        SaveAction.prototype.perform = function () {
 | 
			
		||||
            var self = this,
 | 
			
		||||
                domainObject = this.domainObject,
 | 
			
		||||
            var domainObject = this.domainObject,
 | 
			
		||||
                dialog = new SaveInProgressDialog(this.dialogService);
 | 
			
		||||
 | 
			
		||||
            // Invoke any save behavior introduced by the editor capability;
 | 
			
		||||
@@ -61,21 +58,15 @@ define(
 | 
			
		||||
                return domainObject.getCapability("editor").save();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function onSuccess() {
 | 
			
		||||
            function hideBlockingDialog() {
 | 
			
		||||
                dialog.hide();
 | 
			
		||||
                self.notificationService.info("Save Succeeded");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function onFailure() {
 | 
			
		||||
                dialog.hide();
 | 
			
		||||
                self.notificationService.error("Save Failed");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            dialog.show();
 | 
			
		||||
 | 
			
		||||
            return doSave()
 | 
			
		||||
                .then(onSuccess)
 | 
			
		||||
                .catch(onFailure);
 | 
			
		||||
                .then(hideBlockingDialog)
 | 
			
		||||
                .catch(hideBlockingDialog);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
 
 | 
			
		||||
@@ -33,13 +33,11 @@ define(
 | 
			
		||||
         */
 | 
			
		||||
        function SaveAndStopEditingAction(
 | 
			
		||||
            dialogService,
 | 
			
		||||
            notificationService,
 | 
			
		||||
            context
 | 
			
		||||
        ) {
 | 
			
		||||
            this.context = context;
 | 
			
		||||
            this.domainObject = (context || {}).domainObject;
 | 
			
		||||
            this.dialogService = dialogService;
 | 
			
		||||
            this.notificationService = notificationService;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
@@ -51,7 +49,7 @@ define(
 | 
			
		||||
         */
 | 
			
		||||
        SaveAndStopEditingAction.prototype.perform = function () {
 | 
			
		||||
            var domainObject = this.domainObject,
 | 
			
		||||
                saveAction = new SaveAction(this.dialogService, this.notificationService, this.context);
 | 
			
		||||
                saveAction = new SaveAction(this.dialogService, this.context);
 | 
			
		||||
 | 
			
		||||
            function closeEditor() {
 | 
			
		||||
                return domainObject.getCapability("editor").finish();
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,6 @@ define([
 | 
			
		||||
            policyService,
 | 
			
		||||
            dialogService,
 | 
			
		||||
            copyService,
 | 
			
		||||
            notificationService,
 | 
			
		||||
            context
 | 
			
		||||
        ) {
 | 
			
		||||
            this.domainObject = (context || {}).domainObject;
 | 
			
		||||
@@ -53,7 +52,6 @@ define([
 | 
			
		||||
            this.policyService = policyService;
 | 
			
		||||
            this.dialogService = dialogService;
 | 
			
		||||
            this.copyService = copyService;
 | 
			
		||||
            this.notificationService = notificationService;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
@@ -119,10 +117,8 @@ define([
 | 
			
		||||
 | 
			
		||||
                return self.dialogService
 | 
			
		||||
                    .getUserInput(wizard.getFormStructure(true),
 | 
			
		||||
                        wizard.getInitialFormValue())
 | 
			
		||||
                    .then(wizard.populateObjectFromInput.bind(wizard), function (failureReason) {
 | 
			
		||||
                        return Promise.reject("user canceled");
 | 
			
		||||
                    });
 | 
			
		||||
                        wizard.getInitialFormValue()
 | 
			
		||||
                    ).then(wizard.populateObjectFromInput.bind(wizard));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function showBlockingDialog(object) {
 | 
			
		||||
@@ -180,16 +176,8 @@ define([
 | 
			
		||||
                    });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function onSuccess(object) {
 | 
			
		||||
                self.notificationService.info("Save Succeeded");
 | 
			
		||||
                return object;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function onFailure(reason) {
 | 
			
		||||
            function onFailure() {
 | 
			
		||||
                hideBlockingDialog();
 | 
			
		||||
                if (reason !== "user canceled") {
 | 
			
		||||
                    self.notificationService.error("Save Failed");
 | 
			
		||||
                }
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -202,7 +190,6 @@ define([
 | 
			
		||||
                .then(saveAfterClone)
 | 
			
		||||
                .then(finishEditing)
 | 
			
		||||
                .then(hideBlockingDialog)
 | 
			
		||||
                .then(onSuccess)
 | 
			
		||||
                .catch(onFailure);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -48,10 +48,9 @@ define(
 | 
			
		||||
         * Decorate PersistenceCapability to queue persistence calls when a
 | 
			
		||||
         * transaction is in progress.
 | 
			
		||||
         */
 | 
			
		||||
        TransactionCapabilityDecorator.prototype.getCapabilities = function () {
 | 
			
		||||
        TransactionCapabilityDecorator.prototype.getCapabilities = function (model) {
 | 
			
		||||
            var self = this,
 | 
			
		||||
                capabilities = this.capabilityService.getCapabilities
 | 
			
		||||
                    .apply(this.capabilityService, arguments),
 | 
			
		||||
                capabilities = this.capabilityService.getCapabilities(model),
 | 
			
		||||
                persistenceCapability = capabilities.persistence;
 | 
			
		||||
 | 
			
		||||
            capabilities.persistence = function (domainObject) {
 | 
			
		||||
 
 | 
			
		||||
@@ -41,7 +41,7 @@ define(
 | 
			
		||||
                return {
 | 
			
		||||
                    key: action,
 | 
			
		||||
                    name: action.getMetadata().name,
 | 
			
		||||
                    cssClass: action.getMetadata().cssClass
 | 
			
		||||
                    cssclass: action.getMetadata().cssclass
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -28,49 +28,18 @@ define(
 | 
			
		||||
    [],
 | 
			
		||||
    function () {
 | 
			
		||||
 | 
			
		||||
        function isDirty(domainObject) {
 | 
			
		||||
            var navigatedObject = domainObject,
 | 
			
		||||
                editorCapability = navigatedObject &&
 | 
			
		||||
                    navigatedObject.getCapability("editor");
 | 
			
		||||
 | 
			
		||||
            return editorCapability &&
 | 
			
		||||
                editorCapability.isEditContextRoot() &&
 | 
			
		||||
                editorCapability.dirty();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function cancelEditing(domainObject) {
 | 
			
		||||
            var navigatedObject = domainObject,
 | 
			
		||||
                editorCapability = navigatedObject &&
 | 
			
		||||
                    navigatedObject.getCapability("editor");
 | 
			
		||||
 | 
			
		||||
            return editorCapability &&
 | 
			
		||||
                editorCapability.finish();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Controller which is responsible for populating the scope for
 | 
			
		||||
         * Edit mode
 | 
			
		||||
         * @memberof platform/commonUI/edit
 | 
			
		||||
         * @constructor
 | 
			
		||||
         */
 | 
			
		||||
        function EditObjectController($scope, $location, navigationService) {
 | 
			
		||||
        function EditObjectController($scope, $location, policyService) {
 | 
			
		||||
            this.scope = $scope;
 | 
			
		||||
            var domainObject = $scope.domainObject;
 | 
			
		||||
            this.policyService = policyService;
 | 
			
		||||
 | 
			
		||||
            var removeCheck = navigationService
 | 
			
		||||
                .checkBeforeNavigation(function () {
 | 
			
		||||
                    if (isDirty(domainObject)) {
 | 
			
		||||
                        return "Continuing will cause the loss of any unsaved changes.";
 | 
			
		||||
                    }
 | 
			
		||||
                    return false;
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            $scope.$on('$destroy', function () {
 | 
			
		||||
                removeCheck();
 | 
			
		||||
                cancelEditing(domainObject);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            function setViewForDomainObject() {
 | 
			
		||||
            var navigatedObject;
 | 
			
		||||
            function setViewForDomainObject(domainObject) {
 | 
			
		||||
 | 
			
		||||
                var locationViewKey = $location.search().view;
 | 
			
		||||
 | 
			
		||||
@@ -85,15 +54,34 @@ define(
 | 
			
		||||
                    ((domainObject && domainObject.useCapability('view')) || [])
 | 
			
		||||
                        .forEach(selectViewIfMatching);
 | 
			
		||||
                }
 | 
			
		||||
                navigatedObject = domainObject;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            setViewForDomainObject();
 | 
			
		||||
            $scope.$watch('domainObject', setViewForDomainObject);
 | 
			
		||||
 | 
			
		||||
            $scope.doAction = function (action) {
 | 
			
		||||
                return $scope[action] && $scope[action]();
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Get the warning to show if the user attempts to navigate
 | 
			
		||||
         * away from Edit mode while unsaved changes are present.
 | 
			
		||||
         * @returns {string} the warning to show, or undefined if
 | 
			
		||||
         *          there are no unsaved changes
 | 
			
		||||
         */
 | 
			
		||||
        EditObjectController.prototype.getUnloadWarning = function () {
 | 
			
		||||
            var navigatedObject = this.scope.domainObject,
 | 
			
		||||
                policyMessage;
 | 
			
		||||
 | 
			
		||||
            this.policyService.allow("navigation", navigatedObject, undefined, function (message) {
 | 
			
		||||
                policyMessage = message;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            return policyMessage;
 | 
			
		||||
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return EditObjectController;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -51,7 +51,7 @@ define(
 | 
			
		||||
        function AddAction(type, parent, context, $q, dialogService, policyService) {
 | 
			
		||||
            this.metadata = {
 | 
			
		||||
                key: 'add',
 | 
			
		||||
                cssClass: type.getCssClass(),
 | 
			
		||||
                cssclass: type.getCssClass(),
 | 
			
		||||
                name: type.getName(),
 | 
			
		||||
                type: type.getKey(),
 | 
			
		||||
                description: type.getDescription(),
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,8 @@ define(
 | 
			
		||||
        AddActionProvider.prototype.getActions = function (actionContext) {
 | 
			
		||||
            var context = actionContext || {},
 | 
			
		||||
                key = context.key,
 | 
			
		||||
                destination = context.domainObject;
 | 
			
		||||
                destination = context.domainObject,
 | 
			
		||||
                self = this;
 | 
			
		||||
 | 
			
		||||
            // We only provide Add actions, and we need a
 | 
			
		||||
            // domain object to serve as the container for the
 | 
			
		||||
@@ -65,16 +66,18 @@ define(
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Introduce one create action per type
 | 
			
		||||
            return ['timeline', 'activity'].map(function (type) {
 | 
			
		||||
            return this.typeService.listTypes().filter(function (type) {
 | 
			
		||||
                return self.policyService.allow("creation", type) && self.policyService.allow("composition", destination.getCapability('type'), type);
 | 
			
		||||
            }).map(function (type) {
 | 
			
		||||
                return new AddAction(
 | 
			
		||||
                    this.typeService.getType(type),
 | 
			
		||||
                    type,
 | 
			
		||||
                    destination,
 | 
			
		||||
                    context,
 | 
			
		||||
                    this.$q,
 | 
			
		||||
                    this.dialogService,
 | 
			
		||||
                    this.policyService
 | 
			
		||||
                    self.$q,
 | 
			
		||||
                    self.dialogService,
 | 
			
		||||
                    self.policyService
 | 
			
		||||
                );
 | 
			
		||||
            }, this);
 | 
			
		||||
            });
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return AddActionProvider;
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,7 @@ define(
 | 
			
		||||
        function CreateAction(type, parent, context) {
 | 
			
		||||
            this.metadata = {
 | 
			
		||||
                key: 'create',
 | 
			
		||||
                cssClass: type.getCssClass(),
 | 
			
		||||
                cssclass: type.getCssClass(),
 | 
			
		||||
                name: type.getName(),
 | 
			
		||||
                type: type.getKey(),
 | 
			
		||||
                description: type.getDescription(),
 | 
			
		||||
 
 | 
			
		||||
@@ -56,16 +56,16 @@ define(
 | 
			
		||||
         */
 | 
			
		||||
        CreateWizard.prototype.getFormStructure = function (includeLocation) {
 | 
			
		||||
            var sections = [],
 | 
			
		||||
                domainObject = this.domainObject,
 | 
			
		||||
                type = this.type,
 | 
			
		||||
                policyService = this.policyService;
 | 
			
		||||
 | 
			
		||||
            function validateLocation(parent) {
 | 
			
		||||
                var parentType = parent &&
 | 
			
		||||
                    parent.getCapability('type');
 | 
			
		||||
                return parentType && policyService.allow(
 | 
			
		||||
            function validateLocation(locatingObject) {
 | 
			
		||||
                var locatingType = locatingObject &&
 | 
			
		||||
                    locatingObject.getCapability('type');
 | 
			
		||||
                return locatingType && policyService.allow(
 | 
			
		||||
                    "composition",
 | 
			
		||||
                    parentType,
 | 
			
		||||
                    domainObject
 | 
			
		||||
                    locatingType,
 | 
			
		||||
                    type
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -91,7 +91,7 @@ define(
 | 
			
		||||
            if (includeLocation) {
 | 
			
		||||
                sections.push({
 | 
			
		||||
                    name: 'Location',
 | 
			
		||||
                    cssClass: "grows",
 | 
			
		||||
                    cssclass: "grows",
 | 
			
		||||
                    rows: [{
 | 
			
		||||
                        name: "Save In",
 | 
			
		||||
                        control: "locator",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										104
									
								
								platform/commonUI/edit/src/directives/MCTBeforeUnload.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								platform/commonUI/edit/src/directives/MCTBeforeUnload.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,104 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2016, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT 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 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(
 | 
			
		||||
    [],
 | 
			
		||||
    function () {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Defines the `mct-before-unload` directive. The expression bound
 | 
			
		||||
         * to this attribute will be evaluated during page navigation events
 | 
			
		||||
         * and, if it returns a truthy value, will be used to populate a
 | 
			
		||||
         * prompt to the user to confirm this navigation.
 | 
			
		||||
         * @memberof platform/commonUI/edit
 | 
			
		||||
         * @constructor
 | 
			
		||||
         * @param $window the window
 | 
			
		||||
         */
 | 
			
		||||
        function MCTBeforeUnload($window) {
 | 
			
		||||
            var unloads = [],
 | 
			
		||||
                oldBeforeUnload = $window.onbeforeunload;
 | 
			
		||||
 | 
			
		||||
            // Run all unload functions, returning the first returns truthily.
 | 
			
		||||
            function checkUnloads() {
 | 
			
		||||
                var result;
 | 
			
		||||
                unloads.forEach(function (unload) {
 | 
			
		||||
                    result = result || unload();
 | 
			
		||||
                });
 | 
			
		||||
                return result;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Link function for an mct-before-unload directive usage
 | 
			
		||||
            function link(scope, element, attrs) {
 | 
			
		||||
                // Invoke the
 | 
			
		||||
                function unload() {
 | 
			
		||||
                    return scope.$eval(attrs.mctBeforeUnload);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Stop using this unload expression
 | 
			
		||||
                function removeUnload() {
 | 
			
		||||
                    unloads = unloads.filter(function (callback) {
 | 
			
		||||
                        return callback !== unload;
 | 
			
		||||
                    });
 | 
			
		||||
                    if (unloads.length === 0) {
 | 
			
		||||
                        $window.onbeforeunload = oldBeforeUnload;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Show a dialog before allowing a location change
 | 
			
		||||
                function checkLocationChange(event) {
 | 
			
		||||
                    // Get an unload message (if any)
 | 
			
		||||
                    var warning = unload();
 | 
			
		||||
                    // Prompt the user if there's an unload message
 | 
			
		||||
                    if (warning && !$window.confirm(warning)) {
 | 
			
		||||
                        // ...and prevent the route change if it was confirmed
 | 
			
		||||
                        event.preventDefault();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // If this is the first active instance of this directive,
 | 
			
		||||
                // register as the window's beforeunload handler
 | 
			
		||||
                if (unloads.length === 0) {
 | 
			
		||||
                    $window.onbeforeunload = checkUnloads;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Include this instance of the directive's unload function
 | 
			
		||||
                unloads.push(unload);
 | 
			
		||||
 | 
			
		||||
                // Remove it when the scope is destroyed
 | 
			
		||||
                scope.$on("$destroy", removeUnload);
 | 
			
		||||
 | 
			
		||||
                // Also handle route changes
 | 
			
		||||
                scope.$on("$locationChangeStart", checkLocationChange);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                // Applicable as an attribute
 | 
			
		||||
                restrict: "A",
 | 
			
		||||
                // Link with the provided function
 | 
			
		||||
                link: link
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return MCTBeforeUnload;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										64
									
								
								platform/commonUI/edit/src/policies/EditNavigationPolicy.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								platform/commonUI/edit/src/policies/EditNavigationPolicy.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2016, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT 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 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(
 | 
			
		||||
    [],
 | 
			
		||||
    function () {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Policy controlling whether navigation events should proceed
 | 
			
		||||
         * when object is being edited.
 | 
			
		||||
         * @memberof platform/commonUI/edit
 | 
			
		||||
         * @constructor
 | 
			
		||||
         * @implements {Policy.<Action, ActionContext>}
 | 
			
		||||
         */
 | 
			
		||||
        function EditNavigationPolicy(policyService) {
 | 
			
		||||
            this.policyService = policyService;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * @private
 | 
			
		||||
         */
 | 
			
		||||
        EditNavigationPolicy.prototype.isDirty = function (domainObject) {
 | 
			
		||||
            var navigatedObject = domainObject,
 | 
			
		||||
                editorCapability = navigatedObject &&
 | 
			
		||||
                    navigatedObject.getCapability("editor");
 | 
			
		||||
 | 
			
		||||
            return editorCapability &&
 | 
			
		||||
                editorCapability.isEditContextRoot() &&
 | 
			
		||||
                editorCapability.dirty();
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Allow navigation if an object is not dirty, or if the user elects
 | 
			
		||||
         * to proceed anyway.
 | 
			
		||||
         * @param currentNavigation
 | 
			
		||||
         * @returns {boolean|*} true if the object model is clean; or if
 | 
			
		||||
         * it's dirty and the user wishes to proceed anyway.
 | 
			
		||||
         */
 | 
			
		||||
        EditNavigationPolicy.prototype.allow = function (currentNavigation) {
 | 
			
		||||
            return !this.isDirty(currentNavigation);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return EditNavigationPolicy;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -43,55 +43,98 @@ define(
 | 
			
		||||
         * @implements {Representer}
 | 
			
		||||
         * @constructor
 | 
			
		||||
         */
 | 
			
		||||
        function EditRepresenter($log, $scope) {
 | 
			
		||||
            this.$log = $log;
 | 
			
		||||
            this.$scope = $scope;
 | 
			
		||||
        function EditRepresenter($q, $log, scope) {
 | 
			
		||||
            var self = this;
 | 
			
		||||
 | 
			
		||||
            this.scope = scope;
 | 
			
		||||
            this.listenHandle = undefined;
 | 
			
		||||
 | 
			
		||||
            // Mutate and persist a new version of a domain object's model.
 | 
			
		||||
            function doMutate(model) {
 | 
			
		||||
                var domainObject = self.domainObject;
 | 
			
		||||
 | 
			
		||||
                // First, mutate; then, persist.
 | 
			
		||||
                return $q.when(domainObject.useCapability("mutation", function () {
 | 
			
		||||
                    return model;
 | 
			
		||||
                }));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Handle changes to model and/or view configuration
 | 
			
		||||
            function commit(message) {
 | 
			
		||||
                // Look up from scope; these will have been populated by
 | 
			
		||||
                // mct-representation.
 | 
			
		||||
                var model = scope.model,
 | 
			
		||||
                    configuration = scope.configuration,
 | 
			
		||||
                    domainObject = self.domainObject;
 | 
			
		||||
 | 
			
		||||
                // Log the commit message
 | 
			
		||||
                $log.debug([
 | 
			
		||||
                    "Committing ",
 | 
			
		||||
                    domainObject && domainObject.getModel().name,
 | 
			
		||||
                    "(" + (domainObject && domainObject.getId()) + "):",
 | 
			
		||||
                    message
 | 
			
		||||
                ].join(" "));
 | 
			
		||||
 | 
			
		||||
                // Update the configuration stored in the model, and persist.
 | 
			
		||||
                if (domainObject) {
 | 
			
		||||
                    // Configurations for specific views are stored by
 | 
			
		||||
                    // key in the "configuration" field of the model.
 | 
			
		||||
                    if (self.key && configuration) {
 | 
			
		||||
                        model.configuration = model.configuration || {};
 | 
			
		||||
                        model.configuration[self.key] = configuration;
 | 
			
		||||
                    }
 | 
			
		||||
                    doMutate(model);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Place the "commit" method in the scope
 | 
			
		||||
            scope.commit = commit;
 | 
			
		||||
 | 
			
		||||
            // Clean up when the scope is destroyed
 | 
			
		||||
            scope.$on("$destroy", function () {
 | 
			
		||||
                self.destroy();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            this.$scope.commit = this.commit.bind(this);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Commit any changes made to the in-scope model to the domain object.
 | 
			
		||||
         * Also commits any changes made to $scope.configuration to the proper
 | 
			
		||||
         * configuration value for the current representation.
 | 
			
		||||
         *
 | 
			
		||||
         * @param {String} message a message to log with the commit message.
 | 
			
		||||
         */
 | 
			
		||||
        EditRepresenter.prototype.commit = function (message) {
 | 
			
		||||
            var model = this.$scope.model,
 | 
			
		||||
                configuration = this.$scope.configuration,
 | 
			
		||||
                domainObject = this.domainObject;
 | 
			
		||||
 | 
			
		||||
            this.$log.debug([
 | 
			
		||||
                "Committing ",
 | 
			
		||||
                domainObject && domainObject.getModel().name,
 | 
			
		||||
                "(" + (domainObject && domainObject.getId()) + "):",
 | 
			
		||||
                message
 | 
			
		||||
            ].join(" "));
 | 
			
		||||
 | 
			
		||||
            if (this.domainObject) {
 | 
			
		||||
                if (this.key && configuration) {
 | 
			
		||||
                    model.configuration = model.configuration || {};
 | 
			
		||||
                    model.configuration[this.key] = configuration;
 | 
			
		||||
                }
 | 
			
		||||
                domainObject.useCapability('mutation', function () {
 | 
			
		||||
                    return model;
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Handle a specific representation of a specific domain object
 | 
			
		||||
        EditRepresenter.prototype.represent = function (representation, representedObject) {
 | 
			
		||||
        EditRepresenter.prototype.represent = function represent(representation, representedObject) {
 | 
			
		||||
            var scope = this.scope;
 | 
			
		||||
 | 
			
		||||
            // Track the key, to know which view configuration to save to.
 | 
			
		||||
            this.key = (representation || {}).key;
 | 
			
		||||
            // Track the represented object
 | 
			
		||||
            this.domainObject = representedObject;
 | 
			
		||||
            if (representation) {
 | 
			
		||||
                this.key = representation.key;
 | 
			
		||||
            } else {
 | 
			
		||||
                delete this.key;
 | 
			
		||||
 | 
			
		||||
            // Ensure existing watches are released
 | 
			
		||||
            this.destroy();
 | 
			
		||||
 | 
			
		||||
            function setEditing() {
 | 
			
		||||
                scope.viewObjectTemplate = 'edit-object';
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * Listen for changes in object state. If the object becomes
 | 
			
		||||
             * editable then change the view and inspector regions
 | 
			
		||||
             * object representation accordingly
 | 
			
		||||
             */
 | 
			
		||||
            this.listenHandle = this.domainObject.getCapability('status').listen(function (statuses) {
 | 
			
		||||
                if (statuses.indexOf('editing') !== -1) {
 | 
			
		||||
                    setEditing();
 | 
			
		||||
                } else {
 | 
			
		||||
                    delete scope.viewObjectTemplate;
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (representedObject.hasCapability('editor') && representedObject.getCapability('editor').isEditContextRoot()) {
 | 
			
		||||
                setEditing();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Respond to the destruction of the current representation.
 | 
			
		||||
        EditRepresenter.prototype.destroy = function () {};
 | 
			
		||||
        EditRepresenter.prototype.destroy = function destroy() {
 | 
			
		||||
            return this.listenHandle && this.listenHandle();
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return EditRepresenter;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,6 @@
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
/*global describe,it,expect,beforeEach,jasmine,waitsFor,runs*/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ["../../src/actions/SaveAction"],
 | 
			
		||||
@@ -29,8 +28,7 @@ define(
 | 
			
		||||
            var mockDomainObject,
 | 
			
		||||
                mockEditorCapability,
 | 
			
		||||
                actionContext,
 | 
			
		||||
                mockDialogService,
 | 
			
		||||
                mockNotificationService,
 | 
			
		||||
                dialogService,
 | 
			
		||||
                mockActionCapability,
 | 
			
		||||
                capabilities = {},
 | 
			
		||||
                action;
 | 
			
		||||
@@ -70,17 +68,11 @@ define(
 | 
			
		||||
                actionContext = {
 | 
			
		||||
                    domainObject: mockDomainObject
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                mockDialogService = jasmine.createSpyObj(
 | 
			
		||||
                dialogService = jasmine.createSpyObj(
 | 
			
		||||
                    "dialogService",
 | 
			
		||||
                    ["showBlockingMessage"]
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                mockNotificationService = jasmine.createSpyObj(
 | 
			
		||||
                    "notificationService",
 | 
			
		||||
                    ["info", "error"]
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                mockDomainObject.hasCapability.andReturn(true);
 | 
			
		||||
                mockDomainObject.getCapability.andCallFake(function (capability) {
 | 
			
		||||
                    return capabilities[capability];
 | 
			
		||||
@@ -89,7 +81,7 @@ define(
 | 
			
		||||
                mockEditorCapability.save.andReturn(mockPromise(true));
 | 
			
		||||
                mockEditorCapability.isEditContextRoot.andReturn(true);
 | 
			
		||||
 | 
			
		||||
                action = new SaveAction(mockDialogService, mockNotificationService, actionContext);
 | 
			
		||||
                action = new SaveAction(dialogService, actionContext);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("only applies to domain object with an editor capability", function () {
 | 
			
		||||
@@ -113,54 +105,30 @@ define(
 | 
			
		||||
                    expect(mockEditorCapability.save).toHaveBeenCalled();
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            describe("in order to keep the user in the loop", function () {
 | 
			
		||||
            describe("a blocking dialog", function () {
 | 
			
		||||
                var mockDialogHandle;
 | 
			
		||||
 | 
			
		||||
                beforeEach(function () {
 | 
			
		||||
                    mockDialogHandle = jasmine.createSpyObj("dialogHandle", ["dismiss"]);
 | 
			
		||||
                    mockDialogService.showBlockingMessage.andReturn(mockDialogHandle);
 | 
			
		||||
                    dialogService.showBlockingMessage.andReturn(mockDialogHandle);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                it("shows a dialog while saving", function () {
 | 
			
		||||
                    mockEditorCapability.save.andReturn(new Promise(function () {
 | 
			
		||||
                    }));
 | 
			
		||||
                    action.perform();
 | 
			
		||||
                    expect(mockDialogService.showBlockingMessage).toHaveBeenCalled();
 | 
			
		||||
                    expect(dialogService.showBlockingMessage).toHaveBeenCalled();
 | 
			
		||||
                    expect(mockDialogHandle.dismiss).not.toHaveBeenCalled();
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("hides the dialog when saving is complete", function () {
 | 
			
		||||
                it("hides a dialog when saving is complete", function () {
 | 
			
		||||
                    action.perform();
 | 
			
		||||
                    expect(mockDialogService.showBlockingMessage).toHaveBeenCalled();
 | 
			
		||||
                    expect(dialogService.showBlockingMessage).toHaveBeenCalled();
 | 
			
		||||
                    expect(mockDialogHandle.dismiss).toHaveBeenCalled();
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("notifies if saving succeeded", function () {
 | 
			
		||||
                    var mockCallback = jasmine.createSpy("callback");
 | 
			
		||||
                    mockEditorCapability.save.andReturn(Promise.resolve("success"));
 | 
			
		||||
                    action.perform().then(mockCallback);
 | 
			
		||||
                    waitsFor(function () {
 | 
			
		||||
                        return mockCallback.calls.length > 0;
 | 
			
		||||
                    });
 | 
			
		||||
                    runs(function () {
 | 
			
		||||
                        expect(mockNotificationService.info).toHaveBeenCalled();
 | 
			
		||||
                        expect(mockNotificationService.error).not.toHaveBeenCalled();
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("notifies if saving failed", function () {
 | 
			
		||||
                    var mockCallback = jasmine.createSpy("callback");
 | 
			
		||||
                    mockEditorCapability.save.andReturn(Promise.reject("some failure reason"));
 | 
			
		||||
                    action.perform().then(mockCallback);
 | 
			
		||||
                    waitsFor(function () {
 | 
			
		||||
                        return mockCallback.calls.length > 0;
 | 
			
		||||
                    });
 | 
			
		||||
                    runs(function () {
 | 
			
		||||
                        expect(mockNotificationService.error).toHaveBeenCalled();
 | 
			
		||||
                        expect(mockNotificationService.info).not.toHaveBeenCalled();
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,6 @@
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
/*global describe,it,expect,beforeEach,jasmine*/
 | 
			
		||||
 | 
			
		||||
define(
 | 
			
		||||
    ["../../src/actions/SaveAndStopEditingAction"],
 | 
			
		||||
@@ -36,7 +35,6 @@ define(
 | 
			
		||||
                mockEditorCapability,
 | 
			
		||||
                actionContext,
 | 
			
		||||
                dialogService,
 | 
			
		||||
                notificationService,
 | 
			
		||||
                mockActionCapability,
 | 
			
		||||
                capabilities = {},
 | 
			
		||||
                action;
 | 
			
		||||
@@ -81,11 +79,6 @@ define(
 | 
			
		||||
                    ["showBlockingMessage"]
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                notificationService = jasmine.createSpyObj(
 | 
			
		||||
                    "notificationService",
 | 
			
		||||
                    ["info", "error"]
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                mockDomainObject.hasCapability.andReturn(true);
 | 
			
		||||
                mockDomainObject.getCapability.andCallFake(function (capability) {
 | 
			
		||||
                    return capabilities[capability];
 | 
			
		||||
@@ -94,7 +87,7 @@ define(
 | 
			
		||||
                mockEditorCapability.save.andReturn(mockPromise(true));
 | 
			
		||||
                mockEditorCapability.isEditContextRoot.andReturn(true);
 | 
			
		||||
 | 
			
		||||
                action = new SaveAndStopEditingAction(dialogService, notificationService, actionContext);
 | 
			
		||||
                action = new SaveAndStopEditingAction(dialogService, actionContext);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -27,13 +27,11 @@ define(
 | 
			
		||||
 | 
			
		||||
        describe("The Save As action", function () {
 | 
			
		||||
            var mockDomainObject,
 | 
			
		||||
                mockClonedObject,
 | 
			
		||||
                mockEditorCapability,
 | 
			
		||||
                mockActionCapability,
 | 
			
		||||
                mockObjectService,
 | 
			
		||||
                mockDialogService,
 | 
			
		||||
                mockCopyService,
 | 
			
		||||
                mockNotificationService,
 | 
			
		||||
                mockParent,
 | 
			
		||||
                actionContext,
 | 
			
		||||
                capabilities = {},
 | 
			
		||||
@@ -59,8 +57,7 @@ define(
 | 
			
		||||
                    [
 | 
			
		||||
                        "getCapability",
 | 
			
		||||
                        "hasCapability",
 | 
			
		||||
                        "getModel",
 | 
			
		||||
                        "getId"
 | 
			
		||||
                        "getModel"
 | 
			
		||||
                    ]
 | 
			
		||||
                );
 | 
			
		||||
                mockDomainObject.hasCapability.andReturn(true);
 | 
			
		||||
@@ -68,15 +65,6 @@ define(
 | 
			
		||||
                    return capabilities[capability];
 | 
			
		||||
                });
 | 
			
		||||
                mockDomainObject.getModel.andReturn({location: 'a', persisted: undefined});
 | 
			
		||||
                mockDomainObject.getId.andReturn(0);
 | 
			
		||||
 | 
			
		||||
                mockClonedObject = jasmine.createSpyObj(
 | 
			
		||||
                    "clonedObject",
 | 
			
		||||
                    [
 | 
			
		||||
                        "getId"
 | 
			
		||||
                    ]
 | 
			
		||||
                );
 | 
			
		||||
                mockClonedObject.getId.andReturn(1);
 | 
			
		||||
 | 
			
		||||
                mockParent = jasmine.createSpyObj(
 | 
			
		||||
                    "parentObject",
 | 
			
		||||
@@ -123,27 +111,12 @@ define(
 | 
			
		||||
                        "perform"
 | 
			
		||||
                    ]
 | 
			
		||||
                );
 | 
			
		||||
                mockCopyService.perform.andReturn(mockPromise(mockClonedObject));
 | 
			
		||||
 | 
			
		||||
                mockNotificationService = jasmine.createSpyObj(
 | 
			
		||||
                    "notificationService",
 | 
			
		||||
                    [
 | 
			
		||||
                        "info",
 | 
			
		||||
                        "error"
 | 
			
		||||
                    ]
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                actionContext = {
 | 
			
		||||
                    domainObject: mockDomainObject
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                action = new SaveAsAction(
 | 
			
		||||
                    undefined,
 | 
			
		||||
                    undefined,
 | 
			
		||||
                    mockDialogService,
 | 
			
		||||
                    mockCopyService,
 | 
			
		||||
                    mockNotificationService,
 | 
			
		||||
                    actionContext);
 | 
			
		||||
                action = new SaveAsAction(undefined, undefined, mockDialogService, mockCopyService, actionContext);
 | 
			
		||||
 | 
			
		||||
                spyOn(action, "getObjectService");
 | 
			
		||||
                action.getObjectService.andReturn(mockObjectService);
 | 
			
		||||
@@ -213,7 +186,7 @@ define(
 | 
			
		||||
                expect(mockDialogService.getUserInput).toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("in order to keep the user in the loop", function () {
 | 
			
		||||
            describe("a blocking dialog", function () {
 | 
			
		||||
                var mockDialogHandle;
 | 
			
		||||
 | 
			
		||||
                beforeEach(function () {
 | 
			
		||||
@@ -221,14 +194,14 @@ define(
 | 
			
		||||
                    mockDialogService.showBlockingMessage.andReturn(mockDialogHandle);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("shows a blocking dialog indicating that saving is in progress", function () {
 | 
			
		||||
                it("indicates that a save is taking place", function () {
 | 
			
		||||
                    mockEditorCapability.save.andReturn(new Promise(function () {}));
 | 
			
		||||
                    action.perform();
 | 
			
		||||
                    expect(mockDialogService.showBlockingMessage).toHaveBeenCalled();
 | 
			
		||||
                    expect(mockDialogHandle.dismiss).not.toHaveBeenCalled();
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("hides the blocking dialog after saving finishes", function () {
 | 
			
		||||
                it("is hidden after saving", function () {
 | 
			
		||||
                    var mockCallback = jasmine.createSpy();
 | 
			
		||||
                    action.perform().then(mockCallback);
 | 
			
		||||
                    expect(mockDialogService.showBlockingMessage).toHaveBeenCalled();
 | 
			
		||||
@@ -239,31 +212,6 @@ define(
 | 
			
		||||
                        expect(mockDialogHandle.dismiss).toHaveBeenCalled();
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("notifies if saving succeeded", function () {
 | 
			
		||||
                    var mockCallback = jasmine.createSpy();
 | 
			
		||||
                    action.perform().then(mockCallback);
 | 
			
		||||
                    waitsFor(function () {
 | 
			
		||||
                        return mockCallback.calls.length > 0;
 | 
			
		||||
                    });
 | 
			
		||||
                    runs(function () {
 | 
			
		||||
                        expect(mockNotificationService.info).toHaveBeenCalled();
 | 
			
		||||
                        expect(mockNotificationService.error).not.toHaveBeenCalled();
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                it("notifies if saving failed", function () {
 | 
			
		||||
                    mockCopyService.perform.andReturn(Promise.reject("some failure reason"));
 | 
			
		||||
                    var mockCallback = jasmine.createSpy();
 | 
			
		||||
                    action.perform().then(mockCallback);
 | 
			
		||||
                    waitsFor(function () {
 | 
			
		||||
                        return mockCallback.calls.length > 0;
 | 
			
		||||
                    });
 | 
			
		||||
                    runs(function () {
 | 
			
		||||
                        expect(mockNotificationService.error).toHaveBeenCalled();
 | 
			
		||||
                        expect(mockNotificationService.info).not.toHaveBeenCalled();
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ define(
 | 
			
		||||
        describe("The Edit Action controller", function () {
 | 
			
		||||
            var mockSaveActionMetadata = {
 | 
			
		||||
                name: "mocked-save-action",
 | 
			
		||||
                cssClass: "mocked-save-action-css"
 | 
			
		||||
                cssclass: "mocked-save-action-css"
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            function fakeGetActions(actionContext) {
 | 
			
		||||
@@ -86,7 +86,7 @@ define(
 | 
			
		||||
                expect(menuOptions[1].key).toEqual(mockScope.saveActions[1]);
 | 
			
		||||
                menuOptions.forEach(function (option) {
 | 
			
		||||
                    expect(option.name).toEqual(mockSaveActionMetadata.name);
 | 
			
		||||
                    expect(option.cssClass).toEqual(mockSaveActionMetadata.cssClass);
 | 
			
		||||
                    expect(option.cssclass).toEqual(mockSaveActionMetadata.cssclass);
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -24,19 +24,32 @@ define(
 | 
			
		||||
    ["../../src/controllers/EditObjectController"],
 | 
			
		||||
    function (EditObjectController) {
 | 
			
		||||
 | 
			
		||||
        describe("The Edit Object controller", function () {
 | 
			
		||||
        describe("The Edit mode controller", function () {
 | 
			
		||||
            var mockScope,
 | 
			
		||||
                mockObject,
 | 
			
		||||
                testViews,
 | 
			
		||||
                mockEditorCapability,
 | 
			
		||||
                mockType,
 | 
			
		||||
                mockLocation,
 | 
			
		||||
                mockNavigationService,
 | 
			
		||||
                removeCheck,
 | 
			
		||||
                mockStatusCapability,
 | 
			
		||||
                mockCapabilities,
 | 
			
		||||
                mockPolicyService,
 | 
			
		||||
                controller;
 | 
			
		||||
 | 
			
		||||
            // Utility function; look for a $watch on scope and fire it
 | 
			
		||||
            function fireWatch(expr, value) {
 | 
			
		||||
                mockScope.$watch.calls.forEach(function (call) {
 | 
			
		||||
                    if (call.args[0] === expr) {
 | 
			
		||||
                        call.args[1](value);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockPolicyService = jasmine.createSpyObj(
 | 
			
		||||
                    "policyService",
 | 
			
		||||
                    [
 | 
			
		||||
                        "allow"
 | 
			
		||||
                    ]
 | 
			
		||||
                );
 | 
			
		||||
                mockScope = jasmine.createSpyObj(
 | 
			
		||||
                    "$scope",
 | 
			
		||||
                    ["$on", "$watch"]
 | 
			
		||||
@@ -45,16 +58,16 @@ define(
 | 
			
		||||
                    "domainObject",
 | 
			
		||||
                    ["getId", "getModel", "getCapability", "hasCapability", "useCapability"]
 | 
			
		||||
                );
 | 
			
		||||
                mockEditorCapability = jasmine.createSpyObj(
 | 
			
		||||
                    "mockEditorCapability",
 | 
			
		||||
                    ["isEditContextRoot", "dirty", "finish"]
 | 
			
		||||
                mockType = jasmine.createSpyObj(
 | 
			
		||||
                    "type",
 | 
			
		||||
                    ["hasFeature"]
 | 
			
		||||
                );
 | 
			
		||||
                mockStatusCapability = jasmine.createSpyObj('statusCapability',
 | 
			
		||||
                    ["get"]
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                mockCapabilities = {
 | 
			
		||||
                    "editor" : mockEditorCapability,
 | 
			
		||||
                    "type" : mockType,
 | 
			
		||||
                    "status": mockStatusCapability
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
@@ -62,70 +75,52 @@ define(
 | 
			
		||||
                    ["search"]
 | 
			
		||||
                );
 | 
			
		||||
                mockLocation.search.andReturn({"view": "fixed"});
 | 
			
		||||
                mockNavigationService = jasmine.createSpyObj('navigationService',
 | 
			
		||||
                    ["checkBeforeNavigation"]
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                removeCheck = jasmine.createSpy('removeCheck');
 | 
			
		||||
                mockNavigationService.checkBeforeNavigation.andReturn(removeCheck);
 | 
			
		||||
 | 
			
		||||
                mockObject.getId.andReturn("test");
 | 
			
		||||
                mockObject.getModel.andReturn({ name: "Test object" });
 | 
			
		||||
                mockObject.getCapability.andCallFake(function (key) {
 | 
			
		||||
                    return mockCapabilities[key];
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                testViews = [
 | 
			
		||||
                    { key: 'abc' },
 | 
			
		||||
                    { key: 'def', someKey: 'some value' },
 | 
			
		||||
                    { key: 'xyz' }
 | 
			
		||||
                ];
 | 
			
		||||
 | 
			
		||||
                mockObject.useCapability.andCallFake(function (c) {
 | 
			
		||||
                    return (c === 'view') && testViews;
 | 
			
		||||
                });
 | 
			
		||||
                mockLocation.search.andReturn({ view: 'def' });
 | 
			
		||||
                mockType.hasFeature.andReturn(true);
 | 
			
		||||
 | 
			
		||||
                mockScope.domainObject = mockObject;
 | 
			
		||||
 | 
			
		||||
                controller = new EditObjectController(
 | 
			
		||||
                    mockScope,
 | 
			
		||||
                    mockLocation,
 | 
			
		||||
                    mockNavigationService
 | 
			
		||||
                    mockPolicyService
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("adds a check before navigation", function () {
 | 
			
		||||
                expect(mockNavigationService.checkBeforeNavigation)
 | 
			
		||||
                    .toHaveBeenCalledWith(jasmine.any(Function));
 | 
			
		||||
            it("exposes a warning message for unload", function () {
 | 
			
		||||
                var errorMessage = "Unsaved changes";
 | 
			
		||||
 | 
			
		||||
                var checkFn = mockNavigationService.checkBeforeNavigation.mostRecentCall.args[0];
 | 
			
		||||
                // Normally, should be undefined
 | 
			
		||||
                expect(controller.getUnloadWarning()).toBeUndefined();
 | 
			
		||||
 | 
			
		||||
                mockEditorCapability.isEditContextRoot.andReturn(false);
 | 
			
		||||
                mockEditorCapability.dirty.andReturn(false);
 | 
			
		||||
 | 
			
		||||
                expect(checkFn()).toBe(false);
 | 
			
		||||
 | 
			
		||||
                mockEditorCapability.isEditContextRoot.andReturn(true);
 | 
			
		||||
                expect(checkFn()).toBe(false);
 | 
			
		||||
 | 
			
		||||
                mockEditorCapability.dirty.andReturn(true);
 | 
			
		||||
                expect(checkFn())
 | 
			
		||||
                    .toBe("Continuing will cause the loss of any unsaved changes.");
 | 
			
		||||
                // Override the policy service to prevent navigation
 | 
			
		||||
                mockPolicyService.allow.andCallFake(function (category, object, context, callback) {
 | 
			
		||||
                    callback(errorMessage);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                // Should have some warning message here now
 | 
			
		||||
                expect(controller.getUnloadWarning()).toEqual(errorMessage);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("cleans up on destroy", function () {
 | 
			
		||||
                expect(mockScope.$on)
 | 
			
		||||
                    .toHaveBeenCalledWith("$destroy", jasmine.any(Function));
 | 
			
		||||
 | 
			
		||||
                mockScope.$on.mostRecentCall.args[1]();
 | 
			
		||||
 | 
			
		||||
                expect(mockEditorCapability.finish).toHaveBeenCalled();
 | 
			
		||||
                expect(removeCheck).toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("sets the active view from query parameters", function () {
 | 
			
		||||
                var testViews = [
 | 
			
		||||
                        { key: 'abc' },
 | 
			
		||||
                        { key: 'def', someKey: 'some value' },
 | 
			
		||||
                        { key: 'xyz' }
 | 
			
		||||
                    ];
 | 
			
		||||
 | 
			
		||||
                mockObject.useCapability.andCallFake(function (c) {
 | 
			
		||||
                    return (c === 'view') && testViews;
 | 
			
		||||
                });
 | 
			
		||||
                mockLocation.search.andReturn({ view: 'def' });
 | 
			
		||||
 | 
			
		||||
                fireWatch('domainObject', mockObject);
 | 
			
		||||
                expect(mockScope.representation.selected)
 | 
			
		||||
                    .toEqual(testViews[1]);
 | 
			
		||||
            });
 | 
			
		||||
@@ -31,7 +31,9 @@ define(
 | 
			
		||||
            var mockTypeService,
 | 
			
		||||
                mockDialogService,
 | 
			
		||||
                mockPolicyService,
 | 
			
		||||
                mockTypeMap,
 | 
			
		||||
                mockCreationPolicy,
 | 
			
		||||
                mockCompositionPolicy,
 | 
			
		||||
                mockPolicyMap = {},
 | 
			
		||||
                mockTypes,
 | 
			
		||||
                mockDomainObject,
 | 
			
		||||
                mockQ,
 | 
			
		||||
@@ -53,33 +55,49 @@ define(
 | 
			
		||||
                );
 | 
			
		||||
                mockType.hasFeature.andReturn(true);
 | 
			
		||||
                mockType.getName.andReturn(name);
 | 
			
		||||
                mockType.getKey.andReturn(name);
 | 
			
		||||
                return mockType;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockTypeService = jasmine.createSpyObj(
 | 
			
		||||
                    "typeService",
 | 
			
		||||
                    ["getType"]
 | 
			
		||||
                    ["listTypes"]
 | 
			
		||||
                );
 | 
			
		||||
                mockDialogService = jasmine.createSpyObj(
 | 
			
		||||
                    "dialogService",
 | 
			
		||||
                    ["getUserInput"]
 | 
			
		||||
                );
 | 
			
		||||
                mockPolicyService = jasmine.createSpyObj(
 | 
			
		||||
                    "policyService",
 | 
			
		||||
                    ["allow"]
 | 
			
		||||
                );
 | 
			
		||||
                mockDialogService = {};
 | 
			
		||||
                mockPolicyService = {};
 | 
			
		||||
                mockDomainObject = {};
 | 
			
		||||
 | 
			
		||||
                mockTypes = [
 | 
			
		||||
                    "timeline",
 | 
			
		||||
                    "activity",
 | 
			
		||||
                    "other"
 | 
			
		||||
                ].map(createMockType);
 | 
			
		||||
                mockTypeMap = {};
 | 
			
		||||
                mockDomainObject = jasmine.createSpyObj(
 | 
			
		||||
                    "domainObject",
 | 
			
		||||
                    ["getCapability"]
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                //Mocking getCapability because AddActionProvider uses the
 | 
			
		||||
                // type capability of the destination object.
 | 
			
		||||
                mockDomainObject.getCapability.andReturn({});
 | 
			
		||||
 | 
			
		||||
                mockTypes = ["A", "B", "C"].map(createMockType);
 | 
			
		||||
 | 
			
		||||
                mockTypes.forEach(function (type) {
 | 
			
		||||
                    mockTypeMap[type.getKey()] = type;
 | 
			
		||||
                    mockPolicyMap[type.getName()] = true;
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                mockTypeService.getType.andCallFake(function (key) {
 | 
			
		||||
                    return mockTypeMap[key];
 | 
			
		||||
                });
 | 
			
		||||
                mockCreationPolicy = function (type) {
 | 
			
		||||
                    return mockPolicyMap[type.getName()];
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                mockCompositionPolicy = function () {
 | 
			
		||||
                    return true;
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                mockPolicyService.allow.andReturn(true);
 | 
			
		||||
 | 
			
		||||
                mockTypeService.listTypes.andReturn(mockTypes);
 | 
			
		||||
 | 
			
		||||
                provider = new AddActionProvider(
 | 
			
		||||
                    mockQ,
 | 
			
		||||
@@ -89,16 +107,29 @@ define(
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("provides actions for timeline and activity", function () {
 | 
			
		||||
                var actions = provider.getActions({
 | 
			
		||||
            it("checks for creatability", function () {
 | 
			
		||||
                provider.getActions({
 | 
			
		||||
                    key: "add",
 | 
			
		||||
                    domainObject: mockDomainObject
 | 
			
		||||
                });
 | 
			
		||||
                expect(actions.length).toBe(2);
 | 
			
		||||
                expect(actions[0].metadata.type).toBe('timeline');
 | 
			
		||||
                expect(actions[1].metadata.type).toBe('activity');
 | 
			
		||||
 | 
			
		||||
                // Make sure it was creation which was used to check
 | 
			
		||||
                expect(mockPolicyService.allow)
 | 
			
		||||
                    .toHaveBeenCalledWith("creation", mockTypes[0]);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("checks for composability of type", function () {
 | 
			
		||||
                provider.getActions({
 | 
			
		||||
                    key: "add",
 | 
			
		||||
                    domainObject: mockDomainObject
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                expect(mockPolicyService.allow).toHaveBeenCalledWith(
 | 
			
		||||
                    "composition",
 | 
			
		||||
                    jasmine.any(Object),
 | 
			
		||||
                    jasmine.any(Object)
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                expect(mockDomainObject.getCapability).toHaveBeenCalledWith('type');
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -138,7 +138,7 @@ define(
 | 
			
		||||
 | 
			
		||||
                expect(metadata.name).toEqual("Test");
 | 
			
		||||
                expect(metadata.description).toEqual("a test type");
 | 
			
		||||
                expect(metadata.cssClass).toEqual("icon-telemetry");
 | 
			
		||||
                expect(metadata.cssclass).toEqual("icon-telemetry");
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            describe("the perform function", function () {
 | 
			
		||||
 
 | 
			
		||||
@@ -175,7 +175,7 @@ define(
 | 
			
		||||
                expect(mockPolicyService.allow).toHaveBeenCalledWith(
 | 
			
		||||
                    'composition',
 | 
			
		||||
                    mockOtherType,
 | 
			
		||||
                    mockDomainObject
 | 
			
		||||
                    mockType
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										114
									
								
								platform/commonUI/edit/test/directives/MCTBeforeUnloadSpec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								platform/commonUI/edit/test/directives/MCTBeforeUnloadSpec.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,114 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2016, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT 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 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(
 | 
			
		||||
    ["../../src/directives/MCTBeforeUnload"],
 | 
			
		||||
    function (MCTBeforeUnload) {
 | 
			
		||||
 | 
			
		||||
        describe("The mct-before-unload directive", function () {
 | 
			
		||||
            var mockWindow,
 | 
			
		||||
                mockScope,
 | 
			
		||||
                testAttrs,
 | 
			
		||||
                mockEvent,
 | 
			
		||||
                directive;
 | 
			
		||||
 | 
			
		||||
            function fireListener(eventType, value) {
 | 
			
		||||
                mockScope.$on.calls.forEach(function (call) {
 | 
			
		||||
                    if (call.args[0] === eventType) {
 | 
			
		||||
                        call.args[1](value);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                mockWindow = jasmine.createSpyObj("$window", ['confirm']);
 | 
			
		||||
                mockScope = jasmine.createSpyObj("$scope", ['$eval', '$on']);
 | 
			
		||||
                testAttrs = { mctBeforeUnload: "someExpression" };
 | 
			
		||||
                mockEvent = jasmine.createSpyObj("event", ["preventDefault"]);
 | 
			
		||||
                directive = new MCTBeforeUnload(mockWindow);
 | 
			
		||||
                directive.link(mockScope, {}, testAttrs);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("can be used only as an attribute", function () {
 | 
			
		||||
                expect(directive.restrict).toEqual('A');
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("listens for beforeunload", function () {
 | 
			
		||||
                expect(mockWindow.onbeforeunload).toEqual(jasmine.any(Function));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("listens for route changes", function () {
 | 
			
		||||
                expect(mockScope.$on).toHaveBeenCalledWith(
 | 
			
		||||
                    "$locationChangeStart",
 | 
			
		||||
                    jasmine.any(Function)
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("listens for its scope's destroy event", function () {
 | 
			
		||||
                expect(mockScope.$on).toHaveBeenCalledWith(
 | 
			
		||||
                    "$destroy",
 | 
			
		||||
                    jasmine.any(Function)
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("uses result of evaluated expression as a warning", function () {
 | 
			
		||||
                mockScope.$eval.andReturn(undefined);
 | 
			
		||||
                expect(mockWindow.onbeforeunload(mockEvent)).toBeUndefined();
 | 
			
		||||
                mockScope.$eval.andReturn("some message");
 | 
			
		||||
                expect(mockWindow.onbeforeunload(mockEvent)).toEqual("some message");
 | 
			
		||||
                // Verify that the right expression was evaluated
 | 
			
		||||
                expect(mockScope.$eval).toHaveBeenCalledWith(testAttrs.mctBeforeUnload);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("confirms route changes", function () {
 | 
			
		||||
                // First, try with no unsaved changes;
 | 
			
		||||
                // should not confirm or preventDefault
 | 
			
		||||
                mockScope.$eval.andReturn(undefined);
 | 
			
		||||
                fireListener("$locationChangeStart", mockEvent);
 | 
			
		||||
                expect(mockWindow.confirm).not.toHaveBeenCalled();
 | 
			
		||||
                expect(mockEvent.preventDefault).not.toHaveBeenCalled();
 | 
			
		||||
 | 
			
		||||
                // Next, try with unsaved changes that the user confirms;
 | 
			
		||||
                // should prompt, but not preventDefault
 | 
			
		||||
                mockScope.$eval.andReturn("some message");
 | 
			
		||||
                mockWindow.confirm.andReturn(true);
 | 
			
		||||
                fireListener("$locationChangeStart", mockEvent);
 | 
			
		||||
                expect(mockWindow.confirm).toHaveBeenCalledWith("some message");
 | 
			
		||||
                expect(mockEvent.preventDefault).not.toHaveBeenCalled();
 | 
			
		||||
 | 
			
		||||
                // Finally, act as if the user said no to this dialog;
 | 
			
		||||
                // this should preventDefault on the location change.
 | 
			
		||||
                mockWindow.confirm.andReturn(false);
 | 
			
		||||
                fireListener("$locationChangeStart", mockEvent);
 | 
			
		||||
                expect(mockWindow.confirm).toHaveBeenCalledWith("some message");
 | 
			
		||||
                expect(mockEvent.preventDefault).toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("cleans up listeners when destroyed", function () {
 | 
			
		||||
                fireListener("$destroy", mockEvent);
 | 
			
		||||
                expect(mockWindow.onbeforeunload).toBeUndefined();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -20,70 +20,103 @@
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
 | 
			
		||||
define([
 | 
			
		||||
    '../../src/representers/EditRepresenter'
 | 
			
		||||
], function (
 | 
			
		||||
    EditRepresenter
 | 
			
		||||
) {
 | 
			
		||||
    describe('EditRepresenter', function () {
 | 
			
		||||
        var $log,
 | 
			
		||||
            $scope,
 | 
			
		||||
            representer;
 | 
			
		||||
define(
 | 
			
		||||
    ["../../src/representers/EditRepresenter"],
 | 
			
		||||
    function (EditRepresenter) {
 | 
			
		||||
 | 
			
		||||
        describe("The Edit mode representer", function () {
 | 
			
		||||
            var mockQ,
 | 
			
		||||
                mockLog,
 | 
			
		||||
                mockScope,
 | 
			
		||||
                testRepresentation,
 | 
			
		||||
                mockDomainObject,
 | 
			
		||||
                mockStatusCapability,
 | 
			
		||||
                mockEditorCapability,
 | 
			
		||||
                mockCapabilities,
 | 
			
		||||
                representer;
 | 
			
		||||
 | 
			
		||||
        beforeEach(function () {
 | 
			
		||||
            $log = jasmine.createSpyObj('$log', ['debug']);
 | 
			
		||||
            $scope = {};
 | 
			
		||||
            representer = new EditRepresenter($log, $scope);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('injects a commit function in scope', function () {
 | 
			
		||||
            expect($scope.commit).toEqual(jasmine.any(Function));
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        describe('representation', function () {
 | 
			
		||||
            var domainObject,
 | 
			
		||||
                representation;
 | 
			
		||||
            function mockPromise(value) {
 | 
			
		||||
                return {
 | 
			
		||||
                    then: function (callback) {
 | 
			
		||||
                        return mockPromise(callback(value));
 | 
			
		||||
                    }
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            beforeEach(function () {
 | 
			
		||||
                domainObject = jasmine.createSpyObj('domainObject', [
 | 
			
		||||
                    'getId',
 | 
			
		||||
                    'getModel',
 | 
			
		||||
                    'useCapability'
 | 
			
		||||
                mockQ = { when: mockPromise };
 | 
			
		||||
                mockLog = jasmine.createSpyObj("$log", ["info", "debug"]);
 | 
			
		||||
                mockScope = jasmine.createSpyObj("$scope", ["$watch", "$on"]);
 | 
			
		||||
                testRepresentation = { key: "test" };
 | 
			
		||||
                mockDomainObject = jasmine.createSpyObj("domainObject", [
 | 
			
		||||
                    "getId",
 | 
			
		||||
                    "getModel",
 | 
			
		||||
                    "getCapability",
 | 
			
		||||
                    "useCapability",
 | 
			
		||||
                    "hasCapability"
 | 
			
		||||
                ]);
 | 
			
		||||
                mockStatusCapability =
 | 
			
		||||
                    jasmine.createSpyObj("statusCapability", ["listen"]);
 | 
			
		||||
                mockEditorCapability =
 | 
			
		||||
                    jasmine.createSpyObj("editorCapability", ["isEditContextRoot"]);
 | 
			
		||||
 | 
			
		||||
                domainObject.getId.andReturn('anId');
 | 
			
		||||
                domainObject.getModel.andReturn({name: 'anObject'});
 | 
			
		||||
 | 
			
		||||
                representation = {
 | 
			
		||||
                    key: 'someRepresentation'
 | 
			
		||||
                mockCapabilities = {
 | 
			
		||||
                    'status': mockStatusCapability,
 | 
			
		||||
                    'editor': mockEditorCapability
 | 
			
		||||
                };
 | 
			
		||||
                $scope.model = {name: 'anotherName'};
 | 
			
		||||
                $scope.configuration = {some: 'config'};
 | 
			
		||||
                representer.represent(representation, domainObject);
 | 
			
		||||
 | 
			
		||||
                mockDomainObject.getModel.andReturn({});
 | 
			
		||||
                mockDomainObject.hasCapability.andReturn(true);
 | 
			
		||||
                mockDomainObject.useCapability.andReturn(true);
 | 
			
		||||
                mockDomainObject.getCapability.andCallFake(function (capability) {
 | 
			
		||||
                    return mockCapabilities[capability];
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                representer = new EditRepresenter(mockQ, mockLog, mockScope);
 | 
			
		||||
                representer.represent(testRepresentation, mockDomainObject);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it('logs a message when commiting', function () {
 | 
			
		||||
                $scope.commit('Test Message');
 | 
			
		||||
                expect($log.debug)
 | 
			
		||||
                    .toHaveBeenCalledWith('Committing  anObject (anId): Test Message');
 | 
			
		||||
            it("provides a commit method in scope", function () {
 | 
			
		||||
                expect(mockScope.commit).toEqual(jasmine.any(Function));
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it('mutates the object when committing', function () {
 | 
			
		||||
                $scope.commit('Test Message');
 | 
			
		||||
 | 
			
		||||
                expect(domainObject.useCapability)
 | 
			
		||||
                    .toHaveBeenCalledWith('mutation', jasmine.any(Function));
 | 
			
		||||
 | 
			
		||||
                var mutateValue = domainObject.useCapability.calls[0].args[1]();
 | 
			
		||||
 | 
			
		||||
                expect(mutateValue.configuration.someRepresentation)
 | 
			
		||||
                    .toEqual({some: 'config'});
 | 
			
		||||
                expect(mutateValue.name).toEqual('anotherName');
 | 
			
		||||
            it("Sets edit view template on edit mode", function () {
 | 
			
		||||
                mockStatusCapability.listen.mostRecentCall.args[0](['editing']);
 | 
			
		||||
                mockEditorCapability.isEditContextRoot.andReturn(true);
 | 
			
		||||
                expect(mockScope.viewObjectTemplate).toEqual('edit-object');
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("Cleans up listeners on scope destroy", function () {
 | 
			
		||||
                representer.listenHandle = jasmine.createSpy('listen');
 | 
			
		||||
                mockScope.$on.mostRecentCall.args[1]();
 | 
			
		||||
                expect(representer.listenHandle).toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("mutates upon observed changes", function () {
 | 
			
		||||
                mockScope.model = { someKey: "some value" };
 | 
			
		||||
                mockScope.configuration = { someConfiguration: "something" };
 | 
			
		||||
 | 
			
		||||
                mockScope.commit("Some message");
 | 
			
		||||
 | 
			
		||||
                // Should have mutated the object...
 | 
			
		||||
                expect(mockDomainObject.useCapability).toHaveBeenCalledWith(
 | 
			
		||||
                    "mutation",
 | 
			
		||||
                    jasmine.any(Function)
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                // Finally, check that the provided mutation function
 | 
			
		||||
                // includes both model and configuration
 | 
			
		||||
                expect(
 | 
			
		||||
                    mockDomainObject.useCapability.mostRecentCall.args[1]()
 | 
			
		||||
                ).toEqual({
 | 
			
		||||
                    someKey: "some value",
 | 
			
		||||
                    configuration: {
 | 
			
		||||
                        test: { someConfiguration: "something" }
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
{
 | 
			
		||||
  "metadata": {
 | 
			
		||||
    "name": "openmct-symbols-16px",
 | 
			
		||||
    "lastOpened": 1481575258437,
 | 
			
		||||
    "created": 1481575255265
 | 
			
		||||
    "lastOpened": 1480112601593,
 | 
			
		||||
    "created": 1480112580248
 | 
			
		||||
  },
 | 
			
		||||
  "iconSets": [
 | 
			
		||||
    {
 | 
			
		||||
@@ -535,15 +535,15 @@
 | 
			
		||||
        {
 | 
			
		||||
          "order": 21,
 | 
			
		||||
          "prevSize": 24,
 | 
			
		||||
          "name": "icon-resync",
 | 
			
		||||
          "name": "icon-x-in-circle",
 | 
			
		||||
          "id": 16,
 | 
			
		||||
          "code": 921654,
 | 
			
		||||
          "tempChar": ""
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "order": 121,
 | 
			
		||||
          "order": 117,
 | 
			
		||||
          "id": 103,
 | 
			
		||||
          "name": "icon-x-in-circle",
 | 
			
		||||
          "name": "icon-brightness",
 | 
			
		||||
          "prevSize": 24,
 | 
			
		||||
          "code": 921656,
 | 
			
		||||
          "tempChar": ""
 | 
			
		||||
@@ -551,7 +551,7 @@
 | 
			
		||||
        {
 | 
			
		||||
          "order": 118,
 | 
			
		||||
          "id": 102,
 | 
			
		||||
          "name": "icon-brightness",
 | 
			
		||||
          "name": "icon-contrast",
 | 
			
		||||
          "prevSize": 24,
 | 
			
		||||
          "code": 921657,
 | 
			
		||||
          "tempChar": ""
 | 
			
		||||
@@ -559,7 +559,7 @@
 | 
			
		||||
        {
 | 
			
		||||
          "order": 119,
 | 
			
		||||
          "id": 104,
 | 
			
		||||
          "name": "icon-contrast",
 | 
			
		||||
          "name": "icon-reset",
 | 
			
		||||
          "prevSize": 24,
 | 
			
		||||
          "code": 921664,
 | 
			
		||||
          "tempChar": ""
 | 
			
		||||
@@ -567,7 +567,7 @@
 | 
			
		||||
        {
 | 
			
		||||
          "order": 120,
 | 
			
		||||
          "id": 105,
 | 
			
		||||
          "name": "icon-reset",
 | 
			
		||||
          "name": "icon-resync",
 | 
			
		||||
          "prevSize": 24,
 | 
			
		||||
          "code": 921655,
 | 
			
		||||
          "tempChar": ""
 | 
			
		||||
 
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							@@ -72,11 +72,11 @@
 | 
			
		||||
<glyph unicode="󡀳" glyph-name="icon-thumbs-strip" d="M448 578c0-35.2-28.8-64-64-64h-320c-35.2 0-64 28.8-64 64v320c0 35.2 28.8 64 64 64h320c35.2 0 64-28.8 64-64v-320zM1024 578c0-35.2-28.8-64-64-64h-320c-35.2 0-64 28.8-64 64v320c0 35.2 28.8 64 64 64h320c35.2 0 64-28.8 64-64v-320zM448 2c0-35.2-28.8-64-64-64h-320c-35.2 0-64 28.8-64 64v320c0 35.2 28.8 64 64 64h320c35.2 0 64-28.8 64-64v-320zM1024 2c0-35.2-28.8-64-64-64h-320c-35.2 0-64 28.8-64 64v320c0 35.2 28.8 64 64 64h320c35.2 0 64-28.8 64-64v-320z" />
 | 
			
		||||
<glyph unicode="󡀴" glyph-name="icon-two-parts-both" d="M896 960h-768c-70.4 0-128-57.6-128-128v-768c0-70.4 57.6-128 128-128h768c70.4 0 128 57.6 128 128v768c0 70.4-57.6 128-128 128zM128 832h320v-768h-320v768zM896 64h-320v768h320v-768z" />
 | 
			
		||||
<glyph unicode="󡀵" glyph-name="icon-two-parts-one-only" d="M896 960h-768c-70.4 0-128-57.6-128-128v-768c0-70.4 57.6-128 128-128h768c70.4 0 128 57.6 128 128v768c0 70.4-57.6 128-128 128zM896 64h-320v768h320v-768z" />
 | 
			
		||||
<glyph unicode="󡀶" glyph-name="icon-resync" d="M795.2 795.2c-79.8 65.2-178.8 100.8-283.2 100.8-119.6 0-232.2-46.6-316.8-131.2-69.4-69.4-113.2-157.4-126.6-252.8h130c29.6 145.8 158.8 256 313.4 256 72 0 138.4-23.8 192-64l-176-176h432v432l-164.8-164.8zM512 128c-72 0-138.4 23.8-192 64l176 176h-432v-432l164.8 164.8c79.8-65.2 178.8-100.8 283.2-100.8 119.6 0 232.2 46.6 316.8 131.2 69.4 69.4 113.2 157.4 126.6 252.8h-130c-29.6-145.8-158.8-256-313.4-256z" />
 | 
			
		||||
<glyph unicode="󡀷" glyph-name="icon-reset" d="M460.8 499.2l-187.8 187.8c57.2 42.8 128 68.2 204.8 68.2 188.2 0 341.6-153.2 341.6-341.4s-153.2-341.2-341.4-341.2c-165 0-302.8 117.6-334.6 273h-138.4c14.2-101.8 61-195.6 135-269.6 90.2-90.2 210.4-140 338-140s247.6 49.8 338 140 140 210.4 140 338-49.8 247.6-140 338-210.4 140-338 140c-111.4 0-217-38-302-107.6l-176 175.6v-460.8h460.8z" />
 | 
			
		||||
<glyph unicode="󡀸" glyph-name="icon-x-in-circle" d="M512 960c-282.8 0-512-229.2-512-512s229.2-512 512-512 512 229.2 512 512-229.2 512-512 512zM832 256l-128-128-192 192-192-192-128 128 192 192-192 192 128 128 192-192 192 192 128-128-192-192 192-192z" />
 | 
			
		||||
<glyph unicode="󡀹" glyph-name="icon-brightness" d="M253.414 641.939l-155.172 116.384c-50.233-66.209-85.127-146.713-97.91-234.39l191.586-30.216c8.145 56.552 29.998 106.879 62.068 149.006zM191.98 402.283l-191.919-27.434c13.115-90.459 48.009-170.963 99.174-238.453l154.18 117.665c-31.476 41.347-53.309 91.675-61.231 146.504zM466.283 768.020l-27.434 191.919c-90.459-13.115-170.963-48.009-238.453-99.174l117.665-154.18c41.347 31.476 91.675 53.309 146.504 61.231zM822.323 861.758c-66.209 50.233-146.713 85.127-234.39 97.91l-30.216-191.586c56.552-8.145 106.879-29.998 149.006-62.068zM832.020 493.717l191.919 27.434c-13.115 90.459-48.009 170.963-99.174 238.453l-154.18-117.665c31.476-41.347 53.309-91.675 61.231-146.504zM201.677 34.242c66.209-50.233 146.713-85.127 234.39-97.91l30.216 191.586c-56.552 8.145-106.879 29.998-149.006 62.068zM770.586 254.061l155.131-116.343c50.233 66.209 85.127 146.713 97.91 234.39l-191.586 30.216c-8.125-56.564-29.966-106.906-62.028-149.049zM557.717 127.98l27.434-191.919c90.459 13.115 170.963 48.009 238.453 99.174l-117.665 154.18c-41.347-31.476-91.675-53.309-146.504-61.231zM770.586 448c0-142.813-115.773-258.586-258.586-258.586s-258.586 115.773-258.586 258.586c0 142.813 115.773 258.586 258.586 258.586s258.586-115.773 258.586-258.586z" />
 | 
			
		||||
<glyph unicode="󡁀" glyph-name="icon-contrast" d="M512 960c-282.78 0-512-229.24-512-512s229.22-512 512-512 512 229.24 512 512-229.22 512-512 512zM783.52 176.48c-69.111-69.481-164.785-112.481-270.502-112.481-0.358 0-0.716 0-1.074 0.001l0.055 768c212.070-0.010 383.982-171.929 383.982-384 0-106.034-42.977-202.031-112.462-271.52z" />
 | 
			
		||||
<glyph unicode="󡀶" glyph-name="icon-x-in-circle" d="M795.2 795.2c-79.8 65.2-178.8 100.8-283.2 100.8-119.6 0-232.2-46.6-316.8-131.2-69.4-69.4-113.2-157.4-126.6-252.8h130c29.6 145.8 158.8 256 313.4 256 72 0 138.4-23.8 192-64l-176-176h432v432l-164.8-164.8zM512 128c-72 0-138.4 23.8-192 64l176 176h-432v-432l164.8 164.8c79.8-65.2 178.8-100.8 283.2-100.8 119.6 0 232.2 46.6 316.8 131.2 69.4 69.4 113.2 157.4 126.6 252.8h-130c-29.6-145.8-158.8-256-313.4-256z" />
 | 
			
		||||
<glyph unicode="󡀷" glyph-name="icon-resync" d="M460.8 499.2l-187.8 187.8c57.2 42.8 128 68.2 204.8 68.2 188.2 0 341.6-153.2 341.6-341.4s-153.2-341.2-341.4-341.2c-165 0-302.8 117.6-334.6 273h-138.4c14.2-101.8 61-195.6 135-269.6 90.2-90.2 210.4-140 338-140s247.6 49.8 338 140 140 210.4 140 338-49.8 247.6-140 338-210.4 140-338 140c-111.4 0-217-38-302-107.6l-176 175.6v-460.8h460.8z" />
 | 
			
		||||
<glyph unicode="󡀸" glyph-name="icon-brightness" d="M512 960c-282.8 0-512-229.2-512-512s229.2-512 512-512 512 229.2 512 512-229.2 512-512 512zM832 256l-128-128-192 192-192-192-128 128 192 192-192 192 128 128 192-192 192 192 128-128-192-192 192-192z" />
 | 
			
		||||
<glyph unicode="󡀹" glyph-name="icon-contrast" d="M253.414 641.939l-155.172 116.384c-50.233-66.209-85.127-146.713-97.91-234.39l191.586-30.216c8.145 56.552 29.998 106.879 62.068 149.006zM191.98 402.283l-191.919-27.434c13.115-90.459 48.009-170.963 99.174-238.453l154.18 117.665c-31.476 41.347-53.309 91.675-61.231 146.504zM466.283 768.020l-27.434 191.919c-90.459-13.115-170.963-48.009-238.453-99.174l117.665-154.18c41.347 31.476 91.675 53.309 146.504 61.231zM822.323 861.758c-66.209 50.233-146.713 85.127-234.39 97.91l-30.216-191.586c56.552-8.145 106.879-29.998 149.006-62.068zM832.020 493.717l191.919 27.434c-13.115 90.459-48.009 170.963-99.174 238.453l-154.18-117.665c31.476-41.347 53.309-91.675 61.231-146.504zM201.677 34.242c66.209-50.233 146.713-85.127 234.39-97.91l30.216 191.586c-56.552 8.145-106.879 29.998-149.006 62.068zM770.586 254.061l155.131-116.343c50.233 66.209 85.127 146.713 97.91 234.39l-191.586 30.216c-8.125-56.564-29.966-106.906-62.028-149.049zM557.717 127.98l27.434-191.919c90.459 13.115 170.963 48.009 238.453 99.174l-117.665 154.18c-41.347-31.476-91.675-53.309-146.504-61.231zM770.586 448c0-142.813-115.773-258.586-258.586-258.586s-258.586 115.773-258.586 258.586c0 142.813 115.773 258.586 258.586 258.586s258.586-115.773 258.586-258.586z" />
 | 
			
		||||
<glyph unicode="󡁀" glyph-name="icon-reset" d="M512 960c-282.78 0-512-229.24-512-512s229.22-512 512-512 512 229.24 512 512-229.22 512-512 512zM783.52 176.48c-69.111-69.481-164.785-112.481-270.502-112.481-0.358 0-0.716 0-1.074 0.001l0.055 768c212.070-0.010 383.982-171.929 383.982-384 0-106.034-42.977-202.031-112.462-271.52z" />
 | 
			
		||||
<glyph unicode="󡄀" glyph-name="icon-activity" d="M576 896h-256l320-320h-290.256c-44.264 76.516-126.99 128-221.744 128h-128v-512h128c94.754 0 177.48 51.484 221.744 128h290.256l-320-320h256l448 448-448 448z" />
 | 
			
		||||
<glyph unicode="󡄁" glyph-name="icon-activity-mode" d="M512 960c-214.866 0-398.786-132.372-474.744-320h90.744c56.86 0 107.938-24.724 143.094-64h240.906l-192 192h256l320-320-320-320h-256l192 192h-240.906c-35.156-39.276-86.234-64-143.094-64h-90.744c75.958-187.628 259.878-320 474.744-320 282.77 0 512 229.23 512 512s-229.23 512-512 512z" />
 | 
			
		||||
<glyph unicode="󡄂" glyph-name="icon-autoflow-tabular" d="M192 960c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h64v1024h-64zM384 960h256v-1024h-256v1024zM832 960h-64v-704h256v512c0 105.6-86.4 192-192 192z" />
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB  | 
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -40,6 +40,7 @@ $glyph-icon-brackets: '\e929';
 | 
			
		||||
$glyph-icon-arrows-out: '\e1000';
 | 
			
		||||
$glyph-icon-arrows-right-left: '\e1001';
 | 
			
		||||
$glyph-icon-arrows-up-down: '\e1002';
 | 
			
		||||
$glyph-icon-box-with-dashed-lines: '';
 | 
			
		||||
$glyph-icon-bullet: '\e1004';
 | 
			
		||||
$glyph-icon-calendar: '\e1005';
 | 
			
		||||
$glyph-icon-chain-links: '\e1006';
 | 
			
		||||
@@ -72,11 +73,11 @@ $glyph-icon-T: '\e1032';
 | 
			
		||||
$glyph-icon-thumbs-strip: '\e1033';
 | 
			
		||||
$glyph-icon-two-parts-both: '\e1034';
 | 
			
		||||
$glyph-icon-two-parts-one-only: '\e1035';
 | 
			
		||||
$glyph-icon-resync: '\e1036';
 | 
			
		||||
$glyph-icon-x-in-circle: '\e1038';
 | 
			
		||||
$glyph-icon-brightness: '\e1039';
 | 
			
		||||
$glyph-icon-contrast: '\e1040';
 | 
			
		||||
$glyph-icon-reset: '\e1037';
 | 
			
		||||
$glyph-icon-x-in-circle: '\e1036';
 | 
			
		||||
$glyph-icon-brightness: '\e1038';
 | 
			
		||||
$glyph-icon-contrast: '\e1039';
 | 
			
		||||
$glyph-icon-reset: '\e1040';
 | 
			
		||||
$glyph-icon-resync: '\e1037';
 | 
			
		||||
$glyph-icon-activity: '\e1100';
 | 
			
		||||
$glyph-icon-activity-mode: '\e1101';
 | 
			
		||||
$glyph-icon-autoflow-tabular: '\e1102';
 | 
			
		||||
 
 | 
			
		||||
@@ -76,18 +76,17 @@
 | 
			
		||||
                &:not(.first) {
 | 
			
		||||
                    border-top: 1px solid $colorFormLines;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            .form-row {
 | 
			
		||||
                @include align-items(center);
 | 
			
		||||
                border: none !important;
 | 
			
		||||
                margin-bottom: 0 !important;
 | 
			
		||||
                padding: $interiorMarginSm 0;
 | 
			
		||||
                .label {
 | 
			
		||||
                    min-width: 80px;
 | 
			
		||||
                }
 | 
			
		||||
                input[type='text'],
 | 
			
		||||
                input[type='search'] {
 | 
			
		||||
                    width: 100%;
 | 
			
		||||
                .form-row {
 | 
			
		||||
                    @include align-items(center);
 | 
			
		||||
                    border: none;
 | 
			
		||||
                    padding: $interiorMarginSm 0;
 | 
			
		||||
                    .label {
 | 
			
		||||
                        min-width: 80px;
 | 
			
		||||
                    }
 | 
			
		||||
                    input[type='text'],
 | 
			
		||||
                    input[type='search'] {
 | 
			
		||||
                        width: 100%;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,9 @@
 | 
			
		||||
        height: 70%;
 | 
			
		||||
        width: 50%;
 | 
			
		||||
        min-height: 300px;
 | 
			
		||||
        max-height: 800px;
 | 
			
		||||
        min-width: 600px;
 | 
			
		||||
        max-width: 1000px;
 | 
			
		||||
        z-index: 101;
 | 
			
		||||
        > .contents {
 | 
			
		||||
            $m: $overlayMargin;
 | 
			
		||||
@@ -139,4 +141,4 @@
 | 
			
		||||
    $h: 225px;
 | 
			
		||||
    min-height: $h;
 | 
			
		||||
    height: $h;
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -19,7 +19,6 @@
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
$output-bourbon-deprecation-warnings: false;
 | 
			
		||||
@import "bourbon";
 | 
			
		||||
@import "logo-and-bg";
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@
 | 
			
		||||
 this source code distribution or the Licensing information page available
 | 
			
		||||
 at runtime from the About dialog for additional information.
 | 
			
		||||
-->
 | 
			
		||||
<a class="s-button key-{{parameters.action.getMetadata().key}} {{parameters.action.getMetadata().cssClass}}"
 | 
			
		||||
<a class="s-button key-{{parameters.action.getMetadata().key}} {{parameters.action.getMetadata().cssclass}}"
 | 
			
		||||
   ng-class="{ labeled: parameters.labeled }"
 | 
			
		||||
   title="{{parameters.action.getMetadata().description}}"
 | 
			
		||||
   ng-click="parameters.action.perform()">
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@
 | 
			
		||||
 at runtime from the About dialog for additional information.
 | 
			
		||||
-->
 | 
			
		||||
<span ng-controller="ViewSwitcherController">
 | 
			
		||||
    <div class="view-switcher menu-element s-menu-button {{ngModel.selected.cssClass}}"
 | 
			
		||||
    <div class="view-switcher menu-element s-menu-button {{ngModel.selected.cssclass}}"
 | 
			
		||||
	    ng-if="view.length > 1"
 | 
			
		||||
	    ng-controller="ClickAwayController as toggle">
 | 
			
		||||
 | 
			
		||||
@@ -33,7 +33,7 @@
 | 
			
		||||
            <ul>
 | 
			
		||||
                <li ng-repeat="option in view"
 | 
			
		||||
                    ng-click="ngModel.selected = option; toggle.setState(false)"
 | 
			
		||||
                    class="{{option.cssClass}}">
 | 
			
		||||
                    class="{{option.cssclass}}">
 | 
			
		||||
                    {{option.name}}
 | 
			
		||||
                </li>
 | 
			
		||||
            </ul>
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@
 | 
			
		||||
            <li ng-repeat="menuAction in menuActions"
 | 
			
		||||
                ng-click="menuAction.perform()"
 | 
			
		||||
                title="{{menuAction.getMetadata().description}}"
 | 
			
		||||
                class="{{menuAction.getMetadata().cssClass}}">
 | 
			
		||||
                class="{{menuAction.getMetadata().cssclass}}">
 | 
			
		||||
                {{menuAction.getMetadata().name}}
 | 
			
		||||
            </li>
 | 
			
		||||
        </ul>
 | 
			
		||||
 
 | 
			
		||||
@@ -19,9 +19,6 @@
 | 
			
		||||
 this source code distribution or the Licensing information page available
 | 
			
		||||
 at runtime from the About dialog for additional information.
 | 
			
		||||
-->
 | 
			
		||||
<mct-tree root-object="domainObject"
 | 
			
		||||
    selected-object="ngModel.selectedObject"
 | 
			
		||||
    on-selection="ngModel.onSelection"
 | 
			
		||||
    allow-selection="ngModel.allowSelection">
 | 
			
		||||
<mct-tree mct-object="domainObject" mct-model="ngModel.selectedObject">
 | 
			
		||||
</mct-tree>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -26,54 +26,25 @@ define([
 | 
			
		||||
], function (angular, TreeView) {
 | 
			
		||||
    function MCTTree(gestureService) {
 | 
			
		||||
        function link(scope, element) {
 | 
			
		||||
            if (!scope.allowSelection) {
 | 
			
		||||
                scope.allowSelection = function () {
 | 
			
		||||
                    return true;
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
            if (!scope.onSelection) {
 | 
			
		||||
                scope.onSelection = function () {};
 | 
			
		||||
            }
 | 
			
		||||
            var currentSelection = scope.selectedObject;
 | 
			
		||||
            var treeView = new TreeView(gestureService);
 | 
			
		||||
 | 
			
		||||
            function setSelection(domainObject, event) {
 | 
			
		||||
                if (currentSelection === domainObject) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                if (!scope.allowSelection(domainObject)) {
 | 
			
		||||
                    treeView.value(currentSelection);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                currentSelection = domainObject;
 | 
			
		||||
                scope.onSelection(domainObject);
 | 
			
		||||
                scope.selectedObject = domainObject;
 | 
			
		||||
                if (event && event instanceof MouseEvent) {
 | 
			
		||||
                    scope.$apply();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var unobserve = treeView.observe(setSelection);
 | 
			
		||||
            var treeView = new TreeView(gestureService),
 | 
			
		||||
                unobserve = treeView.observe(function (domainObject) {
 | 
			
		||||
                    if (scope.mctModel !== domainObject) {
 | 
			
		||||
                        scope.mctModel = domainObject;
 | 
			
		||||
                        scope.$apply();
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            element.append(angular.element(treeView.elements()));
 | 
			
		||||
 | 
			
		||||
            scope.$watch('selectedObject', function (object) {
 | 
			
		||||
                currentSelection = object;
 | 
			
		||||
                treeView.value(object);
 | 
			
		||||
            });
 | 
			
		||||
            scope.$watch('rootObject', treeView.model.bind(treeView));
 | 
			
		||||
            scope.$watch('mctModel', treeView.value.bind(treeView));
 | 
			
		||||
            scope.$watch('mctObject', treeView.model.bind(treeView));
 | 
			
		||||
            scope.$on('$destroy', unobserve);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            restrict: "E",
 | 
			
		||||
            link: link,
 | 
			
		||||
            scope: {
 | 
			
		||||
                rootObject: "=",
 | 
			
		||||
                selectedObject: "=",
 | 
			
		||||
                onSelection: "=?",
 | 
			
		||||
                allowSelection: "=?"
 | 
			
		||||
            }
 | 
			
		||||
            scope: { mctObject: "=", mctModel: "=" }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -49,8 +49,8 @@ define([
 | 
			
		||||
 | 
			
		||||
        this.labelView = new TreeLabelView(gestureService);
 | 
			
		||||
 | 
			
		||||
        $(this.labelView.elements()).on('click', function (event) {
 | 
			
		||||
            selectFn(this.activeObject, event);
 | 
			
		||||
        $(this.labelView.elements()).on('click', function () {
 | 
			
		||||
            selectFn(this.activeObject);
 | 
			
		||||
        }.bind(this));
 | 
			
		||||
 | 
			
		||||
        this.li.append($(nodeTemplate));
 | 
			
		||||
 
 | 
			
		||||
@@ -109,11 +109,11 @@ define([
 | 
			
		||||
        }.bind(this));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    TreeView.prototype.value = function (domainObject, event) {
 | 
			
		||||
    TreeView.prototype.value = function (domainObject) {
 | 
			
		||||
        this.selectedObject = domainObject;
 | 
			
		||||
        this.updateNodeViewSelection();
 | 
			
		||||
        this.callbacks.forEach(function (callback) {
 | 
			
		||||
            callback(domainObject, event);
 | 
			
		||||
            callback(domainObject);
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -19,12 +19,10 @@
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
/* global console*/
 | 
			
		||||
 | 
			
		||||
define([
 | 
			
		||||
    '../../src/directives/MCTTree',
 | 
			
		||||
    '../../src/ui/TreeView'
 | 
			
		||||
], function (MCTTree, TreeView) {
 | 
			
		||||
    '../../src/directives/MCTTree'
 | 
			
		||||
], function (MCTTree) {
 | 
			
		||||
    describe("The mct-tree directive", function () {
 | 
			
		||||
        var mockParse,
 | 
			
		||||
            mockGestureService,
 | 
			
		||||
@@ -52,7 +50,6 @@ define([
 | 
			
		||||
            mockExpr = jasmine.createSpy('expr');
 | 
			
		||||
            mockExpr.assign = jasmine.createSpy('assign');
 | 
			
		||||
            mockParse.andReturn(mockExpr);
 | 
			
		||||
            spyOn(TreeView.prototype, 'observe').andCallThrough();
 | 
			
		||||
 | 
			
		||||
            mctTree = new MCTTree(mockParse, mockGestureService);
 | 
			
		||||
        });
 | 
			
		||||
@@ -61,13 +58,8 @@ define([
 | 
			
		||||
            expect(mctTree.restrict).toEqual("E");
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it("two-way binds", function () {
 | 
			
		||||
            expect(mctTree.scope).toEqual({
 | 
			
		||||
                rootObject: "=",
 | 
			
		||||
                selectedObject: "=",
 | 
			
		||||
                allowSelection: "=?",
 | 
			
		||||
                onSelection: "=?"
 | 
			
		||||
            });
 | 
			
		||||
        it("two-way binds to mctObject and mctModel", function () {
 | 
			
		||||
            expect(mctTree.scope).toEqual({ mctObject: "=", mctModel: "=" });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        describe("link", function () {
 | 
			
		||||
@@ -89,16 +81,16 @@ define([
 | 
			
		||||
                expect(mockElement.append).toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("watches for selected-object expression in the parent", function () {
 | 
			
		||||
            it("watches for mct-model's expression in the parent", function () {
 | 
			
		||||
                expect(mockScope.$watch).toHaveBeenCalledWith(
 | 
			
		||||
                    "selectedObject",
 | 
			
		||||
                    "mctModel",
 | 
			
		||||
                    jasmine.any(Function)
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("watches for changes to root-object", function () {
 | 
			
		||||
            it("watches for changes to mct-object", function () {
 | 
			
		||||
                expect(mockScope.$watch).toHaveBeenCalledWith(
 | 
			
		||||
                    "rootObject",
 | 
			
		||||
                    "mctObject",
 | 
			
		||||
                    jasmine.any(Function)
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
@@ -110,10 +102,6 @@ define([
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("watches for changes in tree view", function () {
 | 
			
		||||
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // https://github.com/nasa/openmct/issues/1114
 | 
			
		||||
            it("does not trigger $apply during $watches", function () {
 | 
			
		||||
                mockScope.mctObject = makeMockDomainObject('root');
 | 
			
		||||
@@ -123,18 +111,14 @@ define([
 | 
			
		||||
                });
 | 
			
		||||
                expect(mockScope.$apply).not.toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
            it("does trigger $apply from tree manipulation", function () {
 | 
			
		||||
                if (/PhantomJS/g.test(window.navigator.userAgent)) {
 | 
			
		||||
                    console.log('Unable to run test in PhantomJS due to lack of support for event constructors');
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            it("does trigger $apply from other value changes", function () {
 | 
			
		||||
                // White-boxy; we know this is the setter for the tree's value
 | 
			
		||||
                var treeValueFn = TreeView.prototype.observe.calls[0].args[0];
 | 
			
		||||
                var treeValueFn = mockScope.$watch.calls[0].args[1];
 | 
			
		||||
 | 
			
		||||
                mockScope.mctObject = makeMockDomainObject('root');
 | 
			
		||||
                mockScope.mctMode = makeMockDomainObject('selection');
 | 
			
		||||
 | 
			
		||||
                treeValueFn(makeMockDomainObject('other'), new MouseEvent("click"));
 | 
			
		||||
                treeValueFn(makeMockDomainObject('other'));
 | 
			
		||||
 | 
			
		||||
                expect(mockScope.$apply).toHaveBeenCalled();
 | 
			
		||||
            });
 | 
			
		||||
 
 | 
			
		||||
@@ -289,9 +289,8 @@ define([
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("notifies listeners when value is changed", function () {
 | 
			
		||||
                treeView.value(mockDomainObject, {some: event});
 | 
			
		||||
                expect(mockCallback)
 | 
			
		||||
                    .toHaveBeenCalledWith(mockDomainObject, {some: event});
 | 
			
		||||
                treeView.value(mockDomainObject);
 | 
			
		||||
                expect(mockCallback).toHaveBeenCalledWith(mockDomainObject);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            it("does not notify listeners when deactivated", function () {
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
$output-bourbon-deprecation-warnings: false;
 | 
			
		||||
 | 
			
		||||
@import "bourbon";
 | 
			
		||||
 | 
			
		||||
@import "../../../../general/res/sass/_mixins";
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@
 | 
			
		||||
 * this source code distribution or the Licensing information page available
 | 
			
		||||
 * at runtime from the About dialog for additional information.
 | 
			
		||||
 *****************************************************************************/
 | 
			
		||||
$output-bourbon-deprecation-warnings: false;
 | 
			
		||||
 | 
			
		||||
@import "bourbon";
 | 
			
		||||
 | 
			
		||||
@import "../../../../general/res/sass/_mixins";
 | 
			
		||||
 
 | 
			
		||||
@@ -40,6 +40,9 @@ define([
 | 
			
		||||
                {
 | 
			
		||||
                    "category": "composition",
 | 
			
		||||
                    "implementation": CompositionPolicy,
 | 
			
		||||
                    "depends": [
 | 
			
		||||
                        "$injector"
 | 
			
		||||
                    ],
 | 
			
		||||
                    "message": "Objects of this type cannot contain objects of that type."
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										77
									
								
								platform/containment/src/CapabilityTable.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								platform/containment/src/CapabilityTable.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
			
		||||
/*****************************************************************************
 | 
			
		||||
 * Open MCT, Copyright (c) 2014-2016, United States Government
 | 
			
		||||
 * as represented by the Administrator of the National Aeronautics and Space
 | 
			
		||||
 * Administration. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * Open MCT 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 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(
 | 
			
		||||
    [],
 | 
			
		||||
    function () {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Build a table indicating which types are expected to expose
 | 
			
		||||
         * which capabilities. This supports composition policy (rules
 | 
			
		||||
         * for which objects can contain which other objects) which
 | 
			
		||||
         * sometimes is determined based on the presence of capabilities.
 | 
			
		||||
         * @constructor
 | 
			
		||||
         * @memberof platform/containment
 | 
			
		||||
         */
 | 
			
		||||
        function CapabilityTable(typeService, capabilityService) {
 | 
			
		||||
            var self = this;
 | 
			
		||||
 | 
			
		||||
            // Build an initial model for a type
 | 
			
		||||
            function buildModel(type) {
 | 
			
		||||
                var model = Object.create(type.getInitialModel() || {});
 | 
			
		||||
                model.type = type.getKey();
 | 
			
		||||
                return model;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Get capabilities expected for this type
 | 
			
		||||
            function getCapabilities(type) {
 | 
			
		||||
                return capabilityService.getCapabilities(buildModel(type));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Populate the lookup table for this type's capabilities
 | 
			
		||||
            function addToTable(type) {
 | 
			
		||||
                var typeKey = type.getKey();
 | 
			
		||||
                Object.keys(getCapabilities(type)).forEach(function (key) {
 | 
			
		||||
                    self.table[key] = self.table[key] || {};
 | 
			
		||||
                    self.table[key][typeKey] = true;
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Build the table
 | 
			
		||||
            this.table = {};
 | 
			
		||||
            (typeService.listTypes() || []).forEach(addToTable);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Check if a type is expected to expose a specific capability.
 | 
			
		||||
         * @param {string} typeKey the type identifier
 | 
			
		||||
         * @param {string} capabilityKey the capability identifier
 | 
			
		||||
         * @returns {boolean} true if expected to be exposed
 | 
			
		||||
         */
 | 
			
		||||
        CapabilityTable.prototype.hasCapability = function (typeKey, capabilityKey) {
 | 
			
		||||
            return (this.table[capabilityKey] || {})[typeKey];
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return CapabilityTable;
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
@@ -45,7 +45,9 @@ define(
 | 
			
		||||
        ComposeActionPolicy.prototype.allowComposition = function (containerObject, selectedObject) {
 | 
			
		||||
            // Get the object types involved in the compose action
 | 
			
		||||
            var containerType = containerObject &&
 | 
			
		||||
                containerObject.getCapability('type');
 | 
			
		||||
                    containerObject.getCapability('type'),
 | 
			
		||||
                selectedType = selectedObject &&
 | 
			
		||||
                    selectedObject.getCapability('type');
 | 
			
		||||
 | 
			
		||||
            // Get a reference to the policy service if needed...
 | 
			
		||||
            this.policyService = this.policyService || this.getPolicyService();
 | 
			
		||||
@@ -55,7 +57,7 @@ define(
 | 
			
		||||
                this.policyService.allow(
 | 
			
		||||
                    'composition',
 | 
			
		||||
                    containerType,
 | 
			
		||||
                    selectedObject
 | 
			
		||||
                    selectedType
 | 
			
		||||
                );
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -26,8 +26,8 @@
 | 
			
		||||
 * @namespace platform/containment
 | 
			
		||||
 */
 | 
			
		||||
define(
 | 
			
		||||
    [],
 | 
			
		||||
    function () {
 | 
			
		||||
    ['./ContainmentTable'],
 | 
			
		||||
    function (ContainmentTable) {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Defines composition policy as driven by type metadata.
 | 
			
		||||
@@ -35,33 +35,21 @@ define(
 | 
			
		||||
         * @memberof platform/containment
 | 
			
		||||
         * @implements {Policy.<Type, Type>}
 | 
			
		||||
         */
 | 
			
		||||
        function CompositionPolicy() {
 | 
			
		||||
        function CompositionPolicy($injector) {
 | 
			
		||||
            // We're really just wrapping the containment table and rephrasing
 | 
			
		||||
            // it as a policy decision.
 | 
			
		||||
            var table;
 | 
			
		||||
 | 
			
		||||
            this.getTable = function () {
 | 
			
		||||
                return (table = table || new ContainmentTable(
 | 
			
		||||
                    $injector.get('typeService'),
 | 
			
		||||
                    $injector.get('capabilityService')
 | 
			
		||||
                ));
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        CompositionPolicy.prototype.allow = function (parentType, child) {
 | 
			
		||||
            var parentDef = parentType.getDefinition();
 | 
			
		||||
 | 
			
		||||
            // A parent without containment rules can contain anything.
 | 
			
		||||
            if (!parentDef.contains) {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // If any containment rule matches context type, the candidate
 | 
			
		||||
            // can contain this type.
 | 
			
		||||
            return parentDef.contains.some(function (c) {
 | 
			
		||||
                // Simple containment rules are supported typeKeys.
 | 
			
		||||
                if (typeof c === 'string') {
 | 
			
		||||
                    return c === child.getCapability('type').getKey();
 | 
			
		||||
                }
 | 
			
		||||
                // More complicated rules require context to have all specified
 | 
			
		||||
                // capabilities.
 | 
			
		||||
                if (!Array.isArray(c.has)) {
 | 
			
		||||
                    c.has = [c.has];
 | 
			
		||||
                }
 | 
			
		||||
                return c.has.every(function (capability) {
 | 
			
		||||
                    return child.hasCapability(capability);
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        CompositionPolicy.prototype.allow = function (candidate, context) {
 | 
			
		||||
            return this.getTable().canContain(candidate, context);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return CompositionPolicy;
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user