mirror of
https://github.com/nasa/openmct.git
synced 2025-02-22 10:11:06 +00:00
Merge branch 'master' into open671
This commit is contained in:
commit
f3b265bdd5
@ -1,6 +1,6 @@
|
|||||||
# Contributing to Open MCT Web
|
# Contributing to Open MCT
|
||||||
|
|
||||||
This document describes the process of contributing to Open MCT Web as well
|
This document describes the process of contributing to Open MCT as well
|
||||||
as the standards that will be applied when evaluating contributions.
|
as the standards that will be applied when evaluating contributions.
|
||||||
|
|
||||||
Please be aware that additional agreements will be necessary before we can
|
Please be aware that additional agreements will be necessary before we can
|
||||||
@ -21,9 +21,9 @@ The short version:
|
|||||||
|
|
||||||
## Contribution Process
|
## Contribution Process
|
||||||
|
|
||||||
Open MCT Web uses git for software version control, and for branching and
|
Open MCT uses git for software version control, and for branching and
|
||||||
merging. The central repository is at
|
merging. The central repository is at
|
||||||
https://github.com/nasa/openmctweb.git.
|
https://github.com/nasa/openmct.git.
|
||||||
|
|
||||||
### Roles
|
### Roles
|
||||||
|
|
||||||
@ -116,18 +116,18 @@ the merge back to the master branch.
|
|||||||
|
|
||||||
## Standards
|
## Standards
|
||||||
|
|
||||||
Contributions to Open MCT Web are expected to meet the following standards.
|
Contributions to Open MCT are expected to meet the following standards.
|
||||||
In addition, reviewers should use general discretion before accepting
|
In addition, reviewers should use general discretion before accepting
|
||||||
changes.
|
changes.
|
||||||
|
|
||||||
### Code Standards
|
### Code Standards
|
||||||
|
|
||||||
JavaScript sources in Open MCT Web must satisfy JSLint under its default
|
JavaScript sources in Open MCT must satisfy JSLint under its default
|
||||||
settings. This is verified by the command line build.
|
settings. This is verified by the command line build.
|
||||||
|
|
||||||
#### Code Guidelines
|
#### Code Guidelines
|
||||||
|
|
||||||
JavaScript sources in Open MCT Web should:
|
JavaScript sources in Open MCT should:
|
||||||
|
|
||||||
* Use four spaces for indentation. Tabs should not be used.
|
* Use four spaces for indentation. Tabs should not be used.
|
||||||
* Include JSDoc for any exposed API (e.g. public methods, constructors.)
|
* Include JSDoc for any exposed API (e.g. public methods, constructors.)
|
||||||
@ -159,7 +159,7 @@ JavaScript sources in Open MCT Web should:
|
|||||||
* Third, imperative statements.
|
* Third, imperative statements.
|
||||||
* Finally, the returned value.
|
* Finally, the returned value.
|
||||||
|
|
||||||
Deviations from Open MCT Web code style guidelines require two-party agreement,
|
Deviations from Open MCT code style guidelines require two-party agreement,
|
||||||
typically from the author of the change and its reviewer.
|
typically from the author of the change and its reviewer.
|
||||||
|
|
||||||
#### Code Example
|
#### Code Example
|
||||||
@ -260,7 +260,7 @@ these standards.
|
|||||||
|
|
||||||
## Issue Reporting
|
## Issue Reporting
|
||||||
|
|
||||||
Issues are tracked at https://github.com/nasa/openmctweb/issues
|
Issues are tracked at https://github.com/nasa/openmct/issues
|
||||||
|
|
||||||
Issues should include:
|
Issues should include:
|
||||||
|
|
||||||
|
22
README.md
22
README.md
@ -1,6 +1,6 @@
|
|||||||
# Open MCT Web
|
# Open MCT
|
||||||
|
|
||||||
Open MCT Web is a web-based platform for mission operations user interface
|
Open MCT is a web-based platform for mission operations user interface
|
||||||
software.
|
software.
|
||||||
|
|
||||||
## Bundles
|
## Bundles
|
||||||
@ -8,7 +8,7 @@ software.
|
|||||||
A bundle is a group of software components (including source code, declared
|
A bundle is a group of software components (including source code, declared
|
||||||
as AMD modules, as well as resources such as images and HTML templates)
|
as AMD modules, as well as resources such as images and HTML templates)
|
||||||
that are intended to be added or removed as a single unit. A plug-in for
|
that are intended to be added or removed as a single unit. A plug-in for
|
||||||
Open MCT Web will be expressed as a bundle; platform components are also
|
Open MCT will be expressed as a bundle; platform components are also
|
||||||
expressed as bundles.
|
expressed as bundles.
|
||||||
|
|
||||||
A bundle is also just a directory which contains a file `bundle.json`,
|
A bundle is also just a directory which contains a file `bundle.json`,
|
||||||
@ -16,7 +16,7 @@ which declares its contents.
|
|||||||
|
|
||||||
The file `bundles.json` (note the plural), at the top level of the
|
The file `bundles.json` (note the plural), at the top level of the
|
||||||
repository, is a JSON file containing an array of all bundles (expressed as
|
repository, is a JSON file containing an array of all bundles (expressed as
|
||||||
directory names) to include in a running instance of Open MCT Web. Adding or
|
directory names) to include in a running instance of Open MCT. Adding or
|
||||||
removing paths from this list will add or remove bundles from the running
|
removing paths from this list will add or remove bundles from the running
|
||||||
application.
|
application.
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ To run:
|
|||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
Open MCT Web is built using [`npm`](http://npmjs.com/)
|
Open MCT is built using [`npm`](http://npmjs.com/)
|
||||||
and [`gulp`](http://gulpjs.com/).
|
and [`gulp`](http://gulpjs.com/).
|
||||||
|
|
||||||
To build:
|
To build:
|
||||||
@ -64,18 +64,18 @@ To build:
|
|||||||
`npm run prepublish`
|
`npm run prepublish`
|
||||||
|
|
||||||
This will compile and minify JavaScript sources, as well as copy over assets.
|
This will compile and minify JavaScript sources, as well as copy over assets.
|
||||||
The contents of the `dist` folder will contain a runnable Open MCT Web
|
The contents of the `dist` folder will contain a runnable Open MCT
|
||||||
instance (e.g. by starting an HTTP server in that directory), including:
|
instance (e.g. by starting an HTTP server in that directory), including:
|
||||||
|
|
||||||
* A `main.js` file containing Open MCT Web source code.
|
* A `main.js` file containing Open MCT source code.
|
||||||
* Various assets in the `example` and `platform` directories.
|
* Various assets in the `example` and `platform` directories.
|
||||||
* An `index.html` that runs Open MCT Web in its default configuration.
|
* An `index.html` that runs Open MCT in its default configuration.
|
||||||
|
|
||||||
Additional `gulp` tasks are defined in [the gulpfile](gulpfile.js).
|
Additional `gulp` tasks are defined in [the gulpfile](gulpfile.js).
|
||||||
|
|
||||||
### Building Documentation
|
### Building Documentation
|
||||||
|
|
||||||
Open MCT Web's documentation is generated by an
|
Open MCT's documentation is generated by an
|
||||||
[npm](https://www.npmjs.com/)-based build. It has additional dependencies that
|
[npm](https://www.npmjs.com/)-based build. It has additional dependencies that
|
||||||
may not be available on every platform and thus is not covered in the standard
|
may not be available on every platform and thus is not covered in the standard
|
||||||
npm install. Ensure your system has [libcairo](http://cairographics.org/)
|
npm install. Ensure your system has [libcairo](http://cairographics.org/)
|
||||||
@ -89,7 +89,7 @@ Documentation will be generated in `target/docs`.
|
|||||||
|
|
||||||
# Glossary
|
# Glossary
|
||||||
|
|
||||||
Certain terms are used throughout Open MCT Web with consistent meanings
|
Certain terms are used throughout Open MCT with consistent meanings
|
||||||
or conventions. Any deviations from the below are issues and should be
|
or conventions. Any deviations from the below are issues and should be
|
||||||
addressed (either by updating this glossary or changing code to reflect
|
addressed (either by updating this glossary or changing code to reflect
|
||||||
correct usage.) Other developer documentation, particularly in-line
|
correct usage.) Other developer documentation, particularly in-line
|
||||||
@ -112,7 +112,7 @@ documentation, may presume an understanding of these terms.
|
|||||||
(Most often used in the context of extensions, domain
|
(Most often used in the context of extensions, domain
|
||||||
object models, or other similar application-specific objects.)
|
object models, or other similar application-specific objects.)
|
||||||
* _domain object_: A meaningful object to the user; a distinct thing in
|
* _domain object_: A meaningful object to the user; a distinct thing in
|
||||||
the work support by Open MCT Web. Anything that appears in the left-hand
|
the work support by Open MCT. Anything that appears in the left-hand
|
||||||
tree is a domain object.
|
tree is a domain object.
|
||||||
* _extension_: An extension is a unit of functionality exposed to the
|
* _extension_: An extension is a unit of functionality exposed to the
|
||||||
platform in a declarative fashion by a bundle. For more
|
platform in a declarative fashion by a bundle. For more
|
||||||
|
4
app.js
4
app.js
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
// Defaults
|
// Defaults
|
||||||
options.port = options.port || options.p || 8080;
|
options.port = options.port || options.p || 8080;
|
||||||
|
options.directory = options.directory || options.D || '.';
|
||||||
['include', 'exclude', 'i', 'x'].forEach(function (opt) {
|
['include', 'exclude', 'i', 'x'].forEach(function (opt) {
|
||||||
options[opt] = options[opt] || [];
|
options[opt] = options[opt] || [];
|
||||||
// Make sure includes/excludes always end up as arrays
|
// Make sure includes/excludes always end up as arrays
|
||||||
@ -36,6 +37,7 @@
|
|||||||
console.log(" --port, -p <number> Specify port.");
|
console.log(" --port, -p <number> Specify port.");
|
||||||
console.log(" --include, -i <bundle> Include the specified bundle.");
|
console.log(" --include, -i <bundle> Include the specified bundle.");
|
||||||
console.log(" --exclude, -x <bundle> Exclude the specified bundle.");
|
console.log(" --exclude, -x <bundle> Exclude the specified bundle.");
|
||||||
|
console.log(" --directory, -D <bundle> Serve files from specified directory.");
|
||||||
console.log("");
|
console.log("");
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
@ -71,7 +73,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Expose everything else as static files
|
// Expose everything else as static files
|
||||||
app.use(express['static']('.'));
|
app.use(express['static'](options.directory));
|
||||||
|
|
||||||
// Finally, open the HTTP server
|
// Finally, open the HTTP server
|
||||||
app.listen(options.port);
|
app.listen(options.port);
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "openmctweb",
|
"name": "openmct",
|
||||||
"description": "The OpenMCTWeb core platform",
|
"description": "The Open MCT core platform",
|
||||||
"main": "",
|
"main": "",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"moduleType": [],
|
"moduleType": [],
|
||||||
"homepage": "http://nasa.github.io/openmctweb/",
|
"homepage": "http://nasa.github.io/openmct/",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"angular": "1.4.4",
|
"angular": "1.4.4",
|
||||||
|
@ -5,7 +5,7 @@ software components to communicate. The software components it recognizes
|
|||||||
are:
|
are:
|
||||||
|
|
||||||
* _Extensions_: Individual units of functionality that can be added to
|
* _Extensions_: Individual units of functionality that can be added to
|
||||||
or removed from Open MCT Web. _Extension categories_ distinguish what
|
or removed from Open MCT. _Extension categories_ distinguish what
|
||||||
type of functionality is being added/removed.
|
type of functionality is being added/removed.
|
||||||
* _Bundles_: A grouping of related extensions
|
* _Bundles_: A grouping of related extensions
|
||||||
(named after an analogous concept from [OSGi](http://www.osgi.org/))
|
(named after an analogous concept from [OSGi](http://www.osgi.org/))
|
||||||
@ -19,7 +19,7 @@ manner which the framework layer can understand.
|
|||||||
|
|
||||||
```nomnoml
|
```nomnoml
|
||||||
#direction: down
|
#direction: down
|
||||||
[Open MCT Web|
|
[Open MCT|
|
||||||
[Dependency injection framework]-->[Platform bundle #1]
|
[Dependency injection framework]-->[Platform bundle #1]
|
||||||
[Dependency injection framework]-->[Platform bundle #2]
|
[Dependency injection framework]-->[Platform bundle #2]
|
||||||
[Dependency injection framework]-->[Plugin bundle #1]
|
[Dependency injection framework]-->[Plugin bundle #1]
|
||||||
@ -35,7 +35,7 @@ manner which the framework layer can understand.
|
|||||||
```
|
```
|
||||||
|
|
||||||
The "dependency injection framework" in this case is
|
The "dependency injection framework" in this case is
|
||||||
[AngularJS](https://angularjs.org/). Open MCT Web's framework layer
|
[AngularJS](https://angularjs.org/). Open MCT's framework layer
|
||||||
is really just a thin wrapper over Angular that recognizes the
|
is really just a thin wrapper over Angular that recognizes the
|
||||||
concepts of bundles and extensions (as declared in JSON files) and
|
concepts of bundles and extensions (as declared in JSON files) and
|
||||||
registering extensions with Angular. It additionally acts as a
|
registering extensions with Angular. It additionally acts as a
|
||||||
@ -60,7 +60,7 @@ activities which were performed by the framework component.
|
|||||||
|
|
||||||
## Application Initialization
|
## Application Initialization
|
||||||
|
|
||||||
The framework component initializes an Open MCT Web application following
|
The framework component initializes an Open MCT application following
|
||||||
a simple sequence of steps.
|
a simple sequence of steps.
|
||||||
|
|
||||||
```nomnoml
|
```nomnoml
|
||||||
@ -97,7 +97,7 @@ a simple sequence of steps.
|
|||||||
[Extension]o->[Dependency #3]
|
[Extension]o->[Dependency #3]
|
||||||
```
|
```
|
||||||
|
|
||||||
Open MCT Web's architecture relies on a simple premise: Individual units
|
Open MCT's architecture relies on a simple premise: Individual units
|
||||||
(extensions) only have access to the dependencies they declare that they
|
(extensions) only have access to the dependencies they declare that they
|
||||||
need, and they acquire references to these dependencies via dependency
|
need, and they acquire references to these dependencies via dependency
|
||||||
injection. This has several desirable traits:
|
injection. This has several desirable traits:
|
||||||
@ -121,11 +121,11 @@ injection. This has several desirable traits:
|
|||||||
the framework.
|
the framework.
|
||||||
|
|
||||||
A drawback to this approach is that it makes it difficult to define
|
A drawback to this approach is that it makes it difficult to define
|
||||||
"the architecture" of Open MCT Web, in terms of describing the specific
|
"the architecture" of Open MCT, in terms of describing the specific
|
||||||
units that interact at run-time. The run-time architecture is determined
|
units that interact at run-time. The run-time architecture is determined
|
||||||
by the framework as the consequence of wiring together dependencies.
|
by the framework as the consequence of wiring together dependencies.
|
||||||
As such, the specific architecture of any given application built on
|
As such, the specific architecture of any given application built on
|
||||||
Open MCT Web can look very different.
|
Open MCT can look very different.
|
||||||
|
|
||||||
Keeping that in mind, there are a few useful patterns supported by the
|
Keeping that in mind, there are a few useful patterns supported by the
|
||||||
framework that are useful to keep in mind.
|
framework that are useful to keep in mind.
|
||||||
@ -229,4 +229,4 @@ otherwise a single provider) will be exposed as a single service that
|
|||||||
other extensions can acquire through dependency injection. Because all
|
other extensions can acquire through dependency injection. Because all
|
||||||
components of the same type of service expose the same interface, users
|
components of the same type of service expose the same interface, users
|
||||||
of that service do not need to be aware that they are talking to an
|
of that service do not need to be aware that they are talking to an
|
||||||
aggregator or a provider, for instance.
|
aggregator or a provider, for instance.
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
# Introduction
|
# Introduction
|
||||||
|
|
||||||
The purpose of this document is to familiarize developers with the
|
The purpose of this document is to familiarize developers with the
|
||||||
overall architecture of Open MCT Web.
|
overall architecture of Open MCT.
|
||||||
|
|
||||||
The target audience includes:
|
The target audience includes:
|
||||||
|
|
||||||
* _Platform maintainers_: Individuals involved in developing,
|
* _Platform maintainers_: Individuals involved in developing,
|
||||||
extending, and maintaing capabilities of the platform.
|
extending, and maintaing capabilities of the platform.
|
||||||
* _Integration developers_: Individuals tasked with integrated
|
* _Integration developers_: Individuals tasked with integrated
|
||||||
Open MCT Web into a larger system, who need to understand
|
Open MCT into a larger system, who need to understand
|
||||||
its inner workings sufficiently to complete this integration.
|
its inner workings sufficiently to complete this integration.
|
||||||
|
|
||||||
As the focus of this document is on architecture, whenever possible
|
As the focus of this document is on architecture, whenever possible
|
||||||
@ -17,25 +17,25 @@ omitted. These details may be found in the developer guide.
|
|||||||
|
|
||||||
# Overview
|
# Overview
|
||||||
|
|
||||||
Open MCT Web is client software: It runs in a web browser and
|
Open MCT is client software: It runs in a web browser and
|
||||||
provides a user interface, while communicating with various
|
provides a user interface, while communicating with various
|
||||||
server-side resources through browser APIs.
|
server-side resources through browser APIs.
|
||||||
|
|
||||||
```nomnoml
|
```nomnoml
|
||||||
#direction: right
|
#direction: right
|
||||||
[Client|[Browser|[Open MCT Web]->[Browser APIs]]]
|
[Client|[Browser|[Open MCT]->[Browser APIs]]]
|
||||||
[Server|[Web services]]
|
[Server|[Web services]]
|
||||||
[Client]<->[Server]
|
[Client]<->[Server]
|
||||||
```
|
```
|
||||||
|
|
||||||
While Open MCT Web can be configured to run as a standalone client,
|
While Open MCT can be configured to run as a standalone client,
|
||||||
this is rarely very useful. Instead, it is intended to be used as a
|
this is rarely very useful. Instead, it is intended to be used as a
|
||||||
display and interaction layer for information obtained from a
|
display and interaction layer for information obtained from a
|
||||||
variety of back-end services. Doing so requires authoring or utilizing
|
variety of back-end services. Doing so requires authoring or utilizing
|
||||||
adapter plugins which allow Open MCT Web to interact with these services.
|
adapter plugins which allow Open MCT to interact with these services.
|
||||||
|
|
||||||
Typically, the pattern here is to provide a known interface that
|
Typically, the pattern here is to provide a known interface that
|
||||||
Open MCT Web can utilize, and implement it such that it interacts with
|
Open MCT can utilize, and implement it such that it interacts with
|
||||||
whatever back-end provides the relevant information.
|
whatever back-end provides the relevant information.
|
||||||
Examples of back-ends that can be utilized in this fashion include
|
Examples of back-ends that can be utilized in this fashion include
|
||||||
databases for the persistence of user-created objects, or sources of
|
databases for the persistence of user-created objects, or sources of
|
||||||
@ -43,13 +43,13 @@ telemetry data.
|
|||||||
|
|
||||||
## Software Architecture
|
## Software Architecture
|
||||||
|
|
||||||
The simplest overview of Open MCT Web is to look at it as a "layered"
|
The simplest overview of Open MCT is to look at it as a "layered"
|
||||||
architecture, where each layer more clearly specifies the behavior
|
architecture, where each layer more clearly specifies the behavior
|
||||||
of the software.
|
of the software.
|
||||||
|
|
||||||
```nomnoml
|
```nomnoml
|
||||||
#direction: down
|
#direction: down
|
||||||
[Open MCT Web|
|
[Open MCT|
|
||||||
[Platform]<->[Application]
|
[Platform]<->[Application]
|
||||||
[Framework]->[Application]
|
[Framework]->[Application]
|
||||||
[Framework]->[Platform]
|
[Framework]->[Platform]
|
||||||
@ -64,14 +64,14 @@ These layers are:
|
|||||||
established an abstraction by which different software components
|
established an abstraction by which different software components
|
||||||
may communicate and/or interact.
|
may communicate and/or interact.
|
||||||
* [_Platform_](platform.md): The platform layer defines the general look,
|
* [_Platform_](platform.md): The platform layer defines the general look,
|
||||||
feel, and behavior of Open MCT Web. This includes user-facing components like
|
feel, and behavior of Open MCT. This includes user-facing components like
|
||||||
Browse mode and Edit mode, as well as underlying elements of the
|
Browse mode and Edit mode, as well as underlying elements of the
|
||||||
information model and the general service infrastructure.
|
information model and the general service infrastructure.
|
||||||
* _Application_: The application layer defines specific features of
|
* _Application_: The application layer defines specific features of
|
||||||
an application built on Open MCT Web. This includes adapters to
|
an application built on Open MCT. This includes adapters to
|
||||||
specific back-ends, new types of things for users to create, and
|
specific back-ends, new types of things for users to create, and
|
||||||
new ways of visualizing objects within the system. This layer
|
new ways of visualizing objects within the system. This layer
|
||||||
typically consists of a mix of custom plug-ins to Open MCT Web,
|
typically consists of a mix of custom plug-ins to Open MCT,
|
||||||
as well as optional features (such as Plot view) included alongside
|
as well as optional features (such as Plot view) included alongside
|
||||||
the platform.
|
the platform.
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Overview
|
# Overview
|
||||||
|
|
||||||
The Open MCT Web platform utilizes the [framework layer](Framework.md)
|
The Open MCT platform utilizes the [framework layer](Framework.md)
|
||||||
to provide an extensible baseline for applications which includes:
|
to provide an extensible baseline for applications which includes:
|
||||||
|
|
||||||
* A common user interface (and user interface paradigm) for dealing with
|
* A common user interface (and user interface paradigm) for dealing with
|
||||||
@ -16,7 +16,7 @@ building application, the platform adds more specificity by defining
|
|||||||
additional extension types and allowing for integration with back end
|
additional extension types and allowing for integration with back end
|
||||||
components.
|
components.
|
||||||
|
|
||||||
The run-time architecture of an Open MCT Web application can be categorized
|
The run-time architecture of an Open MCT application can be categorized
|
||||||
into certain high-level tiers:
|
into certain high-level tiers:
|
||||||
|
|
||||||
```nomnoml
|
```nomnoml
|
||||||
@ -29,7 +29,7 @@ into certain high-level tiers:
|
|||||||
[Browser APIs]->[Back-end]
|
[Browser APIs]->[Back-end]
|
||||||
```
|
```
|
||||||
|
|
||||||
Applications built using Open MCT Web may add or configure functionality
|
Applications built using Open MCT may add or configure functionality
|
||||||
in __any of these tiers__.
|
in __any of these tiers__.
|
||||||
|
|
||||||
* _DOM_: The rendered HTML document, composed from HTML templates which
|
* _DOM_: The rendered HTML document, composed from HTML templates which
|
||||||
@ -60,7 +60,7 @@ in __any of these tiers__.
|
|||||||
functionality needed to support the information model. This includes
|
functionality needed to support the information model. This includes
|
||||||
exposing underlying sets of extensions and mediating with the
|
exposing underlying sets of extensions and mediating with the
|
||||||
back-end.
|
back-end.
|
||||||
* _Back-end_: The back-end is out of the scope of Open MCT Web, except
|
* _Back-end_: The back-end is out of the scope of Open MCT, except
|
||||||
for the interfaces which are utilized by adapters participating in the
|
for the interfaces which are utilized by adapters participating in the
|
||||||
service infrastructure. Includes the underlying persistence stores, telemetry
|
service infrastructure. Includes the underlying persistence stores, telemetry
|
||||||
streams, and so forth which the Open MCT Web client is being used to interact
|
streams, and so forth which the Open MCT Web client is being used to interact
|
||||||
@ -70,15 +70,15 @@ in __any of these tiers__.
|
|||||||
|
|
||||||
Once the
|
Once the
|
||||||
[application has been initialized](Framework.md#application-initialization)
|
[application has been initialized](Framework.md#application-initialization)
|
||||||
Open MCT Web primarily operates in an event-driven paradigm; various
|
Open MCT primarily operates in an event-driven paradigm; various
|
||||||
events (mouse clicks, timers firing, receiving responses to XHRs) trigger
|
events (mouse clicks, timers firing, receiving responses to XHRs) trigger
|
||||||
the invocation of functions, typically in the presentation layer for
|
the invocation of functions, typically in the presentation layer for
|
||||||
user actions or in the service infrastructure for server responses.
|
user actions or in the service infrastructure for server responses.
|
||||||
|
|
||||||
The "main point of entry" into an initialized Open MCT Web application
|
The "main point of entry" into an initialized Open MCT application
|
||||||
is effectively the
|
is effectively the
|
||||||
[route](https://docs.angularjs.org/api/ngRoute/service/$route#example)
|
[route](https://docs.angularjs.org/api/ngRoute/service/$route#example)
|
||||||
which is associated with the URL used to access Open MCT Web (or a
|
which is associated with the URL used to access Open MCT (or a
|
||||||
default route.) This route will be associated with a template which
|
default route.) This route will be associated with a template which
|
||||||
will be displayed; this template will include references to directives
|
will be displayed; this template will include references to directives
|
||||||
and controllers which will be interpreted by Angular and used to
|
and controllers which will be interpreted by Angular and used to
|
||||||
@ -107,11 +107,11 @@ both the information model and the service infrastructure.
|
|||||||
|
|
||||||
# Presentation Layer
|
# Presentation Layer
|
||||||
|
|
||||||
The presentation layer of Open MCT Web is responsible for providing
|
The presentation layer of Open MCT is responsible for providing
|
||||||
information to display within templates, and for handling interactions
|
information to display within templates, and for handling interactions
|
||||||
which are initiated from templated DOM elements. AngularJS acts as
|
which are initiated from templated DOM elements. AngularJS acts as
|
||||||
an intermediary between the web page as the user sees it, and the
|
an intermediary between the web page as the user sees it, and the
|
||||||
presentation layer implemented as Open MCT Web extensions.
|
presentation layer implemented as Open MCT extensions.
|
||||||
|
|
||||||
```nomnoml
|
```nomnoml
|
||||||
[Presentation Layer|
|
[Presentation Layer|
|
||||||
@ -143,12 +143,12 @@ to primitives from AngularJS:
|
|||||||
attributes and tags.
|
attributes and tags.
|
||||||
* [_Routes_](https://docs.angularjs.org/api/ngRoute/service/$route#example)
|
* [_Routes_](https://docs.angularjs.org/api/ngRoute/service/$route#example)
|
||||||
are used to associate specific URLs (including the fragment identifier)
|
are used to associate specific URLs (including the fragment identifier)
|
||||||
with specific application states. (In Open MCT Web, these are used to
|
with specific application states. (In Open MCT, these are used to
|
||||||
describe the mode of usage - e.g. browse or edit - as well as to
|
describe the mode of usage - e.g. browse or edit - as well as to
|
||||||
identify the object being used.)
|
identify the object being used.)
|
||||||
* [_Templates_](https://docs.angularjs.org/guide/templates) are partial
|
* [_Templates_](https://docs.angularjs.org/guide/templates) are partial
|
||||||
HTML documents that will be rendered and kept up-to-date by AngularJS.
|
HTML documents that will be rendered and kept up-to-date by AngularJS.
|
||||||
Open MCT Web introduces a custom `mct-include` directive which acts
|
Open MCT introduces a custom `mct-include` directive which acts
|
||||||
as a wrapper around `ng-include` to allow templates to be referred
|
as a wrapper around `ng-include` to allow templates to be referred
|
||||||
to by symbolic names.
|
to by symbolic names.
|
||||||
|
|
||||||
@ -189,10 +189,10 @@ to displaying domain objects.
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
Domain objects are the most fundamental component of Open MCT Web's
|
Domain objects are the most fundamental component of Open MCT's
|
||||||
information model. A domain object is some distinct thing relevant to a
|
information model. A domain object is some distinct thing relevant to a
|
||||||
user's work flow, such as a telemetry channel, display, or similar.
|
user's work flow, such as a telemetry channel, display, or similar.
|
||||||
Open MCT Web is a tool for viewing, browsing, manipulating, and otherwise
|
Open MCT is a tool for viewing, browsing, manipulating, and otherwise
|
||||||
interacting with a graph of domain objects.
|
interacting with a graph of domain objects.
|
||||||
|
|
||||||
A domain object should be conceived of as the union of the following:
|
A domain object should be conceived of as the union of the following:
|
||||||
@ -254,7 +254,7 @@ Concrete examples of capabilities which follow this pattern
|
|||||||
|
|
||||||
# Service Infrastructure
|
# Service Infrastructure
|
||||||
|
|
||||||
Most services exposed by the Open MCT Web platform follow the
|
Most services exposed by the Open MCT platform follow the
|
||||||
[composite services](Framework.md#composite-services) to permit
|
[composite services](Framework.md#composite-services) to permit
|
||||||
a higher degree of flexibility in how a service can be modified
|
a higher degree of flexibility in how a service can be modified
|
||||||
or customized for specific applications.
|
or customized for specific applications.
|
||||||
@ -327,7 +327,7 @@ A short summary of the roles of these services:
|
|||||||
[DomainObjectProvider]o-[CapabilityService]
|
[DomainObjectProvider]o-[CapabilityService]
|
||||||
```
|
```
|
||||||
|
|
||||||
As domain objects are central to Open MCT Web's information model,
|
As domain objects are central to Open MCT's information model,
|
||||||
acquiring domain objects is equally important.
|
acquiring domain objects is equally important.
|
||||||
|
|
||||||
```nomnoml
|
```nomnoml
|
||||||
@ -338,7 +338,7 @@ acquiring domain objects is equally important.
|
|||||||
[<state> Instantiate DomainObject]->[<end> End]
|
[<state> Instantiate DomainObject]->[<end> End]
|
||||||
```
|
```
|
||||||
|
|
||||||
Open MCT Web includes an implementation of an `ObjectService` which
|
Open MCT includes an implementation of an `ObjectService` which
|
||||||
satisfies this capability by:
|
satisfies this capability by:
|
||||||
|
|
||||||
* Consulting the [Model Service](#model-service) to acquire domain object
|
* Consulting the [Model Service](#model-service) to acquire domain object
|
||||||
@ -437,9 +437,9 @@ objects (this allows failures to be recognized and handled in groups.)
|
|||||||
The telemetry service is responsible for acquiring telemetry data.
|
The telemetry service is responsible for acquiring telemetry data.
|
||||||
|
|
||||||
Notably, the platform does not include any providers for
|
Notably, the platform does not include any providers for
|
||||||
`TelemetryService`; applications built on Open MCT Web will need to
|
`TelemetryService`; applications built on Open MCT will need to
|
||||||
implement a provider for this service if they wish to expose telemetry
|
implement a provider for this service if they wish to expose telemetry
|
||||||
data. This is usually the most important step for integrating Open MCT Web
|
data. This is usually the most important step for integrating Open MCT
|
||||||
into an existing telemetry system.
|
into an existing telemetry system.
|
||||||
|
|
||||||
Requests for telemetry data are usually initiated in the
|
Requests for telemetry data are usually initiated in the
|
||||||
@ -721,6 +721,6 @@ disallow.
|
|||||||
```
|
```
|
||||||
|
|
||||||
The type service provides metadata about the different types of domain
|
The type service provides metadata about the different types of domain
|
||||||
objects that exist within an Open MCT Web application. The platform
|
objects that exist within an Open MCT application. The platform
|
||||||
implementation reads these types in from extension category `types`
|
implementation reads these types in from extension category `types`
|
||||||
and wraps them in a JavaScript interface.
|
and wraps them in a JavaScript interface.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Open MCT Web Developer Guide
|
# Open MCT Developer Guide
|
||||||
Victor Woeltjen
|
Victor Woeltjen
|
||||||
|
|
||||||
[victor.woeltjen@nasa.gov](mailto:victor.woeltjen@nasa.gov)
|
[victor.woeltjen@nasa.gov](mailto:victor.woeltjen@nasa.gov)
|
||||||
@ -6,35 +6,36 @@ Victor Woeltjen
|
|||||||
September 23, 2015
|
September 23, 2015
|
||||||
Document Version 1.1
|
Document Version 1.1
|
||||||
|
|
||||||
Date | Version | Summary of Changes | Author
|
Date | Version | Summary of Changes | Author
|
||||||
------------------- | --------- | ----------------------- | ---------------
|
------------------- | --------- | ------------------------- | ---------------
|
||||||
April 29, 2015 | 0 | Initial Draft | Victor Woeltjen
|
April 29, 2015 | 0 | Initial Draft | Victor Woeltjen
|
||||||
May 12, 2015 | 0.1 | | Victor Woeltjen
|
May 12, 2015 | 0.1 | | Victor Woeltjen
|
||||||
June 4, 2015 | 1.0 | Name Changes | Victor Woeltjen
|
June 4, 2015 | 1.0 | Name Changes | Victor Woeltjen
|
||||||
October 4, 2015 | 1.1 | Conversion to MarkDown | Andrew Henry
|
October 4, 2015 | 1.1 | Conversion to MarkDown | Andrew Henry
|
||||||
|
April 5, 2016 | 1.2 | Added Mct-table directive | Andrew Henry
|
||||||
|
|
||||||
# Introduction
|
# Introduction
|
||||||
The purpose of this guide is to familiarize software developers with the Open
|
The purpose of this guide is to familiarize software developers with the Open
|
||||||
MCT Web platform.
|
MCT Web platform.
|
||||||
|
|
||||||
## What is Open MCT Web
|
## What is Open MCT
|
||||||
Open MCT Web is a platform for building user interface and display tools,
|
Open MCT is a platform for building user interface and display tools,
|
||||||
developed at the NASA Ames Research Center in collaboration with teams at the
|
developed at the NASA Ames Research Center in collaboration with teams at the
|
||||||
Jet Propulsion Laboratory. It is written in HTML5, CSS3, and JavaScript, using
|
Jet Propulsion Laboratory. It is written in HTML5, CSS3, and JavaScript, using
|
||||||
[AngularJS](http://www.angularjs.org) as a framework. Its intended use is to
|
[AngularJS](http://www.angularjs.org) as a framework. Its intended use is to
|
||||||
create single-page web applications which integrate data and behavior from a
|
create single-page web applications which integrate data and behavior from a
|
||||||
variety of sources and domains.
|
variety of sources and domains.
|
||||||
|
|
||||||
Open MCT Web has been developed to support the remote operation of space
|
Open MCT has been developed to support the remote operation of space
|
||||||
vehicles, so some of its features are specific to that task; however, it is
|
vehicles, so some of its features are specific to that task; however, it is
|
||||||
flexible enough to be adapted to a variety of other application domains where a
|
flexible enough to be adapted to a variety of other application domains where a
|
||||||
display tool oriented toward browsing, composing, and visualizing would be
|
display tool oriented toward browsing, composing, and visualizing would be
|
||||||
useful.
|
useful.
|
||||||
|
|
||||||
Open MCT Web provides:
|
Open MCT provides:
|
||||||
|
|
||||||
* A common user interface paradigm which can be applied to a variety of domains
|
* A common user interface paradigm which can be applied to a variety of domains
|
||||||
and tasks. Open MCT Web is more than a widget toolkit - it provides a standard
|
and tasks. Open MCT is more than a widget toolkit - it provides a standard
|
||||||
tree-on-the-left, view-on-the-right browsing environment which you customize by
|
tree-on-the-left, view-on-the-right browsing environment which you customize by
|
||||||
adding new browsable object types, visualizations, and back-end adapters.
|
adding new browsable object types, visualizations, and back-end adapters.
|
||||||
* A plugin framework and an extensible API for introducing new application
|
* A plugin framework and an extensible API for introducing new application
|
||||||
@ -43,17 +44,17 @@ features of a variety of types.
|
|||||||
visualizations and infrastructure specific to telemetry display.
|
visualizations and infrastructure specific to telemetry display.
|
||||||
|
|
||||||
## Client-Server Relationship
|
## Client-Server Relationship
|
||||||
Open MCT Web is client software - it runs entirely in the user's web browser. As
|
Open MCT is client software - it runs entirely in the user's web browser. As
|
||||||
such, it is largely 'server agnostic'; any web server capable of serving files
|
such, it is largely 'server agnostic'; any web server capable of serving files
|
||||||
from paths is capable of providing Open MCT Web.
|
from paths is capable of providing Open MCT.
|
||||||
|
|
||||||
While Open MCT Web can be configured to run as a standalone client, this is
|
While Open MCT can be configured to run as a standalone client, this is
|
||||||
rarely very useful. Instead, it is intended to be used as a display and
|
rarely very useful. Instead, it is intended to be used as a display and
|
||||||
interaction layer for information obtained from a variety of back-end services.
|
interaction layer for information obtained from a variety of back-end services.
|
||||||
Doing so requires authoring or utilizing adapter plugins which allow Open MCT
|
Doing so requires authoring or utilizing adapter plugins which allow Open MCT
|
||||||
Web to interact with these services.
|
Web to interact with these services.
|
||||||
|
|
||||||
Typically, the pattern here is to provide a known interface that Open MCT Web
|
Typically, the pattern here is to provide a known interface that Open MCT
|
||||||
can utilize, and implement it such that it interacts with whatever back-end
|
can utilize, and implement it such that it interacts with whatever back-end
|
||||||
provides the relevant information. Examples of back-ends that can be utilized in
|
provides the relevant information. Examples of back-ends that can be utilized in
|
||||||
this fashion include databases for the persistence of user-created objects, or
|
this fashion include databases for the persistence of user-created objects, or
|
||||||
@ -62,52 +63,52 @@ sources of telemetry data.
|
|||||||
See the [Architecture Guide](../architecture/index.md#Overview) for information
|
See the [Architecture Guide](../architecture/index.md#Overview) for information
|
||||||
on the client-server relationship.
|
on the client-server relationship.
|
||||||
|
|
||||||
## Developing with Open MCT Web
|
## Developing with Open MCT
|
||||||
Building applications with Open MCT Web typically means authoring and utilizing
|
Building applications with Open MCT typically means authoring and utilizing
|
||||||
a set of plugins which provide application-specific details about how Open MCT
|
a set of plugins which provide application-specific details about how Open MCT
|
||||||
Web should behave.
|
Web should behave.
|
||||||
|
|
||||||
### Technologies
|
### Technologies
|
||||||
|
|
||||||
Open MCT Web sources are written in JavaScript, with a number of configuration
|
Open MCT sources are written in JavaScript, with a number of configuration
|
||||||
files written in JSON. Displayable components are written in HTML5 and CSS3.
|
files written in JSON. Displayable components are written in HTML5 and CSS3.
|
||||||
Open MCT Web is built using [AngularJS](http://www.angularjs.org) from Google. A
|
Open MCT is built using [AngularJS](http://www.angularjs.org) from Google. A
|
||||||
good understanding of Angular is recommended for developers working with Open
|
good understanding of Angular is recommended for developers working with Open
|
||||||
MCT Web.
|
MCT Web.
|
||||||
|
|
||||||
### Forking
|
### Forking
|
||||||
Open MCT Web does not currently have a single stand-alone artifact that can be
|
Open MCT does not currently have a single stand-alone artifact that can be
|
||||||
used as a library. Instead, the recommended approach for creating a new
|
used as a library. Instead, the recommended approach for creating a new
|
||||||
application is to start by forking/branching Open MCT Web, and then adding new
|
application is to start by forking/branching Open MCT, and then adding new
|
||||||
features from there. Put another way, Open MCT Web's source structure is built
|
features from there. Put another way, Open MCT's source structure is built
|
||||||
to serve as a template for specific applications.
|
to serve as a template for specific applications.
|
||||||
|
|
||||||
Forking in this manner should not require that you edit Open MCT Web's sources.
|
Forking in this manner should not require that you edit Open MCT's sources.
|
||||||
The preferred approach is to create a new directory (peer to `index.html`) for
|
The preferred approach is to create a new directory (peer to `index.html`) for
|
||||||
the new application, then add new bundles (as described in the Framework
|
the new application, then add new bundles (as described in the Framework
|
||||||
chapter) within that directory.
|
chapter) within that directory.
|
||||||
|
|
||||||
To initially clone the Open MCT Web repository:
|
To initially clone the Open MCT repository:
|
||||||
`git clone <repository URL> <local repo directory> -b open-master`
|
`git clone <repository URL> <local repo directory> -b open-master`
|
||||||
|
|
||||||
To create a fork to begin working on a new application using Open MCT Web:
|
To create a fork to begin working on a new application using Open MCT:
|
||||||
|
|
||||||
cd <local repo directory>
|
cd <local repo directory>
|
||||||
git checkout open-master
|
git checkout open-master
|
||||||
git checkout -b <new branch name>
|
git checkout -b <new branch name>
|
||||||
|
|
||||||
As a convention used internally, applications built using Open MCT Web have
|
As a convention used internally, applications built using Open MCT have
|
||||||
master branch names with an identifying prefix. For instance, if building an
|
master branch names with an identifying prefix. For instance, if building an
|
||||||
application called 'Foo', the last statement above would look like:
|
application called 'Foo', the last statement above would look like:
|
||||||
|
|
||||||
git checkout -b foo-master
|
git checkout -b foo-master
|
||||||
|
|
||||||
This convention is not enforced or understood by Open MCT Web in any way; it is
|
This convention is not enforced or understood by Open MCT in any way; it is
|
||||||
mentioned here as a more general recommendation.
|
mentioned here as a more general recommendation.
|
||||||
|
|
||||||
# Overview
|
# Overview
|
||||||
|
|
||||||
Open MCT Web is implemented as a framework component which manages a set of
|
Open MCT is implemented as a framework component which manages a set of
|
||||||
other components. These components, called _bundles_, act as containers to group
|
other components. These components, called _bundles_, act as containers to group
|
||||||
sets of related functionality; individual units of functionality are expressed
|
sets of related functionality; individual units of functionality are expressed
|
||||||
within these bundles as _extensions_.
|
within these bundles as _extensions_.
|
||||||
@ -118,7 +119,7 @@ run-time to satisfy these declared dependency. This dependency injection
|
|||||||
approach allows software components which have been authored separately (e.g. as
|
approach allows software components which have been authored separately (e.g. as
|
||||||
plugins) but to collaborate at run-time.
|
plugins) but to collaborate at run-time.
|
||||||
|
|
||||||
Open MCT Web's framework layer is implemented on top of AngularJS's [dependency
|
Open MCT's framework layer is implemented on top of AngularJS's [dependency
|
||||||
injection mechanism](https://docs.angularjs.org/guide/di) and is modelled after
|
injection mechanism](https://docs.angularjs.org/guide/di) and is modelled after
|
||||||
[OSGi](hhttp://www.osgi.org/) and its [Declarative Services component model](http://wiki.osgi.org/wiki/Declarative_Services).
|
[OSGi](hhttp://www.osgi.org/) and its [Declarative Services component model](http://wiki.osgi.org/wiki/Declarative_Services).
|
||||||
In particular, this is where the term _bundle_ comes from.
|
In particular, this is where the term _bundle_ comes from.
|
||||||
@ -133,7 +134,7 @@ The framework is described in more detail in the [Framework Overview](../archite
|
|||||||
architecture guide.
|
architecture guide.
|
||||||
|
|
||||||
### Tiers
|
### Tiers
|
||||||
While all bundles in a running Open MCT Web instance are effectively peers, it
|
While all bundles in a running Open MCT instance are effectively peers, it
|
||||||
is useful to think of them as a tiered architecture, where each tier adds more
|
is useful to think of them as a tiered architecture, where each tier adds more
|
||||||
specificity to the application.
|
specificity to the application.
|
||||||
```nomnoml
|
```nomnoml
|
||||||
@ -151,7 +152,7 @@ It additionally interprets bundle definitions (see explanation below, as well as
|
|||||||
further detail in the Framework chapter.) At this tier, we are at our most
|
further detail in the Framework chapter.) At this tier, we are at our most
|
||||||
general: We know only that we are a plugin-based application.
|
general: We know only that we are a plugin-based application.
|
||||||
* __Platform__: Components in the Platform tier describe both the general user
|
* __Platform__: Components in the Platform tier describe both the general user
|
||||||
interface and corresponding developer-facing interfaces of Open MCT Web. This
|
interface and corresponding developer-facing interfaces of Open MCT. This
|
||||||
tier provides the general infrastructure for applications. It is less general
|
tier provides the general infrastructure for applications. It is less general
|
||||||
than the framework tier, insofar as this tier introduces a specific user
|
than the framework tier, insofar as this tier introduces a specific user
|
||||||
interface paradigm, but it is still non-specific as to what useful features
|
interface paradigm, but it is still non-specific as to what useful features
|
||||||
@ -159,7 +160,7 @@ will be provided. Although they can be removed or replaced easily, bundles
|
|||||||
provided by the Platform tier generally should not be thought of as optional.
|
provided by the Platform tier generally should not be thought of as optional.
|
||||||
* __Application__: The application tier consists of components which utilize the
|
* __Application__: The application tier consists of components which utilize the
|
||||||
infrastructure provided by the Platform to provide functionality which will (or
|
infrastructure provided by the Platform to provide functionality which will (or
|
||||||
could) be useful to specific applications built using Open MCT Web. These
|
could) be useful to specific applications built using Open MCT. These
|
||||||
include adapters to specific persistence back-ends (such as ElasticSearch or
|
include adapters to specific persistence back-ends (such as ElasticSearch or
|
||||||
CouchDB) as well as bundles which describe more user-facing features (such as
|
CouchDB) as well as bundles which describe more user-facing features (such as
|
||||||
_Plot_ views for visualizing time series data, or _Layout_ objects for
|
_Plot_ views for visualizing time series data, or _Layout_ objects for
|
||||||
@ -168,20 +169,20 @@ compromising basic application functionality, with the caveat that at least one
|
|||||||
persistence adapter needs to be present.
|
persistence adapter needs to be present.
|
||||||
* __Plugins__: Conceptually, this tier is not so different from the application
|
* __Plugins__: Conceptually, this tier is not so different from the application
|
||||||
tier; it consists of bundles describing new features, back-end adapters, that
|
tier; it consists of bundles describing new features, back-end adapters, that
|
||||||
are specific to the application being built on Open MCT Web. It is described as
|
are specific to the application being built on Open MCT. It is described as
|
||||||
a separate tier here because it has one important distinction from the
|
a separate tier here because it has one important distinction from the
|
||||||
application tier: It consists of bundles that are not included with the platform
|
application tier: It consists of bundles that are not included with the platform
|
||||||
(either authored anew for the specific application, or obtained from elsewhere.)
|
(either authored anew for the specific application, or obtained from elsewhere.)
|
||||||
|
|
||||||
Note that bundles in any tier can go off and consult back-end services. In
|
Note that bundles in any tier can go off and consult back-end services. In
|
||||||
practice, this responsibility is handled at the Application and/or Plugin tiers;
|
practice, this responsibility is handled at the Application and/or Plugin tiers;
|
||||||
Open MCT Web is built to be server-agnostic, so any back-end is considered an
|
Open MCT is built to be server-agnostic, so any back-end is considered an
|
||||||
application-specific detail.
|
application-specific detail.
|
||||||
|
|
||||||
## Platform Overview
|
## Platform Overview
|
||||||
|
|
||||||
The "tiered" architecture described in the preceding text describes a way of
|
The "tiered" architecture described in the preceding text describes a way of
|
||||||
thinking of and categorizing software components of a Open MCT Web application,
|
thinking of and categorizing software components of a Open MCT application,
|
||||||
as well as the framework layer's role in mediating between these components.
|
as well as the framework layer's role in mediating between these components.
|
||||||
Once the framework layer has wired these software components together, however,
|
Once the framework layer has wired these software components together, however,
|
||||||
the application's logical architecture emerges.
|
the application's logical architecture emerges.
|
||||||
@ -192,7 +193,7 @@ section of the Platform guide
|
|||||||
|
|
||||||
### Web Services
|
### Web Services
|
||||||
|
|
||||||
As mentioned in the Introduction, Open MCT Web is a platform single-page
|
As mentioned in the Introduction, Open MCT is a platform single-page
|
||||||
applications which runs entirely in the browser. Most applications will want to
|
applications which runs entirely in the browser. Most applications will want to
|
||||||
additionally interact with server-side resources, to (for example) read
|
additionally interact with server-side resources, to (for example) read
|
||||||
telemetry data or store user-created objects. This interaction is handled by
|
telemetry data or store user-created objects. This interaction is handled by
|
||||||
@ -205,7 +206,7 @@ individual bundles using APIs which are supported in browser (such as
|
|||||||
[Web Service #2] <- [Web Browser]
|
[Web Service #2] <- [Web Browser]
|
||||||
[Web Service #3] <- [Web Browser]
|
[Web Service #3] <- [Web Browser]
|
||||||
[<package> Web Browser |
|
[<package> Web Browser |
|
||||||
[<package> Open MCT Web |
|
[<package> Open MCT |
|
||||||
[Plugin Bundle #1]-->[Core API]
|
[Plugin Bundle #1]-->[Core API]
|
||||||
[Core API]<--[Plugin Bundle #2]
|
[Core API]<--[Plugin Bundle #2]
|
||||||
[Platform Bundle #1]-->[Core API]
|
[Platform Bundle #1]-->[Core API]
|
||||||
@ -215,16 +216,16 @@ individual bundles using APIs which are supported in browser (such as
|
|||||||
[Core API]<--[Platform Bundle #5]
|
[Core API]<--[Platform Bundle #5]
|
||||||
[Core API]<--[Plugin Bundle #3]
|
[Core API]<--[Plugin Bundle #3]
|
||||||
]
|
]
|
||||||
[Open MCT Web] ->[Browser APIs]
|
[Open MCT] ->[Browser APIs]
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
This architectural approach ensures a loose coupling between applications built
|
This architectural approach ensures a loose coupling between applications built
|
||||||
using Open MCT Web and the backends which support them.
|
using Open MCT and the backends which support them.
|
||||||
|
|
||||||
### Glossary
|
### Glossary
|
||||||
|
|
||||||
Certain terms are used throughout Open MCT Web with consistent meanings or
|
Certain terms are used throughout Open MCT with consistent meanings or
|
||||||
conventions. Other developer documentation, particularly in-line documentation,
|
conventions. Other developer documentation, particularly in-line documentation,
|
||||||
may presume an understanding of these terms.
|
may presume an understanding of these terms.
|
||||||
|
|
||||||
@ -246,7 +247,7 @@ readable description of a thing; usually a single sentence or short paragraph.
|
|||||||
(Most often used in the context of extensions, domain object models, or other
|
(Most often used in the context of extensions, domain object models, or other
|
||||||
similar application-specific objects.)
|
similar application-specific objects.)
|
||||||
* __domain object__: A meaningful object to the user; a distinct thing in the
|
* __domain object__: A meaningful object to the user; a distinct thing in the
|
||||||
work support by Open MCT Web. Anything that appears in the left-hand tree is a
|
work support by Open MCT. Anything that appears in the left-hand tree is a
|
||||||
domain object.
|
domain object.
|
||||||
* __extension__: An extension is a unit of functionality exposed to the platform
|
* __extension__: An extension is a unit of functionality exposed to the platform
|
||||||
in a declarative fashion by a bundle. The term 'extension category' is used to
|
in a declarative fashion by a bundle. The term 'extension category' is used to
|
||||||
@ -278,10 +279,10 @@ side-by-side without conflicting.
|
|||||||
|
|
||||||
# Framework
|
# Framework
|
||||||
|
|
||||||
Open MCT Web is built on the [AngularJS framework]( http://www.angularjs.org ). A
|
Open MCT is built on the [AngularJS framework]( http://www.angularjs.org ). A
|
||||||
good understanding of that framework is recommended.
|
good understanding of that framework is recommended.
|
||||||
|
|
||||||
Open MCT Web adds an extra layer on top of AngularJS to (a) generalize its
|
Open MCT adds an extra layer on top of AngularJS to (a) generalize its
|
||||||
dependency injection mechanism slightly, particularly to handle many-to-one
|
dependency injection mechanism slightly, particularly to handle many-to-one
|
||||||
relationships; and (b) handle script loading. Combined, these features become a
|
relationships; and (b) handle script loading. Combined, these features become a
|
||||||
plugin mechanism.
|
plugin mechanism.
|
||||||
@ -300,7 +301,7 @@ MCT Web.)
|
|||||||
are collected together in bundles, and may interact with other extensions.
|
are collected together in bundles, and may interact with other extensions.
|
||||||
|
|
||||||
The framework layer, loaded and initiated from `index.html`, is the main point
|
The framework layer, loaded and initiated from `index.html`, is the main point
|
||||||
of entry for an application built on Open MCT Web. It is responsible for wiring
|
of entry for an application built on Open MCT. It is responsible for wiring
|
||||||
together the application at run time (much of this responsibility is actually
|
together the application at run time (much of this responsibility is actually
|
||||||
delegated to Angular); at a high-level, the framework does this by proceeding
|
delegated to Angular); at a high-level, the framework does this by proceeding
|
||||||
through four stages:
|
through four stages:
|
||||||
@ -320,7 +321,7 @@ have been registered.
|
|||||||
|
|
||||||
## Bundles
|
## Bundles
|
||||||
|
|
||||||
The basic configurable unit of Open MCT Web is the _bundle_. This term has been
|
The basic configurable unit of Open MCT is the _bundle_. This term has been
|
||||||
used a bit already; now we'll get to a more formal definition.
|
used a bit already; now we'll get to a more formal definition.
|
||||||
|
|
||||||
A bundle is a directory which contains:
|
A bundle is a directory which contains:
|
||||||
@ -328,13 +329,13 @@ A bundle is a directory which contains:
|
|||||||
* A bundle definition; a file named `bundle.json`.
|
* A bundle definition; a file named `bundle.json`.
|
||||||
* Subdirectories for sources, resources, and tests.
|
* Subdirectories for sources, resources, and tests.
|
||||||
* Optionally, a `README.md` Markdown file describing its contents (this is not
|
* Optionally, a `README.md` Markdown file describing its contents (this is not
|
||||||
used by Open MCT Web in any way, but it's a helpful convention to follow.)
|
used by Open MCT in any way, but it's a helpful convention to follow.)
|
||||||
|
|
||||||
The bundle definition is the main point of entry for the bundle. The framework
|
The bundle definition is the main point of entry for the bundle. The framework
|
||||||
looks at this to determine which components need to be loaded and how they
|
looks at this to determine which components need to be loaded and how they
|
||||||
interact.
|
interact.
|
||||||
|
|
||||||
A plugin in Open MCT Web is a bundle. The platform itself is also decomposed
|
A plugin in Open MCT is a bundle. The platform itself is also decomposed
|
||||||
into bundles, each of which provides some category of functionality. The
|
into bundles, each of which provides some category of functionality. The
|
||||||
difference between a _bundle_ and a _plugin_ is purely a matter of the intended
|
difference between a _bundle_ and a _plugin_ is purely a matter of the intended
|
||||||
use; a plugin is just a bundle that is meant to be easily added or removed. When
|
use; a plugin is just a bundle that is meant to be easily added or removed. When
|
||||||
@ -355,7 +356,7 @@ For instance, if `bundles.json` contained:
|
|||||||
"example/extensions"
|
"example/extensions"
|
||||||
]
|
]
|
||||||
|
|
||||||
...then the Open MCT Web framework would look for bundle definitions at
|
...then the Open MCT framework would look for bundle definitions at
|
||||||
`example/builtins/bundle.json` and `example/extensions/bundle.json`, relative
|
`example/builtins/bundle.json` and `example/extensions/bundle.json`, relative
|
||||||
to the path of `index.html`. No other bundles would be loaded.
|
to the path of `index.html`. No other bundles would be loaded.
|
||||||
|
|
||||||
@ -456,7 +457,7 @@ arrays of extension definitions.
|
|||||||
### General Extensions
|
### General Extensions
|
||||||
|
|
||||||
Extensions are intended as a general-purpose mechanism for adding new types of
|
Extensions are intended as a general-purpose mechanism for adding new types of
|
||||||
functionality to Open MCT Web.
|
functionality to Open MCT.
|
||||||
|
|
||||||
An extension category is registered with Angular under the name of the
|
An extension category is registered with Angular under the name of the
|
||||||
extension, plus a suffix of two square brackets; so, an Angular service (or,
|
extension, plus a suffix of two square brackets; so, an Angular service (or,
|
||||||
@ -465,7 +466,7 @@ extensions, from all bundles, by including this string (e.g. `types[]` to get
|
|||||||
all type definitions) in a dependency declaration.
|
all type definitions) in a dependency declaration.
|
||||||
|
|
||||||
As a convention, extension categories are given single-word, plural nouns for
|
As a convention, extension categories are given single-word, plural nouns for
|
||||||
names within Open MCT Web (e.g. `types`.) This convention is not enforced by the
|
names within Open MCT (e.g. `types`.) This convention is not enforced by the
|
||||||
platform in any way. For extension categories introduced by external plugins, it
|
platform in any way. For extension categories introduced by external plugins, it
|
||||||
is recommended to prefix the extension category with a vendor identifier (or
|
is recommended to prefix the extension category with a vendor identifier (or
|
||||||
similar) followed by a dot, to avoid collisions.
|
similar) followed by a dot, to avoid collisions.
|
||||||
@ -504,7 +505,7 @@ the Angular-supported method for dependency injection is (effectively)
|
|||||||
constructor-style injection; so, both declared dependencies and run-time
|
constructor-style injection; so, both declared dependencies and run-time
|
||||||
arguments are competing for space in a constructor's arguments.
|
arguments are competing for space in a constructor's arguments.
|
||||||
|
|
||||||
To resolve this, the Open MCT Web framework registers extension instances in a
|
To resolve this, the Open MCT framework registers extension instances in a
|
||||||
partially constructed form. That is, the constructor exposed by the extension's
|
partially constructed form. That is, the constructor exposed by the extension's
|
||||||
implementation is effectively decomposed into two calls; the first takes the
|
implementation is effectively decomposed into two calls; the first takes the
|
||||||
dependencies, and returns the constructor in its second form, which takes the
|
dependencies, and returns the constructor in its second form, which takes the
|
||||||
@ -548,7 +549,7 @@ sorted according to these conventions when using them.
|
|||||||
### Angular Built-ins
|
### Angular Built-ins
|
||||||
|
|
||||||
Several entities supported Angular are expressed and managed as extensions in
|
Several entities supported Angular are expressed and managed as extensions in
|
||||||
Open MCT Web. Specifically, these extension categories are _directives_,
|
Open MCT. Specifically, these extension categories are _directives_,
|
||||||
_controllers_, _services_, _constants_, _runs_, and _routes_.
|
_controllers_, _services_, _constants_, _runs_, and _routes_.
|
||||||
|
|
||||||
#### Angular Directives
|
#### Angular Directives
|
||||||
@ -591,7 +592,7 @@ property value , which is the constant value that will be registered.
|
|||||||
In some cases, you want to register code to run as soon as the application
|
In some cases, you want to register code to run as soon as the application
|
||||||
starts; these can be registered as extensions of the [ runs category](https://docs.angularjs.org/api/ng/type/angular.Module#run ).
|
starts; these can be registered as extensions of the [ runs category](https://docs.angularjs.org/api/ng/type/angular.Module#run ).
|
||||||
Implementations registered in this category will be invoked (with their declared
|
Implementations registered in this category will be invoked (with their declared
|
||||||
dependencies) when the Open MCT Web application first starts. (Note that, in
|
dependencies) when the Open MCT application first starts. (Note that, in
|
||||||
this case, the implementation is better thought of as just a function, as
|
this case, the implementation is better thought of as just a function, as
|
||||||
opposed to a constructor function.)
|
opposed to a constructor function.)
|
||||||
|
|
||||||
@ -626,13 +627,13 @@ providers of the same service (that is, with matching `provides` properties);
|
|||||||
for a decorator, this will be whichever provider, decorator, or aggregator is
|
for a decorator, this will be whichever provider, decorator, or aggregator is
|
||||||
next in the sequence of decorators.
|
next in the sequence of decorators.
|
||||||
|
|
||||||
Services exposed by the Open MCT Web platform are often declared as composite
|
Services exposed by the Open MCT platform are often declared as composite
|
||||||
services, as this form is open for a variety of common modifications.
|
services, as this form is open for a variety of common modifications.
|
||||||
|
|
||||||
# Core API
|
# Core API
|
||||||
|
|
||||||
Most of Open MCT Web's relevant API is provided and/or mediated by the
|
Most of Open MCT's relevant API is provided and/or mediated by the
|
||||||
framework; that is, much of developing for Open MCT Web is a matter of adding
|
framework; that is, much of developing for Open MCT is a matter of adding
|
||||||
extensions which access other parts of the platform by means of dependency
|
extensions which access other parts of the platform by means of dependency
|
||||||
injection.
|
injection.
|
||||||
|
|
||||||
@ -641,9 +642,9 @@ to be passed along by other services.
|
|||||||
|
|
||||||
## Domain Objects
|
## Domain Objects
|
||||||
|
|
||||||
Domain objects are the most fundamental component of Open MCT Web's information
|
Domain objects are the most fundamental component of Open MCT's information
|
||||||
model. A domain object is some distinct thing relevant to a user's work flow,
|
model. A domain object is some distinct thing relevant to a user's work flow,
|
||||||
such as a telemetry channel, display, or similar. Open MCT Web is a tool for
|
such as a telemetry channel, display, or similar. Open MCT is a tool for
|
||||||
viewing, browsing, manipulating, and otherwise interacting with a graph of
|
viewing, browsing, manipulating, and otherwise interacting with a graph of
|
||||||
domain objects.
|
domain objects.
|
||||||
|
|
||||||
@ -680,7 +681,7 @@ exposed.
|
|||||||
### Identifier Syntax
|
### Identifier Syntax
|
||||||
|
|
||||||
For most purposes, a domain object identifier can be treated as a purely
|
For most purposes, a domain object identifier can be treated as a purely
|
||||||
symbolic string; these are typically generated by Open MCT Web and plug-ins
|
symbolic string; these are typically generated by Open MCT and plug-ins
|
||||||
should rarely be concerned with its internal structure.
|
should rarely be concerned with its internal structure.
|
||||||
|
|
||||||
A domain object identifier has one or two parts, separated by a colon.
|
A domain object identifier has one or two parts, separated by a colon.
|
||||||
@ -723,7 +724,7 @@ exposed it to be removed from its container.
|
|||||||
containing:
|
containing:
|
||||||
* `name`: Human-readable name.
|
* `name`: Human-readable name.
|
||||||
* `description`: Human-readable summary of this action.
|
* `description`: Human-readable summary of this action.
|
||||||
* `glyph`: Single character to be displayed in Open MCT Web's icon font set.
|
* `glyph`: Single character to be displayed in Open MCT's icon font set.
|
||||||
* `context`: The context in which this action is being performed (see below)
|
* `context`: The context in which this action is being performed (see below)
|
||||||
|
|
||||||
Action instances are typically obtained via a domain object's `action`
|
Action instances are typically obtained via a domain object's `action`
|
||||||
@ -739,7 +740,7 @@ dragged object in a drag-and-drop operation.)
|
|||||||
|
|
||||||
## Telemetry
|
## Telemetry
|
||||||
|
|
||||||
Telemetry series data in Open MCT Web is represented by a common interface, and
|
Telemetry series data in Open MCT is represented by a common interface, and
|
||||||
packaged in a consistent manner to facilitate passing telemetry updates around
|
packaged in a consistent manner to facilitate passing telemetry updates around
|
||||||
multiple visualizations.
|
multiple visualizations.
|
||||||
|
|
||||||
@ -752,7 +753,7 @@ is useful when multiple distinct data sources are in use side-by-side.
|
|||||||
* `key`: A machine-readable identifier for a unique series of telemetry within
|
* `key`: A machine-readable identifier for a unique series of telemetry within
|
||||||
that source.
|
that source.
|
||||||
* _Note: This API is still under development; additional properties, such as
|
* _Note: This API is still under development; additional properties, such as
|
||||||
start and end time, should be present in future versions of Open MCT Web._
|
start and end time, should be present in future versions of Open MCT._
|
||||||
|
|
||||||
Additional properties may be included in telemetry requests which have specific
|
Additional properties may be included in telemetry requests which have specific
|
||||||
interpretations for specific sources.
|
interpretations for specific sources.
|
||||||
@ -776,7 +777,7 @@ not. (Typically, domain values are interpreted as UTC timestamps in milliseconds
|
|||||||
relative to the UNIX epoch.) A series must have at least one domain and one
|
relative to the UNIX epoch.) A series must have at least one domain and one
|
||||||
range, and may have more than one.
|
range, and may have more than one.
|
||||||
|
|
||||||
Telemetry series data in Open MCT Web is expressed via the following
|
Telemetry series data in Open MCT is expressed via the following
|
||||||
`TelemetrySeries` interface:
|
`TelemetrySeries` interface:
|
||||||
|
|
||||||
* `getPointCount()`: Returns the number of unique points/samples in this series.
|
* `getPointCount()`: Returns the number of unique points/samples in this series.
|
||||||
@ -815,7 +816,7 @@ interface:
|
|||||||
* `getName()`: Get the human-readable name for this type.
|
* `getName()`: Get the human-readable name for this type.
|
||||||
* `getDescription()`: Get a human-readable summary of this type.
|
* `getDescription()`: Get a human-readable summary of this type.
|
||||||
* `getGlyph()`: Get the single character to be rendered as an icon for this type
|
* `getGlyph()`: Get the single character to be rendered as an icon for this type
|
||||||
in Open MCT Web's custom font set.
|
in Open MCT's custom font set.
|
||||||
* `getInitialModel()`: Get a domain object model that represents the initial
|
* `getInitialModel()`: Get a domain object model that represents the initial
|
||||||
state (before user specification of properties) for domain objects of this type.
|
state (before user specification of properties) for domain objects of this type.
|
||||||
* `getDefinition()`: Get the extension definition for this type, as a JavaScript
|
* `getDefinition()`: Get the extension definition for this type, as a JavaScript
|
||||||
@ -831,7 +832,7 @@ an array of `TypeProperty` instances.
|
|||||||
### Type Features
|
### Type Features
|
||||||
|
|
||||||
Features of a domain object type are expressed as symbolic string identifiers.
|
Features of a domain object type are expressed as symbolic string identifiers.
|
||||||
They are defined in practice by usage; currently, the Open MCT Web platform only
|
They are defined in practice by usage; currently, the Open MCT platform only
|
||||||
uses the creation feature to determine which domain object types should appear
|
uses the creation feature to determine which domain object types should appear
|
||||||
in the Create menu.
|
in the Create menu.
|
||||||
|
|
||||||
@ -885,7 +886,7 @@ Categories supported by the platform include:
|
|||||||
* `key`: A machine-readable identifier for this action.
|
* `key`: A machine-readable identifier for this action.
|
||||||
* `name`: A human-readable name for this action (e.g. to show in a menu)
|
* `name`: A human-readable name for this action (e.g. to show in a menu)
|
||||||
* `description`: A human-readable summary of the behavior of this action.
|
* `description`: A human-readable summary of the behavior of this action.
|
||||||
* `glyph`: A single character which will be rendered in Open MCT Web's custom
|
* `glyph`: A single character which will be rendered in Open MCT's custom
|
||||||
font set as an icon for this action.
|
font set as an icon for this action.
|
||||||
|
|
||||||
## Capabilities Category
|
## Capabilities Category
|
||||||
@ -996,7 +997,7 @@ of unremoved listeners.
|
|||||||
## Indicators Category
|
## Indicators Category
|
||||||
|
|
||||||
An indicator is an element that should appear in the status area at the bottom
|
An indicator is an element that should appear in the status area at the bottom
|
||||||
of a running Open MCT Web client instance.
|
of a running Open MCT client instance.
|
||||||
|
|
||||||
### Standard Indicators
|
### Standard Indicators
|
||||||
|
|
||||||
@ -1006,7 +1007,7 @@ provide implementations with the following methods:
|
|||||||
* `getText()`: Provides the human-readable text that will be displayed for this
|
* `getText()`: Provides the human-readable text that will be displayed for this
|
||||||
indicator.
|
indicator.
|
||||||
* `getGlyph()`: Provides a single-character string that will be displayed as an
|
* `getGlyph()`: Provides a single-character string that will be displayed as an
|
||||||
icon in Open MCT Web's custom font set.
|
icon in Open MCT's custom font set.
|
||||||
* `getDescription()`: Provides a human-readable summary of the current state of
|
* `getDescription()`: Provides a human-readable summary of the current state of
|
||||||
this indicator; will be displayed in a tooltip on hover.
|
this indicator; will be displayed in a tooltip on hover.
|
||||||
* `getClass()`: Get a CSS class that will be applied to this indicator.
|
* `getClass()`: Get a CSS class that will be applied to this indicator.
|
||||||
@ -1032,7 +1033,7 @@ this variety do not need to provide an implementation.
|
|||||||
## Licenses Category
|
## Licenses Category
|
||||||
|
|
||||||
The extension category `licenses` can be used to add entries into the 'Licensing
|
The extension category `licenses` can be used to add entries into the 'Licensing
|
||||||
information' page, reachable from Open MCT Web's About dialog.
|
information' page, reachable from Open MCT's About dialog.
|
||||||
|
|
||||||
Licenses may have the following properties, all of which are strings:
|
Licenses may have the following properties, all of which are strings:
|
||||||
|
|
||||||
@ -1045,11 +1046,11 @@ Licenses may have the following properties, all of which are strings:
|
|||||||
|
|
||||||
## Policies Category
|
## Policies Category
|
||||||
|
|
||||||
Policies are used to handle decisions made using Open MCT Web's `policyService`;
|
Policies are used to handle decisions made using Open MCT's `policyService`;
|
||||||
examples of these decisions are determining the applicability of certain
|
examples of these decisions are determining the applicability of certain
|
||||||
actions, or checking whether or not a domain object of one type can contain a
|
actions, or checking whether or not a domain object of one type can contain a
|
||||||
domain object of a different type. See the section on the Policies for an
|
domain object of a different type. See the section on the Policies for an
|
||||||
overview of Open MCT Web's policy model.
|
overview of Open MCT's policy model.
|
||||||
|
|
||||||
A policy's extension definition should include:
|
A policy's extension definition should include:
|
||||||
|
|
||||||
@ -1065,7 +1066,7 @@ context)`. The specific types used for `candidate` and `context` vary by policy
|
|||||||
category; in general, what is being asked is 'is this candidate allowed in this
|
category; in general, what is being asked is 'is this candidate allowed in this
|
||||||
context?' This method should return a boolean value.
|
context?' This method should return a boolean value.
|
||||||
|
|
||||||
Open MCT Web's policy model requires consensus; a policy decision is allowed
|
Open MCT's policy model requires consensus; a policy decision is allowed
|
||||||
when and only when all policies choose to allow it. As such, policies should
|
when and only when all policies choose to allow it. As such, policies should
|
||||||
generally be written to reject a certain case, and allow (by returning `true`)
|
generally be written to reject a certain case, and allow (by returning `true`)
|
||||||
anything else.
|
anything else.
|
||||||
@ -1194,7 +1195,7 @@ Templates do not have implementations.
|
|||||||
## Types Category
|
## Types Category
|
||||||
|
|
||||||
The types extension category describes types of domain objects which may
|
The types extension category describes types of domain objects which may
|
||||||
appear within Open MCT Web.
|
appear within Open MCT.
|
||||||
|
|
||||||
A type's extension definition should have the following properties:
|
A type's extension definition should have the following properties:
|
||||||
|
|
||||||
@ -1202,7 +1203,7 @@ A type's extension definition should have the following properties:
|
|||||||
stored to and matched against the type property of domain object models.
|
stored to and matched against the type property of domain object models.
|
||||||
* `name`: The human-readable name for this domain object type.
|
* `name`: The human-readable name for this domain object type.
|
||||||
* `description`: A human-readable summary of this domain object type.
|
* `description`: A human-readable summary of this domain object type.
|
||||||
* `glyph`: A single character to be rendered as an icon in Open MCT Web's custom
|
* `glyph`: A single character to be rendered as an icon in Open MCT's custom
|
||||||
font set.
|
font set.
|
||||||
* `model`: A domain object model, used as the initial state for created domain
|
* `model`: A domain object model, used as the initial state for created domain
|
||||||
objects of this type (before any properties are specified.)
|
objects of this type (before any properties are specified.)
|
||||||
@ -1251,7 +1252,7 @@ utilized via `mct-representation`); additionally:
|
|||||||
|
|
||||||
* `name`: The human-readable name for this view type.
|
* `name`: The human-readable name for this view type.
|
||||||
* description : A human-readable summary of this view type.
|
* description : A human-readable summary of this view type.
|
||||||
* `glyph`: A single character to be rendered as an icon in Open MCT Web's custom
|
* `glyph`: A single character to be rendered as an icon in Open MCT's custom
|
||||||
font set.
|
font set.
|
||||||
* `type`: Optional; if present, this representation is only applicable for
|
* `type`: Optional; if present, this representation is only applicable for
|
||||||
domain object's of this type.
|
domain object's of this type.
|
||||||
@ -1293,7 +1294,7 @@ are visible, and what state they manage and/or behavior they invoke.
|
|||||||
|
|
||||||
This set may contain up to two different objects: The _view proxy_, which is
|
This set may contain up to two different objects: The _view proxy_, which is
|
||||||
used to make changes to the view as a whole, and the _selected object_, which is
|
used to make changes to the view as a whole, and the _selected object_, which is
|
||||||
used to represent some state within the view. (Future versions of Open MCT Web
|
used to represent some state within the view. (Future versions of Open MCT
|
||||||
may support multiple selected objects.)
|
may support multiple selected objects.)
|
||||||
|
|
||||||
The `selection` object made available during Edit mode has the following
|
The `selection` object made available during Edit mode has the following
|
||||||
@ -1329,14 +1330,14 @@ are supported:
|
|||||||
|
|
||||||
# Directives
|
# Directives
|
||||||
|
|
||||||
Open MCT Web defines several Angular directives that are intended for use both
|
Open MCT defines several Angular directives that are intended for use both
|
||||||
internally within the platform, and by plugins.
|
internally within the platform, and by plugins.
|
||||||
|
|
||||||
## Before Unload
|
## Before Unload
|
||||||
|
|
||||||
The `mct-before-unload` directive is used to listen for (and prompt for user
|
The `mct-before-unload` directive is used to listen for (and prompt for user
|
||||||
confirmation) of navigation changes in the browser. This includes reloading,
|
confirmation) of navigation changes in the browser. This includes reloading,
|
||||||
following links out of Open MCT Web, or changing routes. It is used to hook into
|
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
|
both `onbeforeunload` event handling as well as route changes from within
|
||||||
Angular.
|
Angular.
|
||||||
|
|
||||||
@ -1448,7 +1449,7 @@ Passed as plain text in the attribute.
|
|||||||
|
|
||||||
### Form Structure
|
### Form Structure
|
||||||
|
|
||||||
Forms in Open MCT Web have a common structure to permit consistent display. A
|
Forms in Open MCT have a common structure to permit consistent display. A
|
||||||
form is broken down into sections, which will be displayed in groups; each
|
form is broken down into sections, which will be displayed in groups; each
|
||||||
section is broken down into rows, each of which provides a control for a single
|
section is broken down into rows, each of which provides a control for a single
|
||||||
property. Input from this form is two-way bound to the object passed via
|
property. Input from this form is two-way bound to the object passed via
|
||||||
@ -1600,9 +1601,64 @@ there are items .
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
## Table
|
||||||
|
|
||||||
|
The `mct-table` directive provides a generic table component, with optional
|
||||||
|
sorting and filtering capabilities. The table can be pre-populated with data
|
||||||
|
by setting the `rows` parameter, and it can be updated in real-time using the
|
||||||
|
`add:row` and `remove:row` broadcast events. The table will expand to occupy
|
||||||
|
100% of the size of its containing element. The table is highly optimized for
|
||||||
|
very large data sets.
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
The table supports two events for notifying that the rows have changed. For
|
||||||
|
performance reasons, the table does not monitor the content of `rows`
|
||||||
|
constantly.
|
||||||
|
|
||||||
|
* `add:row`: A `$broadcast` event that will notify the table that a new row
|
||||||
|
has been added to the table.
|
||||||
|
|
||||||
|
eg. The code below adds a new row, and alerts the table using the `add:row`
|
||||||
|
event. Sorting and filtering will be applied automatically by the table component.
|
||||||
|
|
||||||
|
```
|
||||||
|
$scope.rows.push(newRow);
|
||||||
|
$scope.$broadcast('add:row', $scope.rows.length-1);
|
||||||
|
```
|
||||||
|
|
||||||
|
* `remove:row`: A `$broadcast` event that will notify the table that a row
|
||||||
|
should be removed from the table.
|
||||||
|
|
||||||
|
eg. The code below removes a row from the rows array, and then alerts the table
|
||||||
|
to its removal.
|
||||||
|
|
||||||
|
```
|
||||||
|
$scope.rows.slice(5, 1);
|
||||||
|
$scope.$broadcast('remove:row', 5);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
* `headers`: An array of string values which will constitute the column titles
|
||||||
|
that appear at the top of the table. Corresponding values are specified in
|
||||||
|
the rows using the header title provided here.
|
||||||
|
* `rows`: An array of objects containing row values. Each element in the
|
||||||
|
array must be an associative array, where the key corresponds to a column header.
|
||||||
|
* `enableFilter`: A boolean that if true, will enable searching and result
|
||||||
|
filtering. When enabled, each column will have a text input field that can be
|
||||||
|
used to filter the table rows in real time.
|
||||||
|
* `enableSort`: A boolean determining whether rows can be sorted. If true,
|
||||||
|
sorting will be enabled allowing sorting by clicking on column headers. Only
|
||||||
|
one column may be sorted at a time.
|
||||||
|
* `autoScroll`: A boolean value that if true, will cause the table to automatically
|
||||||
|
scroll to the bottom as new data arrives. Auto-scroll can be disengaged manually
|
||||||
|
by scrolling away from the bottom of the table, and can also be enabled manually
|
||||||
|
by scrolling to the bottom of the table rows.
|
||||||
|
|
||||||
# Services
|
# Services
|
||||||
|
|
||||||
The Open MCT Web platform provides a variety of services which can be retrieved
|
The Open MCT platform provides a variety of services which can be retrieved
|
||||||
and utilized via dependency injection. These services fall into two categories:
|
and utilized via dependency injection. These services fall into two categories:
|
||||||
|
|
||||||
* _Composite Services_ are defined by a set of components extensions; plugins may
|
* _Composite Services_ are defined by a set of components extensions; plugins may
|
||||||
@ -1614,7 +1670,7 @@ utilized by plugins but are not intended to be modified or augmented.
|
|||||||
|
|
||||||
## Composite Type Services
|
## Composite Type Services
|
||||||
|
|
||||||
This section describes the composite services exposed by Open MCT Web,
|
This section describes the composite services exposed by Open MCT,
|
||||||
specifically focusing on their interface and contract.
|
specifically focusing on their interface and contract.
|
||||||
|
|
||||||
In many cases, the platform will include a provider for a service which consumes
|
In many cases, the platform will include a provider for a service which consumes
|
||||||
@ -1932,7 +1988,7 @@ The `workerService` may be used to run web workers defined via the
|
|||||||
as a shared worker); if the `key` is unknown, returns `undefined`.
|
as a shared worker); if the `key` is unknown, returns `undefined`.
|
||||||
|
|
||||||
# Models
|
# Models
|
||||||
Domain object models in Open MCT Web are JavaScript objects describing the
|
Domain object models in Open MCT are JavaScript objects describing the
|
||||||
persistent state of the domain objects they describe. Their contents include a
|
persistent state of the domain objects they describe. Their contents include a
|
||||||
mix of commonly understood metadata attributes; attributes which are recognized
|
mix of commonly understood metadata attributes; attributes which are recognized
|
||||||
by and/or determine the applicability of specific extensions; and properties
|
by and/or determine the applicability of specific extensions; and properties
|
||||||
@ -1948,7 +2004,7 @@ MCT Web and can be utilized directly:
|
|||||||
## Extension-specific Properties
|
## Extension-specific Properties
|
||||||
|
|
||||||
Other properties of domain object models have specific meaning imposed by other
|
Other properties of domain object models have specific meaning imposed by other
|
||||||
extensions within the Open MCT Web platform.
|
extensions within the Open MCT platform.
|
||||||
|
|
||||||
### Capability-specific Properties
|
### Capability-specific Properties
|
||||||
|
|
||||||
@ -2232,7 +2288,7 @@ way of its `composition` capability.)
|
|||||||
|
|
||||||
# Policies
|
# Policies
|
||||||
|
|
||||||
Policies are consulted to determine when certain behavior in Open MCT Web is
|
Policies are consulted to determine when certain behavior in Open MCT is
|
||||||
allowed. Policy questions are assigned to certain categories, which broadly
|
allowed. Policy questions are assigned to certain categories, which broadly
|
||||||
describe the type of decision being made; within each category, policies have a
|
describe the type of decision being made; within each category, policies have a
|
||||||
candidate (the thing which may or may not be allowed) and, optionally, a context
|
candidate (the thing which may or may not be allowed) and, optionally, a context
|
||||||
@ -2257,13 +2313,13 @@ The candidate argument is the view's extension definition; the context argument
|
|||||||
is the `DomainObject` to be viewed.
|
is the `DomainObject` to be viewed.
|
||||||
|
|
||||||
# Build-Test-Deploy
|
# Build-Test-Deploy
|
||||||
Open MCT Web is designed to support a broad variety of build and deployment
|
Open MCT is designed to support a broad variety of build and deployment
|
||||||
options. The sources can be deployed in the same directory structure used during
|
options. The sources can be deployed in the same directory structure used during
|
||||||
development. A few utilities are included to support development processes.
|
development. A few utilities are included to support development processes.
|
||||||
|
|
||||||
## Command-line Build
|
## Command-line Build
|
||||||
|
|
||||||
Open MCT Web is built using [`npm`](http://npmjs.com/)
|
Open MCT is built using [`npm`](http://npmjs.com/)
|
||||||
and [`gulp`](http://gulpjs.com/).
|
and [`gulp`](http://gulpjs.com/).
|
||||||
|
|
||||||
To install build dependencies (only needs to be run once):
|
To install build dependencies (only needs to be run once):
|
||||||
@ -2275,12 +2331,12 @@ To build:
|
|||||||
`npm run prepublish`
|
`npm run prepublish`
|
||||||
|
|
||||||
This will compile and minify JavaScript sources, as well as copy over assets.
|
This will compile and minify JavaScript sources, as well as copy over assets.
|
||||||
The contents of the `dist` folder will contain a runnable Open MCT Web
|
The contents of the `dist` folder will contain a runnable Open MCT
|
||||||
instance (e.g. by starting an HTTP server in that directory), including:
|
instance (e.g. by starting an HTTP server in that directory), including:
|
||||||
|
|
||||||
* A `main.js` file containing Open MCT Web source code.
|
* A `main.js` file containing Open MCT source code.
|
||||||
* Various assets in the `example` and `platform` directories.
|
* Various assets in the `example` and `platform` directories.
|
||||||
* An `index.html` that runs Open MCT Web in its default configuration.
|
* An `index.html` that runs Open MCT in its default configuration.
|
||||||
|
|
||||||
Additional `gulp` tasks are defined in [the gulpfile](gulpfile.js).
|
Additional `gulp` tasks are defined in [the gulpfile](gulpfile.js).
|
||||||
|
|
||||||
@ -2289,7 +2345,7 @@ download build dependencies.
|
|||||||
|
|
||||||
## Test Suite
|
## Test Suite
|
||||||
|
|
||||||
Open MCT Web uses [Jasmine 1.3](http://jasmine.github.io/) and
|
Open MCT uses [Jasmine 1.3](http://jasmine.github.io/) and
|
||||||
[Karma](http://karma-runner.github.io) for automated testing.
|
[Karma](http://karma-runner.github.io) for automated testing.
|
||||||
|
|
||||||
The test suite is configured to load any scripts ending with `Spec.js` found
|
The test suite is configured to load any scripts ending with `Spec.js` found
|
||||||
@ -2327,8 +2383,8 @@ information using [Blanket.JS](http://blanketjs.org/) and display this at the
|
|||||||
bottom of the screen. Currently, only statement coverage is displayed.
|
bottom of the screen. Currently, only statement coverage is displayed.
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
Open MCT Web is built to be flexible in terms of the deployment strategies it
|
Open MCT is built to be flexible in terms of the deployment strategies it
|
||||||
supports. In order to run in the browser, Open MCT Web needs:
|
supports. In order to run in the browser, Open MCT needs:
|
||||||
|
|
||||||
1. HTTP access to sources/resources for the framework, platform, and all active
|
1. HTTP access to sources/resources for the framework, platform, and all active
|
||||||
bundles.
|
bundles.
|
||||||
@ -2337,13 +2393,13 @@ external services need to support HTTP or some other web-accessible interface,
|
|||||||
like WebSockets.)
|
like WebSockets.)
|
||||||
|
|
||||||
Any HTTP server capable of serving flat files is sufficient for the first point.
|
Any HTTP server capable of serving flat files is sufficient for the first point.
|
||||||
The command-line build also packages Open MCT Web into a `.war` file for easier
|
The command-line build also packages Open MCT into a `.war` file for easier
|
||||||
deployment on containers such as Apache Tomcat.
|
deployment on containers such as Apache Tomcat.
|
||||||
|
|
||||||
The second point may be less flexible, as it depends upon the specific services
|
The second point may be less flexible, as it depends upon the specific services
|
||||||
to be utilized by Open MCT Web. Because of this, it is often the set of external
|
to be utilized by Open MCT. Because of this, it is often the set of external
|
||||||
services (and the manner in which they are exposed) that determine how to deploy
|
services (and the manner in which they are exposed) that determine how to deploy
|
||||||
Open MCT Web.
|
Open MCT.
|
||||||
|
|
||||||
One important constraint to consider in this context is the browser's same
|
One important constraint to consider in this context is the browser's same
|
||||||
origin policy. If external services are not on the same apparent host and port
|
origin policy. If external services are not on the same apparent host and port
|
||||||
@ -2360,7 +2416,7 @@ configuration does not create a security vulnerability.
|
|||||||
Examples of deployment strategies (and the conditions under which they make the
|
Examples of deployment strategies (and the conditions under which they make the
|
||||||
most sense) include:
|
most sense) include:
|
||||||
|
|
||||||
* If the external services that Open MCT Web will utilize are all running on
|
* If the external services that Open MCT will utilize are all running on
|
||||||
[Apache Tomcat](https://tomcat.apache.org/), then it makes sense to run Open
|
[Apache Tomcat](https://tomcat.apache.org/), then it makes sense to run Open
|
||||||
MCT Web from the same Tomcat instance as a separate web application. The
|
MCT Web from the same Tomcat instance as a separate web application. The
|
||||||
`.war` artifact produced by the command line build facilitates this deployment
|
`.war` artifact produced by the command line build facilitates this deployment
|
||||||
@ -2371,28 +2427,28 @@ hosts/ports, then it may make sense to use a web server that supports proxying,
|
|||||||
such as the [Apache HTTP Server](http://httpd.apache.org/). In this
|
such as the [Apache HTTP Server](http://httpd.apache.org/). In this
|
||||||
configuration, the HTTP server would be configured to proxy (or reverse proxy)
|
configuration, the HTTP server would be configured to proxy (or reverse proxy)
|
||||||
requests at specific paths to the various external services, while providing
|
requests at specific paths to the various external services, while providing
|
||||||
Open MCT Web as flat files from a different path.
|
Open MCT as flat files from a different path.
|
||||||
* If a single server component is being developed to handle all server-side
|
* If a single server component is being developed to handle all server-side
|
||||||
needs of an Open MCT Web instance, it can make sense to serve Open MCT Web (as
|
needs of an Open MCT instance, it can make sense to serve Open MCT (as
|
||||||
flat files) from the same component using an embedded HTTP server such as
|
flat files) from the same component using an embedded HTTP server such as
|
||||||
[Nancy](http://nancyfx.org/).
|
[Nancy](http://nancyfx.org/).
|
||||||
* If no external services are needed (or if the 'external services' will just
|
* If no external services are needed (or if the 'external services' will just
|
||||||
be generating flat files to read) it makes sense to utilize a lightweight flat
|
be generating flat files to read) it makes sense to utilize a lightweight flat
|
||||||
file HTTP server such as [Lighttpd](http://www.lighttpd.net/). In this
|
file HTTP server such as [Lighttpd](http://www.lighttpd.net/). In this
|
||||||
configuration, Open MCT Web sources/resources would be placed at one path, while
|
configuration, Open MCT sources/resources would be placed at one path, while
|
||||||
the files generated by the external service are placed at another path.
|
the files generated by the external service are placed at another path.
|
||||||
* If all external services support CORS, it may make sense to have an HTTP
|
* If all external services support CORS, it may make sense to have an HTTP
|
||||||
server that is solely responsible for making Open MCT Web sources/resources
|
server that is solely responsible for making Open MCT sources/resources
|
||||||
available, and to have Open MCT Web contact these external services directly.
|
available, and to have Open MCT contact these external services directly.
|
||||||
Again, lightweight HTTP servers such as [Lighttpd](http://www.lighttpd.net/)
|
Again, lightweight HTTP servers such as [Lighttpd](http://www.lighttpd.net/)
|
||||||
are useful in this circumstance. The downside of this option is that additional
|
are useful in this circumstance. The downside of this option is that additional
|
||||||
configuration effort is required, both to enable CORS on the external services,
|
configuration effort is required, both to enable CORS on the external services,
|
||||||
and to ensure that Open MCT Web can correctly locate these services.
|
and to ensure that Open MCT can correctly locate these services.
|
||||||
|
|
||||||
Another important consideration is authentication. By design, Open MCT Web does
|
Another important consideration is authentication. By design, Open MCT does
|
||||||
not handle user authentication. Instead, this should typically be treated as a
|
not handle user authentication. Instead, this should typically be treated as a
|
||||||
deployment-time concern, where authentication is handled by the HTTP server
|
deployment-time concern, where authentication is handled by the HTTP server
|
||||||
which provides Open MCT Web, or an external access management system.
|
which provides Open MCT, or an external access management system.
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
In most of the deployment options above, some level of configuration is likely
|
In most of the deployment options above, some level of configuration is likely
|
||||||
@ -2400,7 +2456,7 @@ to be needed or desirable to make sure that bundles can reach the external
|
|||||||
services they need to reach. Most commonly this means providing the path or URL
|
services they need to reach. Most commonly this means providing the path or URL
|
||||||
to an external service.
|
to an external service.
|
||||||
|
|
||||||
Configurable parameters within Open MCT Web are specified via constants
|
Configurable parameters within Open MCT are specified via constants
|
||||||
(literally, as extensions of the `constants` category) and accessed via
|
(literally, as extensions of the `constants` category) and accessed via
|
||||||
dependency injection by the scripts which need them. Reasonable defaults for
|
dependency injection by the scripts which need them. Reasonable defaults for
|
||||||
these constants are provided in the bundle where they are used. Plugins are
|
these constants are provided in the bundle where they are used. Plugins are
|
||||||
@ -2419,7 +2475,7 @@ for error, but is viable if there are a small number of constants to change.
|
|||||||
constants. This is particularly appropriate when multiple configurations (e.g.
|
constants. This is particularly appropriate when multiple configurations (e.g.
|
||||||
development, test, production) need to be managed easily; these can be swapped
|
development, test, production) need to be managed easily; these can be swapped
|
||||||
quickly by changing the set of active bundles in bundles.json.
|
quickly by changing the set of active bundles in bundles.json.
|
||||||
* Deploy Open MCT Web and its external services in such a fashion that the
|
* Deploy Open MCT and its external services in such a fashion that the
|
||||||
default paths to reach external services are all correct.
|
default paths to reach external services are all correct.
|
||||||
|
|
||||||
### Configuration Constants
|
### Configuration Constants
|
||||||
@ -2430,7 +2486,7 @@ The following constants have global significance:
|
|||||||
to be overridden by other bundles, but persistence adapters may wish to
|
to be overridden by other bundles, but persistence adapters may wish to
|
||||||
consume this constant in order to provide persistence for that space.
|
consume this constant in order to provide persistence for that space.
|
||||||
|
|
||||||
The following configuration constants are recognized by Open MCT Web bundles:
|
The following configuration constants are recognized by Open MCT bundles:
|
||||||
* Common UI elements - `platform/commonUI/general`
|
* Common UI elements - `platform/commonUI/general`
|
||||||
* `THEME`: A string identifying the current theme symbolically. Individual
|
* `THEME`: A string identifying the current theme symbolically. Individual
|
||||||
stylesheets (the `stylesheets` extension category) may specify an optional
|
stylesheets (the `stylesheets` extension category) may specify an optional
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
# Open MCT Web Documentation
|
# Open MCT Documentation
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
Documentation is provided to support the use and development of
|
Documentation is provided to support the use and development of
|
||||||
Open MCT Web. It's recommended that before doing
|
Open MCT. It's recommended that before doing
|
||||||
any development with Open MCT Web you take some time to familiarize yourself
|
any development with Open MCT you take some time to familiarize yourself
|
||||||
with the documentation below.
|
with the documentation below.
|
||||||
|
|
||||||
Open MCT Web provides functionality out of the box, but it's also a platform for
|
Open MCT provides functionality out of the box, but it's also a platform for
|
||||||
building rich mission operations applications based on modern web technology.
|
building rich mission operations applications based on modern web technology.
|
||||||
The platform is configured declaratively, and defines conventions for
|
The platform is configured declaratively, and defines conventions for
|
||||||
building on the provided capabilities by creating modular 'bundles' that
|
building on the provided capabilities by creating modular 'bundles' that
|
||||||
@ -17,7 +17,7 @@
|
|||||||
## Sections
|
## Sections
|
||||||
|
|
||||||
* The [Architecture Overview](architecture/) describes the concepts used
|
* The [Architecture Overview](architecture/) describes the concepts used
|
||||||
throughout Open MCT Web, and gives a high level overview of the platform's design.
|
throughout Open MCT, and gives a high level overview of the platform's design.
|
||||||
|
|
||||||
* The [Developer's Guide](guide/) goes into more detail about how to use the
|
* The [Developer's Guide](guide/) goes into more detail about how to use the
|
||||||
platform and the functionality that it provides.
|
platform and the functionality that it provides.
|
||||||
@ -31,5 +31,4 @@
|
|||||||
functions that make up the software platform.
|
functions that make up the software platform.
|
||||||
|
|
||||||
* Finally, the [Development Process](process/) document describes the
|
* Finally, the [Development Process](process/) document describes the
|
||||||
Open MCT Web software development cycle.
|
Open MCT software development cycle.
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Development Cycle
|
# Development Cycle
|
||||||
|
|
||||||
Development of Open MCT Web occurs on an iterative cycle of
|
Development of Open MCT occurs on an iterative cycle of
|
||||||
sprints and releases.
|
sprints and releases.
|
||||||
|
|
||||||
* A _sprint_ is three weeks in duration, and represents a
|
* A _sprint_ is three weeks in duration, and represents a
|
||||||
@ -151,11 +151,9 @@ emphasis on testing.
|
|||||||
ensuring software passes that testing in order to ship on time;
|
ensuring software passes that testing in order to ship on time;
|
||||||
may prefer to disable malfunctioning components and fix them
|
may prefer to disable malfunctioning components and fix them
|
||||||
in a subsequent sprint, for example.
|
in a subsequent sprint, for example.
|
||||||
* __Ship.__ Tag a code snapshot that has passed acceptance
|
* [__Ship.__](version.md) Tag a code snapshot that has passed release/sprint
|
||||||
testing and deploy that version. (Only true if acceptance
|
testing and deploy that version. (Only true if relevant
|
||||||
testing has passed by this point; if acceptance testing has not
|
testing has passed by this point; if testing has not
|
||||||
been passed, will need to make ad hoc decisions with stakeholders,
|
been passed, will need to make ad hoc decisions with stakeholders,
|
||||||
e.g. "extend the sprint" or "defer shipment until end of next
|
e.g. "extend the sprint" or "defer shipment until end of next
|
||||||
sprint.")
|
sprint.")
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
# Development Process
|
# Development Process
|
||||||
|
|
||||||
The process used to develop Open MCT Web is described in the following
|
The process used to develop Open MCT is described in the following
|
||||||
documents:
|
documents:
|
||||||
|
|
||||||
* [Development Cycle](cycle.md): Describes how and when specific
|
* The [Development Cycle](cycle.md) describes how and when specific
|
||||||
process points are repeated during development.
|
process points are repeated during development.
|
||||||
|
* The [Version Guide](version.md) describes version numbering for
|
||||||
|
Open MCT (both semantics and process.)
|
||||||
* Testing is described in two documents:
|
* Testing is described in two documents:
|
||||||
* The [Test Plan](testing/plan.md) summarizes the approaches used
|
* The [Test Plan](testing/plan.md) summarizes the approaches used
|
||||||
to test Open MCT Web.
|
to test Open MCT.
|
||||||
* The [Test Procedures](testing/procedures.md) document what
|
* The [Test Procedures](testing/procedures.md) document what
|
||||||
specific tests are performed to verify correctness, and how
|
specific tests are performed to verify correctness, and how
|
||||||
they should be carried out.
|
they should be carried out.
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Test Levels
|
## Test Levels
|
||||||
|
|
||||||
Testing for Open MCT Web includes:
|
Testing for Open MCT includes:
|
||||||
|
|
||||||
* _Smoke testing_: Brief, informal testing to verify that no major issues
|
* _Smoke testing_: Brief, informal testing to verify that no major issues
|
||||||
or regressions are present in the software, or in specific features of
|
or regressions are present in the software, or in specific features of
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
This document is intended to be used:
|
This document is intended to be used:
|
||||||
|
|
||||||
* By testers, to verify that Open MCT Web behaves as specified.
|
* By testers, to verify that Open MCT behaves as specified.
|
||||||
* By the development team, to document new test cases and to provide
|
* By the development team, to document new test cases and to provide
|
||||||
guidance on how to author these.
|
guidance on how to author these.
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ Test cases should be narrow in scope; if a list of steps is excessively
|
|||||||
long (or must be written vaguely to be kept short) it should be broken
|
long (or must be written vaguely to be kept short) it should be broken
|
||||||
down into multiple tests which reference one another.
|
down into multiple tests which reference one another.
|
||||||
|
|
||||||
All requirements satisfied by Open MCT Web should be verifiable using
|
All requirements satisfied by Open MCT should be verifiable using
|
||||||
one or more test procedures.
|
one or more test procedures.
|
||||||
|
|
||||||
## Glossary
|
## Glossary
|
||||||
@ -166,4 +166,4 @@ Eval. criteria | Visual inspection
|
|||||||
* Logs should not contain any unexpected warnings or errors ("expected"
|
* Logs should not contain any unexpected warnings or errors ("expected"
|
||||||
warnings or errors are those that have been documented and prioritized
|
warnings or errors are those that have been documented and prioritized
|
||||||
as known issues, or those that are explained by transient conditions
|
as known issues, or those that are explained by transient conditions
|
||||||
external to the software, such as network outages.)
|
external to the software, such as network outages.)
|
||||||
|
142
docs/src/process/version.md
Normal file
142
docs/src/process/version.md
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
# Version Guide
|
||||||
|
|
||||||
|
This document describes semantics and processes for providing version
|
||||||
|
numbers for Open MCT, and additionally provides guidelines for dependent
|
||||||
|
projects developed by the same team.
|
||||||
|
|
||||||
|
Versions are incremented at specific points in Open MCT's
|
||||||
|
[Development Cycle](cycle.md); see that document for a description of
|
||||||
|
sprints and releases.
|
||||||
|
|
||||||
|
## Audience
|
||||||
|
|
||||||
|
Individuals interested in consuming version numbers can be categorized as
|
||||||
|
follows:
|
||||||
|
|
||||||
|
* _Users_: Generally disinterested, occasionally wish to identify version
|
||||||
|
to cross-reference against documentation, or to report issues.
|
||||||
|
* _Testers_: Want to identify which version of the software they are
|
||||||
|
testing, e.g. to file issues for defects.
|
||||||
|
* _Internal developers_: Often, inverse of testers; want to identify which
|
||||||
|
version of software was/is in use when certain behavior is observed. Want
|
||||||
|
to be able to correlate versions in use with “streams” of development
|
||||||
|
(e.g. dev vs. prod), when possible.
|
||||||
|
* _External developers_: Need to understand which version of software is
|
||||||
|
in use when developing/maintaining plug-ins, in order to ensure
|
||||||
|
compatibility of their software.
|
||||||
|
|
||||||
|
## Version Reporting
|
||||||
|
|
||||||
|
Software versions should be reflected in the user interface of the
|
||||||
|
application in three ways:
|
||||||
|
|
||||||
|
* _Version number_: A semantic version (see below) which serves both to
|
||||||
|
uniquely identify releases, as well as to inform plug-in developers
|
||||||
|
about compatibility with previous releases.
|
||||||
|
* _Revision identifier_: While using git, the commit hash. Supports
|
||||||
|
internal developers and testers by uniquely identifying client
|
||||||
|
software snapshots.
|
||||||
|
* _Branding_: Identifies which variant is in use. (Typically, Open MCT
|
||||||
|
is re-branded when deployed for a specific mission or center.)
|
||||||
|
|
||||||
|
## Version Numbering
|
||||||
|
|
||||||
|
Open MCT shall provide version numbers consistent with
|
||||||
|
[Semantic Versioning 2.0.0](http://semver.org/). In summary, versions
|
||||||
|
are expressed in a "major.minor.patch" form, and incremented based on
|
||||||
|
nature of changes to external API. Breaking changes require a "major"
|
||||||
|
version increment; backwards-compatible changes require a "minor"
|
||||||
|
version increment; neutral changes (such as bug fixes) require a "patch"
|
||||||
|
version increment. A hyphen-separated suffix indicates a pre-release
|
||||||
|
version, which may be unstable or may not fully meet compatibility
|
||||||
|
requirements.
|
||||||
|
|
||||||
|
Additionally, the following project-specific standards will be used:
|
||||||
|
|
||||||
|
* During development, a "-SNAPSHOT" suffix shall be appended to the
|
||||||
|
version number. The version number before the suffix shall reflect
|
||||||
|
the next expected version number for release.
|
||||||
|
* Prior to a 1.0.0 release, the _minor_ version will be incremented
|
||||||
|
on a per-release basis; the _patch_ version will be incremented on a
|
||||||
|
per-sprint basis.
|
||||||
|
* Starting at version 1.0.0, version numbers will be updated with each
|
||||||
|
completed sprint. The version number for the sprint shall be
|
||||||
|
determined relative to the previous released version; the decision
|
||||||
|
to increment the _major_, _minor_, or _patch_ version should be
|
||||||
|
made based on the nature of changes during that release. (It is
|
||||||
|
recommended that these numbers are incremented as changes are
|
||||||
|
introduced, such that at end of release the version number may
|
||||||
|
be chosen by simply removing the suffix.)
|
||||||
|
* The first three sprints in a release may be unstable; in these cases, a
|
||||||
|
unique version identifier should still be generated, but a suffix
|
||||||
|
should be included to indicate that the version is not necessarily
|
||||||
|
production-ready. Recommended suffixes are:
|
||||||
|
|
||||||
|
Sprint | Suffix
|
||||||
|
:------:|:--------:
|
||||||
|
1 | `-alpha`
|
||||||
|
2 | `-beta`
|
||||||
|
3 | `-rc`
|
||||||
|
|
||||||
|
### Scope of External API
|
||||||
|
|
||||||
|
"External API" refers to the API exposed to, documented for, and used by
|
||||||
|
plug-in developers. Changes to interfaces used internally by Open MCT
|
||||||
|
(or otherwise not documented for use externally) require only a _patch_
|
||||||
|
version bump.
|
||||||
|
|
||||||
|
## Incrementing Versions
|
||||||
|
|
||||||
|
At the end of a sprint, the [project manager](cycle.md#roles)
|
||||||
|
should update (or delegate the task of updating) Open MCT version
|
||||||
|
numbers by the following process:
|
||||||
|
|
||||||
|
1. Update version number in `package.json`
|
||||||
|
1. Remove `-SNAPSHOT` suffix.
|
||||||
|
2. Verify that resulting version number meets semantic versioning
|
||||||
|
requirements relative to previous stable version. Increment if
|
||||||
|
necessary.
|
||||||
|
3. If version is considered unstable (which may be the case during
|
||||||
|
the first three sprints of a release), apply a new suffix per
|
||||||
|
[Version Numbering](#version-numbering) guidance above.
|
||||||
|
2. Tag the release.
|
||||||
|
1. Commit changes to `package.json` on the `master` branch.
|
||||||
|
The commit message should reference the sprint being closed,
|
||||||
|
preferably by a URL reference to the associated Milestone in
|
||||||
|
GitHub.
|
||||||
|
2. Verify that build still completes, that application passes
|
||||||
|
smoke-testing, and that only differences from tested versions
|
||||||
|
are the changes to version number above.
|
||||||
|
3. Push the `master` branch.
|
||||||
|
4. Tag this commit with the version number, prepending the letter "v".
|
||||||
|
(e.g. `git tag v0.9.3-alpha`)
|
||||||
|
5. Push the tag to GitHub. (e.g. `git push origin v0.9.3-alpha`).
|
||||||
|
3. Upload a release archive.
|
||||||
|
1. Run `npm pack` to generate the archive.
|
||||||
|
2. Use the [GitHub release interface](https://github.com/nasa/openmct/releases)
|
||||||
|
to draft a new release.
|
||||||
|
3. Choose the existing tag for the new version (created and pushed above.)
|
||||||
|
Enter the tag name as the release name as well; see existing releases
|
||||||
|
for examples.
|
||||||
|
4. Attach the release archive.
|
||||||
|
5. Designate the release as a "pre-release" as appropriate (for instance,
|
||||||
|
when the version number has been suffixed as unstable, or when
|
||||||
|
the version number is below 1.0.0.)
|
||||||
|
4. Restore snapshot status in `package.json`
|
||||||
|
1. Remove any suffix from the version number, or increment the
|
||||||
|
_patch_ version if there is no suffix.
|
||||||
|
2. Append a `-SNAPSHOT` suffix.
|
||||||
|
3. Commit changes to `package.json` on the `master` branch.
|
||||||
|
The commit message should reference the sprint being opened,
|
||||||
|
preferably by a URL reference to the associated Milestone in
|
||||||
|
GitHub.
|
||||||
|
4. Verify that build still completes, that application passes
|
||||||
|
smoke-testing.
|
||||||
|
5. Push the `master` branch.
|
||||||
|
|
||||||
|
Projects dependent on Open MCT being co-developed by the Open MCT
|
||||||
|
team should follow a similar process, except that they should
|
||||||
|
additionally update their dependency on Open MCT to point to the
|
||||||
|
latest archive when removing their `-SNAPSHOT` status, and
|
||||||
|
that they should be pointed back to the `master` branch after
|
||||||
|
this has completed.
|
@ -1,4 +1,4 @@
|
|||||||
# Open MCT Web Tutorials
|
# Open MCT Tutorials
|
||||||
|
|
||||||
Victor Woeltjen
|
Victor Woeltjen
|
||||||
victor.woeltjen@nasa.gov
|
victor.woeltjen@nasa.gov
|
||||||
@ -23,9 +23,9 @@ been added or removed as part of the tutorial. In these cases, any lines added
|
|||||||
will be indicated with a '+' at the start of the line. Any lines removed will
|
will be indicated with a '+' at the start of the line. Any lines removed will
|
||||||
be indicated with a '-'.
|
be indicated with a '-'.
|
||||||
|
|
||||||
## Setting Up Open MCT Web
|
## Setting Up Open MCT
|
||||||
|
|
||||||
In this section, we will cover the steps necessary to get a minimal Open MCT Web
|
In this section, we will cover the steps necessary to get a minimal Open MCT
|
||||||
developer environment up and running. Once we have this, we will be able to
|
developer environment up and running. Once we have this, we will be able to
|
||||||
proceed with writing plugins as described in this tutorial.
|
proceed with writing plugins as described in this tutorial.
|
||||||
|
|
||||||
@ -40,22 +40,22 @@ more recent versions, but this cannot be guaranteed.
|
|||||||
* Google Chrome v42: https://www.google.com/chrome/
|
* Google Chrome v42: https://www.google.com/chrome/
|
||||||
* A text editor.
|
* A text editor.
|
||||||
|
|
||||||
Open MCT Web can be run without any of these tools, provided suitable
|
Open MCT can be run without any of these tools, provided suitable
|
||||||
alternatives are taken; see the [Open MCT Web Developer Guide](../guide/index.md)
|
alternatives are taken; see the [Open MCT Developer Guide](../guide/index.md)
|
||||||
for a more general overview of how to run and deploy a Open MCT Web application.
|
for a more general overview of how to run and deploy a Open MCT application.
|
||||||
|
|
||||||
### Check out Open MCT Web Sources
|
### Check out Open MCT Sources
|
||||||
|
|
||||||
First step is to check out Open MCT Web from the source repository.
|
First step is to check out Open MCT from the source repository.
|
||||||
|
|
||||||
`git clone https://github.com/nasa/openmctweb.git openmctweb`
|
`git clone https://github.com/nasa/openmctweb.git openmctweb`
|
||||||
|
|
||||||
This will create a copy of the Open MCT Web source code repository in the folder
|
This will create a copy of the Open MCT source code repository in the folder
|
||||||
`openmctweb` (relative to the path from which you ran the command.)
|
`openmctweb` (relative to the path from which you ran the command.)
|
||||||
If you have a repository URL, use that as the "path to repo" above. Alternately,
|
If you have a repository URL, use that as the "path to repo" above. Alternately,
|
||||||
if you received Open MCT Web as a git bundle, the path to that bundle on the
|
if you received Open MCT as a git bundle, the path to that bundle on the
|
||||||
local filesystem can be used instead.
|
local filesystem can be used instead.
|
||||||
At this point, it will also be useful to branch off of Open MCT Web v0.6.2
|
At this point, it will also be useful to branch off of Open MCT v0.6.2
|
||||||
(which was used when writing these tutorials) to begin adding plugins.
|
(which was used when writing these tutorials) to begin adding plugins.
|
||||||
|
|
||||||
cd openmctweb
|
cd openmctweb
|
||||||
@ -64,12 +64,12 @@ At this point, it will also be useful to branch off of Open MCT Web v0.6.2
|
|||||||
|
|
||||||
### Configuring Persistence
|
### Configuring Persistence
|
||||||
|
|
||||||
In its default configuration, Open MCT Web will try to use ElasticSearch
|
In its default configuration, Open MCT will try to use ElasticSearch
|
||||||
(expected to be deployed at /elastic on the same HTTP server running Open MCT
|
(expected to be deployed at /elastic on the same HTTP server running Open MCT
|
||||||
Web) to persist user-created domain objects. We don't need that for these
|
Web) to persist user-created domain objects. We don't need that for these
|
||||||
tutorials, so we will replace the ElasticSearch plugin with the example
|
tutorials, so we will replace the ElasticSearch plugin with the example
|
||||||
persistence plugin. This doesn't actually persist, so anything we create within
|
persistence plugin. This doesn't actually persist, so anything we create within
|
||||||
Open MCT Web will be lost on reload, but that's fine for purposes of these
|
Open MCT will be lost on reload, but that's fine for purposes of these
|
||||||
tutorials.
|
tutorials.
|
||||||
|
|
||||||
To change this configuration, edit bundles.json (at the top level of the Open
|
To change this configuration, edit bundles.json (at the top level of the Open
|
||||||
@ -132,7 +132,7 @@ __bundles.json__
|
|||||||
|
|
||||||
### Run a Web Server
|
### Run a Web Server
|
||||||
|
|
||||||
The next step is to run a web server so that you can view the Open MCT Web
|
The next step is to run a web server so that you can view the Open MCT
|
||||||
client (including the plugins you add to it) in browser. Any web server can
|
client (including the plugins you add to it) in browser. Any web server can
|
||||||
be used for hosting OpenMCTWeb, and a trivial web server is provided in this
|
be used for hosting OpenMCTWeb, and a trivial web server is provided in this
|
||||||
package for the purposes of running the tutorials. The provided web server
|
package for the purposes of running the tutorials. The provided web server
|
||||||
@ -144,11 +144,11 @@ To run the tutorial web server
|
|||||||
|
|
||||||
### Viewing in Browser
|
### Viewing in Browser
|
||||||
|
|
||||||
Once running, you should be able to view Open MCT Web from your browser at
|
Once running, you should be able to view Open MCT from your browser at
|
||||||
http://localhost:8080/ (assuming the web server is running on port 8080,
|
http://localhost:8080/ (assuming the web server is running on port 8080,
|
||||||
and OpenMCTWeb is installed at the server's root path).
|
and OpenMCTWeb is installed at the server's root path).
|
||||||
[Google Chrome](https://www.google.com/chrome/) is recommended for these
|
[Google Chrome](https://www.google.com/chrome/) is recommended for these
|
||||||
tutorials, as Chrome is Open MCT Web's "test-to" browser. The browser cache
|
tutorials, as Chrome is Open MCT's "test-to" browser. The browser cache
|
||||||
can sometimes interfere with development (masking changes by
|
can sometimes interfere with development (masking changes by
|
||||||
using older versions of sources); to avoid this, it is easiest to run Chrome
|
using older versions of sources); to avoid this, it is easiest to run Chrome
|
||||||
with Developer Tools expanded, and "Disable cache" selected from the Network
|
with Developer Tools expanded, and "Disable cache" selected from the Network
|
||||||
@ -158,7 +158,7 @@ tab, as shown below.
|
|||||||
|
|
||||||
# Tutorials
|
# Tutorials
|
||||||
|
|
||||||
These tutorials cover three of the common tasks in Open MCT Web:
|
These tutorials cover three of the common tasks in Open MCT:
|
||||||
|
|
||||||
* The "to-do list" tutorial illustrates how to add a new application feature.
|
* The "to-do list" tutorial illustrates how to add a new application feature.
|
||||||
* The "bar graph" tutorial illustrates how to add a new telemetry visualization.
|
* The "bar graph" tutorial illustrates how to add a new telemetry visualization.
|
||||||
@ -167,17 +167,17 @@ backend.
|
|||||||
|
|
||||||
## To-do List
|
## To-do List
|
||||||
|
|
||||||
The goal of this tutorial is to add a new application feature to Open MCT Web:
|
The goal of this tutorial is to add a new application feature to Open MCT:
|
||||||
To-do lists. Users should be able to create and manage these to track items that
|
To-do lists. Users should be able to create and manage these to track items that
|
||||||
they need to do. This is modelled after the to-do lists at http://todomvc.com/.
|
they need to do. This is modelled after the to-do lists at http://todomvc.com/.
|
||||||
|
|
||||||
### Step 1-Create the Plugin
|
### Step 1-Create the Plugin
|
||||||
|
|
||||||
The first step to adding a new feature to Open MCT Web is to create the plugin
|
The first step to adding a new feature to Open MCT is to create the plugin
|
||||||
which will expose that feature. A plugin in Open MCT Web is represented by what
|
which will expose that feature. A plugin in Open MCT is represented by what
|
||||||
is called a bundle; a bundle, in turn, is a directory which contains a file
|
is called a bundle; a bundle, in turn, is a directory which contains a file
|
||||||
bundle.json, which in turn describes where other relevant sources & resources
|
bundle.json, which in turn describes where other relevant sources & resources
|
||||||
will be. The syntax of this file is described in more detail in the Open MCT Web
|
will be. The syntax of this file is described in more detail in the Open MCT
|
||||||
Developer Guide.
|
Developer Guide.
|
||||||
|
|
||||||
We will create this file in the directory tutorials/todo (we can hereafter refer
|
We will create this file in the directory tutorials/todo (we can hereafter refer
|
||||||
@ -254,7 +254,7 @@ __bundles.json__
|
|||||||
```
|
```
|
||||||
__bundles.json__
|
__bundles.json__
|
||||||
|
|
||||||
At this point, we can reload Open MCT Web. We haven't introduced any new
|
At this point, we can reload Open MCT. We haven't introduced any new
|
||||||
functionality, so we don't see anything different, but if we run with logging
|
functionality, so we don't see anything different, but if we run with logging
|
||||||
enabled ( http://localhost:8080/?log=info ) and check the browser console, we
|
enabled ( http://localhost:8080/?log=info ) and check the browser console, we
|
||||||
should see:
|
should see:
|
||||||
@ -265,11 +265,11 @@ should see:
|
|||||||
|
|
||||||
### Step 2-Add a Domain Object Type
|
### Step 2-Add a Domain Object Type
|
||||||
|
|
||||||
Features in a Open MCT Web application are most commonly expressed as domain
|
Features in a Open MCT application are most commonly expressed as domain
|
||||||
objects and/or views thereof. A domain object is some thing that is relevant to
|
objects and/or views thereof. A domain object is some thing that is relevant to
|
||||||
the work that the Open MCT Web application is meant to support. Domain objects
|
the work that the Open MCT application is meant to support. Domain objects
|
||||||
can be created, organized, edited, placed in layouts, and so forth. (For a
|
can be created, organized, edited, placed in layouts, and so forth. (For a
|
||||||
deeper explanation of domain objects, see the Open MCT Web Developer Guide.)
|
deeper explanation of domain objects, see the Open MCT Developer Guide.)
|
||||||
|
|
||||||
In the case of our to-do list feature, the to-do list itself is the thing we'll
|
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
|
want users to be able to create and edit. So, we will add that as a new type in
|
||||||
@ -303,7 +303,7 @@ Going through the properties we've defined:
|
|||||||
domain objects of this type.
|
domain objects of this type.
|
||||||
* The `name` of "To-Do List" is the human-readable name for this type, and will
|
* The `name` of "To-Do List" is the human-readable name for this type, and will
|
||||||
be shown to users.
|
be shown to users.
|
||||||
* The `glyph` refers to a special character in Open MCT Web's custom font set;
|
* The `glyph` refers to a special character in Open MCT's custom font set;
|
||||||
this will be used as an icon.
|
this will be used as an icon.
|
||||||
* The `description` is also human-readable, and will be used whenever a longer
|
* The `description` is also human-readable, and will be used whenever a longer
|
||||||
explanation of what this type is should be shown.
|
explanation of what this type is should be shown.
|
||||||
@ -312,7 +312,7 @@ this type. Including `creation` here means that we want users to be able to
|
|||||||
create this (in other cases, we may wish to expose things as domain objects
|
create this (in other cases, we may wish to expose things as domain objects
|
||||||
which aren't user-created, in which case we would omit this.)
|
which aren't user-created, in which case we would omit this.)
|
||||||
|
|
||||||
If we reload Open MCT Web, we see that our new domain object type appears in the
|
If we reload Open MCT, we see that our new domain object type appears in the
|
||||||
Create menu:
|
Create menu:
|
||||||
|
|
||||||

|

|
||||||
@ -324,10 +324,10 @@ because we haven't defined any yet.
|
|||||||
### Step 3-Add a View
|
### Step 3-Add a View
|
||||||
|
|
||||||
In order to allow a to-do list to be used, we need to define and display its
|
In order to allow a to-do list to be used, we need to define and display its
|
||||||
contents. In Open MCT Web, the pattern that the user expects is that they'll
|
contents. In Open MCT, the pattern that the user expects is that they'll
|
||||||
click on an object in the left-hand tree, and see a visualization of it to the
|
click on an object in the left-hand tree, and see a visualization of it to the
|
||||||
right; in Open MCT Web, these visualizations are called views.
|
right; in Open MCT, these visualizations are called views.
|
||||||
A view in Open MCT Web is defined by an Angular template. We'll add that in the
|
A view in Open MCT is defined by an Angular template. We'll add that in the
|
||||||
directory `tutorials/todo/res/templates` (`res` is, by default, the directory
|
directory `tutorials/todo/res/templates` (`res` is, by default, the directory
|
||||||
where bundle-related resources are kept, and `templates` is where HTML templates
|
where bundle-related resources are kept, and `templates` is where HTML templates
|
||||||
are stored by convention.)
|
are stored by convention.)
|
||||||
@ -357,12 +357,12 @@ to filter down to either complete or incomplete tasks.
|
|||||||
of the domain object being viewed; this contains all of the persistent state
|
of the domain object being viewed; this contains all of the persistent state
|
||||||
associated with that object. This model is effectively just a JSON document, so
|
associated with that object. This model is effectively just a JSON document, so
|
||||||
we can choose what goes into it (so long as we take care not to collide with
|
we can choose what goes into it (so long as we take care not to collide with
|
||||||
platform-defined properties; see the Open MCT Web Developer Guide.) Here, we
|
platform-defined properties; see the Open MCT Developer Guide.) Here, we
|
||||||
assume that all tasks will be stored in a property `tasks`, and that each will be
|
assume that all tasks will be stored in a property `tasks`, and that each will be
|
||||||
an object containing a `description` (the readable summary of the task) and a
|
an object containing a `description` (the readable summary of the task) and a
|
||||||
boolean `completed` flag.
|
boolean `completed` flag.
|
||||||
|
|
||||||
To expose this view in Open MCT Web, we need to declare it in our bundle
|
To expose this view in Open MCT, we need to declare it in our bundle
|
||||||
definition:
|
definition:
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
@ -399,7 +399,7 @@ contains the following properties:
|
|||||||
* Its `key` is its machine-readable name; we've given it the same name here as
|
* Its `key` is its machine-readable name; we've given it the same name here as
|
||||||
the domain object type, but could have chosen any unique name.
|
the domain object type, but could have chosen any unique name.
|
||||||
|
|
||||||
* The `type` property tells Open MCT Web that this view is only applicable to
|
* The `type` property tells Open MCT that this view is only applicable to
|
||||||
domain objects of that type. This means that we'll see this view for To-do Lists
|
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.)
|
that we create, but not for other domain objects (such as Folders.)
|
||||||
|
|
||||||
@ -449,10 +449,10 @@ definition of that type.
|
|||||||
```
|
```
|
||||||
__tutorials/todo/bundle.json__
|
__tutorials/todo/bundle.json__
|
||||||
|
|
||||||
Now, when To-do List objects are created in Open MCT Web, they will initially
|
Now, when To-do List objects are created in Open MCT, they will initially
|
||||||
have the state described by that model property.
|
have the state described by that model property.
|
||||||
|
|
||||||
If we reload Open MCT Web, create a To-do List, and navigate to it in the tree,
|
If we reload Open MCT, create a To-do List, and navigate to it in the tree,
|
||||||
we should now see:
|
we should now see:
|
||||||
|
|
||||||

|

|
||||||
@ -527,7 +527,7 @@ first argument is falsy.)
|
|||||||
|
|
||||||
* `toggleCompletion` changes whether or not a task is complete. We make the
|
* `toggleCompletion` changes whether or not a task is complete. We make the
|
||||||
change via the domain object's `mutation` capability, and then persist the
|
change via the domain object's `mutation` capability, and then persist the
|
||||||
change via its `persistence` capability. See the Open MCT Web Developer Guide
|
change via its `persistence` capability. See the Open MCT Developer Guide
|
||||||
for more information on these capabilities.
|
for more information on these capabilities.
|
||||||
|
|
||||||
* `showTask` is meant to be used to help decide if a task should be shown, based
|
* `showTask` is meant to be used to help decide if a task should be shown, based
|
||||||
@ -537,7 +537,7 @@ the use of the double-not !! to coerce the completed flag to a boolean, for
|
|||||||
equality testing.)
|
equality testing.)
|
||||||
|
|
||||||
Note that these functions make reference to `$scope.domainObject;` this is the
|
Note that these functions make reference to `$scope.domainObject;` this is the
|
||||||
domain object being viewed, which is passed into the scope by Open MCT Web
|
domain object being viewed, which is passed into the scope by Open MCT
|
||||||
prior to our template being utilized.
|
prior to our template being utilized.
|
||||||
|
|
||||||
On its own, this controller merely exposes these functions; the next step is to
|
On its own, this controller merely exposes these functions; the next step is to
|
||||||
@ -640,7 +640,7 @@ if we go to My Items and come back.
|
|||||||
We now have a somewhat-functional view of our To-Do List, but we're still
|
We now have a somewhat-functional view of our To-Do List, but we're still
|
||||||
missing some important functionality: Adding and removing tasks!
|
missing some important functionality: Adding and removing tasks!
|
||||||
|
|
||||||
This is a good place to discuss the user interface style of Open MCT Web. Open
|
This is a good place to discuss the user interface style of Open MCT. Open
|
||||||
MCT Web draws a distinction between "using" and "editing" a domain object; in
|
MCT Web draws a distinction between "using" and "editing" a domain object; in
|
||||||
general, you can only make changes to a domain object while in Edit mode, which
|
general, you can only make changes to a domain object while in Edit mode, which
|
||||||
is reachable from the button with a pencil icon. This distinction helps users
|
is reachable from the button with a pencil icon. This distinction helps users
|
||||||
@ -732,14 +732,14 @@ What we've stated here is that the To-Do List's view will have a toolbar which
|
|||||||
contains two sections (which will be visually separated by a divider), each of
|
contains two sections (which will be visually separated by a divider), each of
|
||||||
which contains one button. The first is a button labelled "Add Task" that will
|
which contains one button. The first is a button labelled "Add Task" that will
|
||||||
invoke an `addTask` method; the second is a button with a glyph (which will appear
|
invoke an `addTask` method; the second is a button with a glyph (which will appear
|
||||||
as a trash can in Open MCT Web's custom font set) which will invoke a `removeTask`
|
as a trash can in Open MCT's custom font set) which will invoke a `removeTask`
|
||||||
method. For more information on forms and tool bars in Open MCT Web, see the
|
method. For more information on forms and tool bars in Open MCT, see the
|
||||||
Open MCT Web Developer Guide.
|
Open MCT Developer Guide.
|
||||||
|
|
||||||
If we reload and run Open MCT Web, we won't see any tool bar when we switch over
|
If we reload and run Open MCT, we won't see any tool bar when we switch over
|
||||||
to Edit mode. This is because the aforementioned methods are expected to be
|
to Edit mode. This is because the aforementioned methods are expected to be
|
||||||
found on currently-selected elements; we haven't done anything with selections
|
found on currently-selected elements; we haven't done anything with selections
|
||||||
in our view yet, so the Open MCT Web platform will filter this tool bar down to
|
in our view yet, so the Open MCT platform will filter this tool bar down to
|
||||||
all the applicable controls, which means no controls at all.
|
all the applicable controls, which means no controls at all.
|
||||||
|
|
||||||
To support selection, we will need to make some changes to our controller:
|
To support selection, we will need to make some changes to our controller:
|
||||||
@ -842,7 +842,7 @@ click the _Add Task_ button. This form is described declaratively, and populates
|
|||||||
an object that has the same format as tasks in the `tasks` array of our
|
an object that has the same format as tasks in the `tasks` array of our
|
||||||
To-Do List's model.
|
To-Do List's model.
|
||||||
* We've added an argument to the `TodoController`: The `dialogService`, which is
|
* We've added an argument to the `TodoController`: The `dialogService`, which is
|
||||||
exposed by the Open MCT Web platform to handle showing dialogs.
|
exposed by the Open MCT platform to handle showing dialogs.
|
||||||
* Some utility functions for handling the actual adding and removing of tasks.
|
* Some utility functions for handling the actual adding and removing of tasks.
|
||||||
These use the `mutation` capability to modify the tasks in the To-Do List's
|
These use the `mutation` capability to modify the tasks in the To-Do List's
|
||||||
model.
|
model.
|
||||||
@ -947,7 +947,7 @@ declare that dependency in its extension definition:
|
|||||||
```
|
```
|
||||||
__tutorials/todo/bundle.json__
|
__tutorials/todo/bundle.json__
|
||||||
|
|
||||||
If we now reload Open MCT Web, we'll be able to see the new functionality we've
|
If we now reload Open MCT, we'll be able to see the new functionality we've
|
||||||
added. If we Create a new To-Do List, navigate to it, and click the button with
|
added. If we Create a new To-Do List, navigate to it, and click the button with
|
||||||
the Pencil icon in the top-right, we'll be in edit mode. We see, first, that our
|
the Pencil icon in the top-right, we'll be in edit mode. We see, first, that our
|
||||||
"Add Task" button appears in the tool bar:
|
"Add Task" button appears in the tool bar:
|
||||||
@ -1136,7 +1136,7 @@ Here, we have defined classes and appearances for:
|
|||||||
* A message, which we will add next, to display when there are no tasks
|
* A message, which we will add next, to display when there are no tasks
|
||||||
(`example-message`).
|
(`example-message`).
|
||||||
|
|
||||||
To include this CSS file in our running instance of Open MCT Web, we need to
|
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
|
declare it in our bundle definition, this time as an extension of category
|
||||||
`stylesheets`:
|
`stylesheets`:
|
||||||
```diff
|
```diff
|
||||||
@ -1436,7 +1436,7 @@ The corresponding CSS file which styles and positions these elements:
|
|||||||
__tutorials/bargraph/res/css/bargraph.css__
|
__tutorials/bargraph/res/css/bargraph.css__
|
||||||
|
|
||||||
This is already enough that, if we add `"tutorials/bargraph"` to `bundles.json`,
|
This is already enough that, if we add `"tutorials/bargraph"` to `bundles.json`,
|
||||||
we should be able to run Open MCT Web and see our Bar Graph as an available view
|
we should be able to run Open MCT and see our Bar Graph as an available view
|
||||||
for domain objects which provide telemetry (such as the example
|
for domain objects which provide telemetry (such as the example
|
||||||
_Sine Wave Generator_) as well as for _Telemetry Panel_ objects:
|
_Sine Wave Generator_) as well as for _Telemetry Panel_ objects:
|
||||||
|
|
||||||
@ -1502,7 +1502,7 @@ will help support some positioning in the template.
|
|||||||
to real-time telemetry updates. This will deal with most of the complexity of
|
to real-time telemetry updates. This will deal with most of the complexity of
|
||||||
dealing with telemetry (e.g. differentiating between individual telemetry points
|
dealing with telemetry (e.g. differentiating between individual telemetry points
|
||||||
and telemetry panels, monitoring latest values) and provide us with a useful
|
and telemetry panels, monitoring latest values) and provide us with a useful
|
||||||
interface for populating our view. The the Open MCT Web Developer Guide for more
|
interface for populating our view. The the Open MCT Developer Guide for more
|
||||||
information on dealing with telemetry.
|
information on dealing with telemetry.
|
||||||
|
|
||||||
Whenever the telemetry handler invokes its callbacks, we update the set of
|
Whenever the telemetry handler invokes its callbacks, we update the set of
|
||||||
@ -1594,7 +1594,7 @@ service we made use of.
|
|||||||
```
|
```
|
||||||
__tutorials/bargraph/bundle.json__
|
__tutorials/bargraph/bundle.json__
|
||||||
|
|
||||||
When we reload Open MCT Web, we are now able to see that our bar graph view
|
When we reload Open MCT, we are now able to see that our bar graph view
|
||||||
correctly labels one bar per telemetry-providing domain object, as shown for
|
correctly labels one bar per telemetry-providing domain object, as shown for
|
||||||
this Telemetry Panel containing four Sine Wave Generators.
|
this Telemetry Panel containing four Sine Wave Generators.
|
||||||
|
|
||||||
@ -1703,7 +1703,7 @@ __tutorials/bargraph/res/templates/bargraph.html__
|
|||||||
Here, we utilize the functions we just provided from the controller to position
|
Here, we utilize the functions we just provided from the controller to position
|
||||||
the bar, using an ng-style attribute.
|
the bar, using an ng-style attribute.
|
||||||
|
|
||||||
When we reload Open MCT Web, our bar graph view now looks like:
|
When we reload Open MCT, our bar graph view now looks like:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@ -1714,7 +1714,7 @@ sine waves, but what about other values? We want to provide the user with a
|
|||||||
means of configuring these boundaries.
|
means of configuring these boundaries.
|
||||||
|
|
||||||
This is normally done via Edit mode. Since view configuration is a common
|
This is normally done via Edit mode. Since view configuration is a common
|
||||||
problem, the Open MCT Web platform exposes a configuration object - called
|
problem, the Open MCT platform exposes a configuration object - called
|
||||||
`configuration` - into our view's scope. We can populate it as we please, and
|
`configuration` - into our view's scope. We can populate it as we please, and
|
||||||
when we return to our view later, those changes will be persisted.
|
when we return to our view later, those changes will be persisted.
|
||||||
|
|
||||||
@ -1884,14 +1884,14 @@ defaults (if needed), and expose its state into the scope.
|
|||||||
and `high` as entered by the user from the tool bar. This uses the
|
and `high` as entered by the user from the tool bar. This uses the
|
||||||
getter-setters we defined previously.
|
getter-setters we defined previously.
|
||||||
|
|
||||||
If we reload Open MCT Web and go to a Bar Graph view in Edit mode, we now see
|
If we reload Open MCT and go to a Bar Graph view in Edit mode, we now see
|
||||||
that we can change these bounds from the tool bar.
|
that we can change these bounds from the tool bar.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Telemetry Adapter
|
## Telemetry Adapter
|
||||||
|
|
||||||
The goal of this tutorial is to demonstrate how to integrate Open MCT Web
|
The goal of this tutorial is to demonstrate how to integrate Open MCT
|
||||||
with an existing telemetry system.
|
with an existing telemetry system.
|
||||||
|
|
||||||
A summary of the steps we will take:
|
A summary of the steps we will take:
|
||||||
@ -1902,7 +1902,7 @@ A summary of the steps we will take:
|
|||||||
|
|
||||||
### Step 0-Expose Your Telemetry
|
### Step 0-Expose Your Telemetry
|
||||||
|
|
||||||
As a precondition to integrating telemetry data into Open MCT Web, this
|
As a precondition to integrating telemetry data into Open MCT, this
|
||||||
information needs to be available over web-based interfaces. In practice,
|
information needs to be available over web-based interfaces. In practice,
|
||||||
this will most likely mean exposing data over HTTP, or over WebSockets.
|
this will most likely mean exposing data over HTTP, or over WebSockets.
|
||||||
For purposes of this tutorial, a simple node server is provided to stand
|
For purposes of this tutorial, a simple node server is provided to stand
|
||||||
@ -2080,7 +2080,7 @@ measurement.
|
|||||||
(Note that the term "measurement" is used to describe a distinct data series
|
(Note that the term "measurement" is used to describe a distinct data series
|
||||||
within this system; in other systems, these have been called channels,
|
within this system; in other systems, these have been called channels,
|
||||||
mnemonics, telemetry points, or other names. No preference is made here;
|
mnemonics, telemetry points, or other names. No preference is made here;
|
||||||
Open MCT Web is easily adapted to use the terminology appropriate to your
|
Open MCT is easily adapted to use the terminology appropriate to your
|
||||||
system.)
|
system.)
|
||||||
Additionally, while running the server from the terminal we can toggle the
|
Additionally, while running the server from the terminal we can toggle the
|
||||||
state of the "spacecraft" by hitting enter; this will turn the "thrusters"
|
state of the "spacecraft" by hitting enter; this will turn the "thrusters"
|
||||||
@ -2162,9 +2162,9 @@ telemetry.
|
|||||||
__tutorial-server/dictionary.json__
|
__tutorial-server/dictionary.json__
|
||||||
|
|
||||||
It should be noted that neither the interface for the example server nor the
|
It should be noted that neither the interface for the example server nor the
|
||||||
dictionary format are expected by Open MCT Web; rather, these are intended to
|
dictionary format are expected by Open MCT; rather, these are intended to
|
||||||
stand in for some existing source of telemetry data to which we wish to adapt
|
stand in for some existing source of telemetry data to which we wish to adapt
|
||||||
Open MCT Web.
|
Open MCT.
|
||||||
|
|
||||||
We can run this example server by:
|
We can run this example server by:
|
||||||
|
|
||||||
@ -2181,11 +2181,11 @@ like https://www.npmjs.com/package/wscat :
|
|||||||
< {"type":"dictionary","value":{"name":"Example Spacecraft","identifier":"sc","subsystems":[{"name":"Propulsion","identifier":"prop","measurements":[{"name":"Fuel","identifier":"prop.fuel","units":"kilograms","type":"float"},{"name":"Thrusters","identifier":"prop.thrusters","units":"None","type":"string"}]},{"name":"Communications","identifier":"comms","measurements":[{"name":"Received","identifier":"comms.recd","units":"bytes","type":"integer"},{"name":"Sent","identifier":"comms.sent","units":"bytes","type":"integer"}]},{"name":"Power","identifier":"pwr","measurements":[{"name":"Generator Temperature","identifier":"pwr.temp","units":"C","type":"float"},{"name":"Generator Current","identifier":"pwr.c","units":"A","type":"float"},{"name":"Generator Voltage","identifier":"pwr.v","units":"V","type":"float"}]}]}}
|
< {"type":"dictionary","value":{"name":"Example Spacecraft","identifier":"sc","subsystems":[{"name":"Propulsion","identifier":"prop","measurements":[{"name":"Fuel","identifier":"prop.fuel","units":"kilograms","type":"float"},{"name":"Thrusters","identifier":"prop.thrusters","units":"None","type":"string"}]},{"name":"Communications","identifier":"comms","measurements":[{"name":"Received","identifier":"comms.recd","units":"bytes","type":"integer"},{"name":"Sent","identifier":"comms.sent","units":"bytes","type":"integer"}]},{"name":"Power","identifier":"pwr","measurements":[{"name":"Generator Temperature","identifier":"pwr.temp","units":"C","type":"float"},{"name":"Generator Current","identifier":"pwr.c","units":"A","type":"float"},{"name":"Generator Voltage","identifier":"pwr.v","units":"V","type":"float"}]}]}}
|
||||||
|
|
||||||
Now that the example server's interface is reasonably well-understood, a plugin
|
Now that the example server's interface is reasonably well-understood, a plugin
|
||||||
can be written to adapt Open MCT Web to utilize it.
|
can be written to adapt Open MCT to utilize it.
|
||||||
|
|
||||||
### Step 1-Add a Top-level Object
|
### Step 1-Add a Top-level Object
|
||||||
|
|
||||||
Since Open MCT Web uses an "object-first" approach to accessing data, before
|
Since Open MCT uses an "object-first" approach to accessing data, before
|
||||||
we'll be able to do anything with this new data source, we'll need to have a
|
we'll be able to do anything with this new data source, we'll need to have a
|
||||||
way to explore the available measurements in the tree. In this step, we will
|
way to explore the available measurements in the tree. In this step, we will
|
||||||
add a top-level object which will serve as a container; in the next step, we
|
add a top-level object which will serve as a container; in the next step, we
|
||||||
@ -2276,7 +2276,7 @@ If we include this in our set of active bundles:
|
|||||||
|
|
||||||
__bundles.json__
|
__bundles.json__
|
||||||
|
|
||||||
...we will be able to reload Open MCT Web and see that it is present:
|
...we will be able to reload Open MCT and see that it is present:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@ -2287,7 +2287,7 @@ dictionary.
|
|||||||
|
|
||||||
In order to expose the telemetry dictionary, we first need to read it from the
|
In order to expose the telemetry dictionary, we first need to read it from the
|
||||||
server. Our first step will be to add a service that will handle interactions
|
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 Web directly, but will be
|
with the server; this will not be used by Open MCT directly, but will be
|
||||||
used by subsequent components we add.
|
used by subsequent components we add.
|
||||||
|
|
||||||
/*global define,WebSocket*/
|
/*global define,WebSocket*/
|
||||||
@ -2340,7 +2340,7 @@ Once the dictionary has been loaded, we will want to represent its contents
|
|||||||
as domain objects. Specifically, we want subsystems to appear as objects
|
as domain objects. Specifically, we want subsystems to appear as objects
|
||||||
under My Spacecraft, and measurements to appear as objects within those
|
under My Spacecraft, and measurements to appear as objects within those
|
||||||
subsystems. This means that we need to convert the data from the dictionary
|
subsystems. This means that we need to convert the data from the dictionary
|
||||||
into domain object models, and expose these to Open MCT Web via a
|
into domain object models, and expose these to Open MCT via a
|
||||||
`modelService`.
|
`modelService`.
|
||||||
|
|
||||||
/*global define*/
|
/*global define*/
|
||||||
@ -2449,16 +2449,16 @@ also prefix it with `example_tlm`:. This accomplishes a few things:
|
|||||||
* We can easily tell whether an identifier is expected to be in the
|
* We can easily tell whether an identifier is expected to be in the
|
||||||
dictionary or not.
|
dictionary or not.
|
||||||
* We avoid naming collisions with other model providers.
|
* We avoid naming collisions with other model providers.
|
||||||
* Finally, Open MCT Web uses the colon prefix as a hint that this domain
|
* Finally, Open MCT uses the colon prefix as a hint that this domain
|
||||||
object will not be in the persistence store.
|
object will not be in the persistence store.
|
||||||
* A couple of new types are introduced here (in the `type` field of the domain
|
* A couple of new types are introduced here (in the `type` field of the domain
|
||||||
object models we create); we will need to define these as extensions as well in
|
object models we create); we will need to define these as extensions as well in
|
||||||
order for them to display correctly.
|
order for them to display correctly.
|
||||||
* The `composition` field of each subsystem contained the Open MCT Web
|
* The `composition` field of each subsystem contained the Open MCT
|
||||||
identifiers of all the measurements in that subsystem. This `composition` field
|
identifiers of all the measurements in that subsystem. This `composition` field
|
||||||
will be used by Open MCT Web to determine what domain objects contain other
|
will be used by Open MCT to determine what domain objects contain other
|
||||||
domain objects (e.g. to populate the tree.)
|
domain objects (e.g. to populate the tree.)
|
||||||
* The `telemetry` field of each measurement will be used by Open MCT Web to
|
* The `telemetry` field of each measurement will be used by Open MCT to
|
||||||
understand how to request and interpret telemetry data for this object. The
|
understand how to request and interpret telemetry data for this object. The
|
||||||
`key` is the machine-readable identifier for this measurement within the
|
`key` is the machine-readable identifier for this measurement within the
|
||||||
telemetry system; the `ranges` provide metadata about the values for this data.
|
telemetry system; the `ranges` provide metadata about the values for this data.
|
||||||
@ -2637,7 +2637,7 @@ overridden if defined anywhere else, allowing configuration bundles to specify
|
|||||||
different URLs for the WebSocket connection.
|
different URLs for the WebSocket connection.
|
||||||
* The initializer script is registered using the `runs` category of extension,
|
* The initializer script is registered using the `runs` category of extension,
|
||||||
to ensure that this executes (and populates the contents of the top-level My
|
to ensure that this executes (and populates the contents of the top-level My
|
||||||
Spacecraft object) once Open MCT Web is started.
|
Spacecraft object) once Open MCT is started.
|
||||||
* This depends upon the `example.adapter` service we exposed above, as well
|
* This depends upon the `example.adapter` service we exposed above, as well
|
||||||
as Angular's `$q`; these services will be made available in the constructor
|
as Angular's `$q`; these services will be made available in the constructor
|
||||||
call.
|
call.
|
||||||
@ -2648,7 +2648,7 @@ this is registered under the extension category `components`.
|
|||||||
we exposed above, as well as Angular's `$q`; these services will be made
|
we exposed above, as well as Angular's `$q`; these services will be made
|
||||||
available in the constructor call.
|
available in the constructor call.
|
||||||
|
|
||||||
Now if we run Open MCT Web (assuming our example telemetry server is also
|
Now if we run Open MCT (assuming our example telemetry server is also
|
||||||
running) and expand our top-level node completely, we see the contents of our
|
running) and expand our top-level node completely, we see the contents of our
|
||||||
dictionary:
|
dictionary:
|
||||||
|
|
||||||
@ -2793,7 +2793,7 @@ that will resolve only when all histories have been packaged. Promise-chaining
|
|||||||
is used to ensure that the resolved value will be the fully-packaged data.
|
is used to ensure that the resolved value will be the fully-packaged data.
|
||||||
|
|
||||||
It is worth mentioning here that the `requests` we receive should look a little
|
It is worth mentioning here that the `requests` we receive should look a little
|
||||||
familiar. When Open MCT Web generates a `request` object associated with a
|
familiar. When Open MCT generates a `request` object associated with a
|
||||||
domain object, it does so by merging together three JavaScript objects:
|
domain object, it does so by merging together three JavaScript objects:
|
||||||
|
|
||||||
* First, the `telemetry` property from that domain object's type definition.
|
* First, the `telemetry` property from that domain object's type definition.
|
||||||
@ -2936,7 +2936,7 @@ back to see it. We can fix this by adding support for telemetry subscriptions.
|
|||||||
### Step 4-Real-time Telemetry
|
### Step 4-Real-time Telemetry
|
||||||
|
|
||||||
Finally, we want to utilize the server's ability to subscribe to telemetry
|
Finally, we want to utilize the server's ability to subscribe to telemetry
|
||||||
from Open MCT Web. To do this, first we want to expose some new methods for
|
from Open MCT. To do this, first we want to expose some new methods for
|
||||||
this from our server adapter:
|
this from our server adapter:
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
@ -3116,6 +3116,6 @@ we issue an unsubscribe request. (We don't take any care to avoid issuing
|
|||||||
multiple subscribe requests to the server, because we happen to know that the
|
multiple subscribe requests to the server, because we happen to know that the
|
||||||
server can handle this.)
|
server can handle this.)
|
||||||
|
|
||||||
Running Open MCT Web again, we can still plot our historical telemetry - but
|
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
|
now we also see that it updates in real-time as more data comes in from the
|
||||||
server.
|
server.
|
||||||
|
@ -94,7 +94,8 @@ gulp.task('stylesheets', function () {
|
|||||||
.pipe(sourcemaps.init())
|
.pipe(sourcemaps.init())
|
||||||
.pipe(sass(options.sass).on('error', sass.logError))
|
.pipe(sass(options.sass).on('error', sass.logError))
|
||||||
.pipe(rename(function (file) {
|
.pipe(rename(function (file) {
|
||||||
file.dirname = file.dirname.replace('/sass', '/css');
|
file.dirname =
|
||||||
|
file.dirname.replace(path.sep + 'sass', path.sep + 'css');
|
||||||
return file;
|
return file;
|
||||||
}))
|
}))
|
||||||
.pipe(sourcemaps.write('.'))
|
.pipe(sourcemaps.write('.'))
|
||||||
|
@ -30,7 +30,11 @@
|
|||||||
</script>
|
</script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
require(['main'], function (mct) {
|
require(['main'], function (mct) {
|
||||||
mct.run();
|
require([
|
||||||
|
'./example/imagery/bundle',
|
||||||
|
'./example/eventGenerator/bundle',
|
||||||
|
'./example/generator/bundle'
|
||||||
|
], mct.run.bind(mct));
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<link rel="stylesheet" href="platform/commonUI/general/res/css/startup-base.css">
|
<link rel="stylesheet" href="platform/commonUI/general/res/css/startup-base.css">
|
||||||
|
6
main.js
6
main.js
@ -92,11 +92,7 @@ define([
|
|||||||
'./platform/entanglement/bundle',
|
'./platform/entanglement/bundle',
|
||||||
'./platform/search/bundle',
|
'./platform/search/bundle',
|
||||||
'./platform/status/bundle',
|
'./platform/status/bundle',
|
||||||
'./platform/commonUI/regions/bundle',
|
'./platform/commonUI/regions/bundle'
|
||||||
|
|
||||||
'./example/imagery/bundle',
|
|
||||||
'./example/eventGenerator/bundle',
|
|
||||||
'./example/generator/bundle'
|
|
||||||
], function (Main, legacyRegistry) {
|
], function (Main, legacyRegistry) {
|
||||||
return {
|
return {
|
||||||
legacyRegistry: legacyRegistry,
|
legacyRegistry: legacyRegistry,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "openmctweb",
|
"name": "openmct",
|
||||||
"version": "0.10.0-SNAPSHOT",
|
"version": "0.10.1-SNAPSHOT",
|
||||||
"description": "The Open MCT core platform",
|
"description": "The Open MCT core platform",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^4.13.1",
|
"express": "^4.13.1",
|
||||||
@ -50,11 +50,11 @@
|
|||||||
"jsdoc": "jsdoc -c jsdoc.json -r -d target/docs/api",
|
"jsdoc": "jsdoc -c jsdoc.json -r -d target/docs/api",
|
||||||
"otherdoc": "node docs/gendocs.js --in docs/src --out target/docs --suppress-toc 'docs/src/index.md|docs/src/process/index.md'",
|
"otherdoc": "node docs/gendocs.js --in docs/src --out target/docs --suppress-toc 'docs/src/index.md|docs/src/process/index.md'",
|
||||||
"docs": "npm run jsdoc ; npm run otherdoc",
|
"docs": "npm run jsdoc ; npm run otherdoc",
|
||||||
"prepublish": "./node_modules/bower/bin/bower install && ./node_modules/gulp/bin/gulp.js install"
|
"prepublish": "node ./node_modules/bower/bin/bower install && node ./node_modules/gulp/bin/gulp.js install"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/nasa/openmctweb.git"
|
"url": "https://github.com/nasa/openmct.git"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
<div class="abs t-about l-about t-about-openmctweb s-about" ng-controller = "AboutController as about">
|
<div class="abs t-about l-about t-about-openmctweb s-about" ng-controller = "AboutController as about">
|
||||||
<div class="l-splash s-splash"></div>
|
<div class="l-splash s-splash"></div>
|
||||||
<div class="s-text l-content">
|
<div class="s-text l-content">
|
||||||
<h1 class="l-title s-title">OpenMCT Web</h1>
|
<h1 class="l-title s-title">Open MCT</h1>
|
||||||
<div class="l-description s-description">
|
<div class="l-description s-description">
|
||||||
<p>Open MCT Web, Copyright © 2014-2015, United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All rights reserved.</p>
|
<p>Open MCT Web, Copyright © 2014-2015, United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All rights reserved.</p>
|
||||||
<p>Open MCT Web is licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at <a target="_blank" href="http://www.apache.org/licenses/LICENSE-2.0">http://www.apache.org/licenses/LICENSE-2.0</a>.</p>
|
<p>Open MCT Web is licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at <a target="_blank" href="http://www.apache.org/licenses/LICENSE-2.0">http://www.apache.org/licenses/LICENSE-2.0</a>.</p>
|
||||||
|
@ -31,11 +31,13 @@ define([
|
|||||||
"./src/actions/PropertiesAction",
|
"./src/actions/PropertiesAction",
|
||||||
"./src/actions/RemoveAction",
|
"./src/actions/RemoveAction",
|
||||||
"./src/actions/SaveAction",
|
"./src/actions/SaveAction",
|
||||||
|
"./src/actions/SaveAsAction",
|
||||||
"./src/actions/CancelAction",
|
"./src/actions/CancelAction",
|
||||||
"./src/policies/EditActionPolicy",
|
"./src/policies/EditActionPolicy",
|
||||||
"./src/policies/EditableLinkPolicy",
|
"./src/policies/EditableLinkPolicy",
|
||||||
"./src/policies/EditableMovePolicy",
|
"./src/policies/EditableMovePolicy",
|
||||||
"./src/policies/EditNavigationPolicy",
|
"./src/policies/EditNavigationPolicy",
|
||||||
|
"./src/policies/EditContextualActionPolicy",
|
||||||
"./src/representers/EditRepresenter",
|
"./src/representers/EditRepresenter",
|
||||||
"./src/representers/EditToolbarRepresenter",
|
"./src/representers/EditToolbarRepresenter",
|
||||||
"text!./res/templates/library.html",
|
"text!./res/templates/library.html",
|
||||||
@ -55,11 +57,13 @@ define([
|
|||||||
PropertiesAction,
|
PropertiesAction,
|
||||||
RemoveAction,
|
RemoveAction,
|
||||||
SaveAction,
|
SaveAction,
|
||||||
|
SaveAsAction,
|
||||||
CancelAction,
|
CancelAction,
|
||||||
EditActionPolicy,
|
EditActionPolicy,
|
||||||
EditableLinkPolicy,
|
EditableLinkPolicy,
|
||||||
EditableMovePolicy,
|
EditableMovePolicy,
|
||||||
EditNavigationPolicy,
|
EditNavigationPolicy,
|
||||||
|
EditContextualActionPolicy,
|
||||||
EditRepresenter,
|
EditRepresenter,
|
||||||
EditToolbarRepresenter,
|
EditToolbarRepresenter,
|
||||||
libraryTemplate,
|
libraryTemplate,
|
||||||
@ -163,6 +167,15 @@ define([
|
|||||||
"implementation": SaveAction,
|
"implementation": SaveAction,
|
||||||
"name": "Save",
|
"name": "Save",
|
||||||
"description": "Save changes made to these objects.",
|
"description": "Save changes made to these objects.",
|
||||||
|
"depends": [],
|
||||||
|
"priority": "mandatory"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "save",
|
||||||
|
"category": "conclude-editing",
|
||||||
|
"implementation": SaveAsAction,
|
||||||
|
"name": "Save",
|
||||||
|
"description": "Save changes made to these objects.",
|
||||||
"depends": [
|
"depends": [
|
||||||
"$injector",
|
"$injector",
|
||||||
"policyService",
|
"policyService",
|
||||||
@ -189,6 +202,11 @@ define([
|
|||||||
"category": "action",
|
"category": "action",
|
||||||
"implementation": EditActionPolicy
|
"implementation": EditActionPolicy
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"category": "action",
|
||||||
|
"implementation": EditContextualActionPolicy,
|
||||||
|
"depends": ["navigationService", "editModeBlacklist", "nonEditContextBlacklist"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"category": "action",
|
"category": "action",
|
||||||
"implementation": EditableMovePolicy
|
"implementation": EditableMovePolicy
|
||||||
@ -254,6 +272,16 @@ define([
|
|||||||
{
|
{
|
||||||
"implementation": EditToolbarRepresenter
|
"implementation": EditToolbarRepresenter
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"constants": [
|
||||||
|
{
|
||||||
|
"key":"editModeBlacklist",
|
||||||
|
"value": ["copy", "follow", "window", "link", "locate"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "nonEditContextBlacklist",
|
||||||
|
"value": ["copy", "follow", "properties", "move", "link", "remove", "locate"]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -21,8 +21,8 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define(
|
define(
|
||||||
['../../../browse/src/creation/CreateWizard'],
|
[],
|
||||||
function (CreateWizard) {
|
function () {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The "Save" action; the action triggered by clicking Save from
|
* The "Save" action; the action triggered by clicking Save from
|
||||||
@ -33,31 +33,11 @@ define(
|
|||||||
* @memberof platform/commonUI/edit
|
* @memberof platform/commonUI/edit
|
||||||
*/
|
*/
|
||||||
function SaveAction(
|
function SaveAction(
|
||||||
$injector,
|
|
||||||
policyService,
|
|
||||||
dialogService,
|
|
||||||
creationService,
|
|
||||||
copyService,
|
|
||||||
context
|
context
|
||||||
) {
|
) {
|
||||||
this.domainObject = (context || {}).domainObject;
|
this.domainObject = (context || {}).domainObject;
|
||||||
this.injectObjectService = function(){
|
|
||||||
this.objectService = $injector.get("objectService");
|
|
||||||
};
|
|
||||||
this.policyService = policyService;
|
|
||||||
this.dialogService = dialogService;
|
|
||||||
this.creationService = creationService;
|
|
||||||
this.copyService = copyService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SaveAction.prototype.getObjectService = function(){
|
|
||||||
// Lazily acquire object service (avoids cyclical dependency)
|
|
||||||
if (!this.objectService) {
|
|
||||||
this.injectObjectService();
|
|
||||||
}
|
|
||||||
return this.objectService;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save changes and conclude editing.
|
* Save changes and conclude editing.
|
||||||
*
|
*
|
||||||
@ -66,9 +46,7 @@ define(
|
|||||||
* @memberof platform/commonUI/edit.SaveAction#
|
* @memberof platform/commonUI/edit.SaveAction#
|
||||||
*/
|
*/
|
||||||
SaveAction.prototype.perform = function () {
|
SaveAction.prototype.perform = function () {
|
||||||
var domainObject = this.domainObject,
|
var domainObject = this.domainObject;
|
||||||
copyService = this.copyService,
|
|
||||||
self = this;
|
|
||||||
|
|
||||||
function resolveWith(object){
|
function resolveWith(object){
|
||||||
return function () {
|
return function () {
|
||||||
@ -76,63 +54,13 @@ define(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function doWizardSave(parent) {
|
|
||||||
var wizard = new CreateWizard(
|
|
||||||
domainObject,
|
|
||||||
parent,
|
|
||||||
self.policyService
|
|
||||||
);
|
|
||||||
|
|
||||||
return self.dialogService
|
|
||||||
.getUserInput(
|
|
||||||
wizard.getFormStructure(true),
|
|
||||||
wizard.getInitialFormValue()
|
|
||||||
)
|
|
||||||
.then(wizard.populateObjectFromInput.bind(wizard));
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetchObject(objectId){
|
|
||||||
return self.getObjectService().getObjects([objectId]).then(function(objects){
|
|
||||||
return objects[objectId];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getParent(object){
|
|
||||||
return fetchObject(object.getModel().location);
|
|
||||||
}
|
|
||||||
|
|
||||||
function allowClone(objectToClone) {
|
|
||||||
return (objectToClone.getId() === domainObject.getId()) ||
|
|
||||||
objectToClone.getCapability('location').isOriginal();
|
|
||||||
}
|
|
||||||
|
|
||||||
function cloneIntoParent(parent) {
|
|
||||||
return copyService.perform(domainObject, parent, allowClone);
|
|
||||||
}
|
|
||||||
|
|
||||||
function cancelEditingAfterClone(clonedObject) {
|
|
||||||
return domainObject.getCapability("editor").cancel()
|
|
||||||
.then(resolveWith(clonedObject));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invoke any save behavior introduced by the editor capability;
|
// Invoke any save behavior introduced by the editor capability;
|
||||||
// this is introduced by EditableDomainObject which is
|
// this is introduced by EditableDomainObject which is
|
||||||
// used to insulate underlying objects from changes made
|
// used to insulate underlying objects from changes made
|
||||||
// during editing.
|
// during editing.
|
||||||
function doSave() {
|
function doSave() {
|
||||||
//This is a new 'virtual object' that has not been persisted
|
return domainObject.getCapability("editor").save()
|
||||||
// yet.
|
.then(resolveWith(domainObject.getOriginalObject()));
|
||||||
if (domainObject.getModel().persisted === undefined){
|
|
||||||
return getParent(domainObject)
|
|
||||||
.then(doWizardSave)
|
|
||||||
.then(getParent)
|
|
||||||
.then(cloneIntoParent)
|
|
||||||
.then(cancelEditingAfterClone)
|
|
||||||
.catch(resolveWith(false));
|
|
||||||
} else {
|
|
||||||
return domainObject.getCapability("editor").save()
|
|
||||||
.then(resolveWith(domainObject.getOriginalObject()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Discard the current root view (which will be the editing
|
// Discard the current root view (which will be the editing
|
||||||
@ -157,7 +85,8 @@ define(
|
|||||||
SaveAction.appliesTo = function (context) {
|
SaveAction.appliesTo = function (context) {
|
||||||
var domainObject = (context || {}).domainObject;
|
var domainObject = (context || {}).domainObject;
|
||||||
return domainObject !== undefined &&
|
return domainObject !== undefined &&
|
||||||
domainObject.hasCapability("editor");
|
domainObject.hasCapability("editor") &&
|
||||||
|
domainObject.getModel().persisted !== undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
return SaveAction;
|
return SaveAction;
|
||||||
|
166
platform/commonUI/edit/src/actions/SaveAsAction.js
Normal file
166
platform/commonUI/edit/src/actions/SaveAsAction.js
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
define(
|
||||||
|
['../../../browse/src/creation/CreateWizard'],
|
||||||
|
function (CreateWizard) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The "Save" action; the action triggered by clicking Save from
|
||||||
|
* Edit Mode. Exits the editing user interface and invokes object
|
||||||
|
* capabilities to persist the changes that have been made.
|
||||||
|
* @constructor
|
||||||
|
* @implements {Action}
|
||||||
|
* @memberof platform/commonUI/edit
|
||||||
|
*/
|
||||||
|
function SaveAsAction(
|
||||||
|
$injector,
|
||||||
|
policyService,
|
||||||
|
dialogService,
|
||||||
|
creationService,
|
||||||
|
copyService,
|
||||||
|
context
|
||||||
|
) {
|
||||||
|
this.domainObject = (context || {}).domainObject;
|
||||||
|
this.injectObjectService = function(){
|
||||||
|
this.objectService = $injector.get("objectService");
|
||||||
|
};
|
||||||
|
this.policyService = policyService;
|
||||||
|
this.dialogService = dialogService;
|
||||||
|
this.creationService = creationService;
|
||||||
|
this.copyService = copyService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
SaveAsAction.prototype.createWizard = function (parent) {
|
||||||
|
return new CreateWizard(
|
||||||
|
this.domainObject,
|
||||||
|
parent,
|
||||||
|
this.policyService
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
SaveAsAction.prototype.getObjectService = function(){
|
||||||
|
// Lazily acquire object service (avoids cyclical dependency)
|
||||||
|
if (!this.objectService) {
|
||||||
|
this.injectObjectService();
|
||||||
|
}
|
||||||
|
return this.objectService;
|
||||||
|
};
|
||||||
|
|
||||||
|
function resolveWith(object){
|
||||||
|
return function () {
|
||||||
|
return object;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save changes and conclude editing.
|
||||||
|
*
|
||||||
|
* @returns {Promise} a promise that will be fulfilled when
|
||||||
|
* cancellation has completed
|
||||||
|
* @memberof platform/commonUI/edit.SaveAction#
|
||||||
|
*/
|
||||||
|
SaveAsAction.prototype.perform = function () {
|
||||||
|
// Discard the current root view (which will be the editing
|
||||||
|
// UI, which will have been pushed atop the Browse UI.)
|
||||||
|
function returnToBrowse(object) {
|
||||||
|
if (object) {
|
||||||
|
object.getCapability("action").perform("navigate");
|
||||||
|
}
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.save().then(returnToBrowse);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
SaveAsAction.prototype.save = function () {
|
||||||
|
var self = this,
|
||||||
|
domainObject = this.domainObject,
|
||||||
|
copyService = this.copyService;
|
||||||
|
|
||||||
|
function doWizardSave(parent) {
|
||||||
|
var wizard = self.createWizard(parent);
|
||||||
|
|
||||||
|
return self.dialogService
|
||||||
|
.getUserInput(wizard.getFormStructure(true),
|
||||||
|
wizard.getInitialFormValue()
|
||||||
|
).then(wizard.populateObjectFromInput.bind(wizard));
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchObject(objectId){
|
||||||
|
return self.getObjectService().getObjects([objectId]).then(function(objects){
|
||||||
|
return objects[objectId];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getParent(object){
|
||||||
|
return fetchObject(object.getModel().location);
|
||||||
|
}
|
||||||
|
|
||||||
|
function allowClone(objectToClone) {
|
||||||
|
return (objectToClone.getId() === domainObject.getId()) ||
|
||||||
|
objectToClone.getCapability('location').isOriginal();
|
||||||
|
}
|
||||||
|
|
||||||
|
function cloneIntoParent(parent) {
|
||||||
|
return copyService.perform(domainObject, parent, allowClone);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelEditingAfterClone(clonedObject) {
|
||||||
|
return domainObject.getCapability("editor").cancel()
|
||||||
|
.then(resolveWith(clonedObject));
|
||||||
|
}
|
||||||
|
|
||||||
|
return getParent(domainObject)
|
||||||
|
.then(doWizardSave)
|
||||||
|
.then(getParent)
|
||||||
|
.then(cloneIntoParent)
|
||||||
|
.then(cancelEditingAfterClone)
|
||||||
|
.catch(resolveWith(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this action is applicable in a given context.
|
||||||
|
* This will ensure that a domain object is present in the context,
|
||||||
|
* and that this domain object is in Edit mode.
|
||||||
|
* @returns true if applicable
|
||||||
|
*/
|
||||||
|
SaveAsAction.appliesTo = function (context) {
|
||||||
|
var domainObject = (context || {}).domainObject;
|
||||||
|
return domainObject !== undefined &&
|
||||||
|
domainObject.hasCapability("editor") &&
|
||||||
|
domainObject.getModel().persisted === undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
return SaveAsAction;
|
||||||
|
}
|
||||||
|
);
|
@ -36,14 +36,6 @@ define(
|
|||||||
this.policyService = policyService;
|
this.policyService = policyService;
|
||||||
}
|
}
|
||||||
|
|
||||||
function applicableView(key){
|
|
||||||
return ['plot', 'scrolling'].indexOf(key) >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function editableType(key){
|
|
||||||
return key === 'telemetry.panel';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a count of views which are not flagged as non-editable.
|
* Get a count of views which are not flagged as non-editable.
|
||||||
* @private
|
* @private
|
||||||
@ -63,7 +55,8 @@ define(
|
|||||||
|
|
||||||
// A view is editable unless explicitly flagged as not
|
// A view is editable unless explicitly flagged as not
|
||||||
(views || []).forEach(function (view) {
|
(views || []).forEach(function (view) {
|
||||||
if (view.editable === true || (applicableView(view.key) && editableType(type.getKey()))) {
|
if (view.editable === true ||
|
||||||
|
(view.key === 'plot' && type.getKey() === 'telemetry.panel')) {
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[],
|
||||||
|
function () {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Policy controlling whether the context menu is visible when
|
||||||
|
* objects are being edited
|
||||||
|
* @param navigationService
|
||||||
|
* @param editModeBlacklist A blacklist of actions disallowed from
|
||||||
|
* context menu when navigated object is being edited
|
||||||
|
* @param nonEditContextBlacklist A blacklist of actions disallowed
|
||||||
|
* from context menu of non-editable objects, when navigated object
|
||||||
|
* is being edited
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function EditContextualActionPolicy(navigationService, editModeBlacklist, nonEditContextBlacklist) {
|
||||||
|
this.navigationService = navigationService;
|
||||||
|
|
||||||
|
//The list of objects disallowed on target object when in edit mode
|
||||||
|
this.editModeBlacklist = editModeBlacklist;
|
||||||
|
//The list of objects disallowed on target object that is not in
|
||||||
|
// edit mode (ie. the context menu in the tree on the LHS).
|
||||||
|
this.nonEditContextBlacklist = nonEditContextBlacklist;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isParentEditable(object) {
|
||||||
|
var parent = object.hasCapability("context") && object.getCapability("context").getParent();
|
||||||
|
return !!parent && parent.hasCapability("editor");
|
||||||
|
}
|
||||||
|
|
||||||
|
EditContextualActionPolicy.prototype.allow = function (action, context) {
|
||||||
|
var selectedObject = context.domainObject,
|
||||||
|
navigatedObject = this.navigationService.getNavigation(),
|
||||||
|
actionMetadata = action.getMetadata ? action.getMetadata() : {};
|
||||||
|
|
||||||
|
if (navigatedObject.hasCapability('editor')) {
|
||||||
|
if (selectedObject.hasCapability('editor') || isParentEditable(selectedObject)){
|
||||||
|
return this.editModeBlacklist.indexOf(actionMetadata.key) === -1;
|
||||||
|
} else {
|
||||||
|
//Target is in the context menu
|
||||||
|
return this.nonEditContextBlacklist.indexOf(actionMetadata.key) === -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return EditContextualActionPolicy;
|
||||||
|
}
|
||||||
|
);
|
@ -25,11 +25,11 @@ define(
|
|||||||
function (SaveAction) {
|
function (SaveAction) {
|
||||||
|
|
||||||
describe("The Save action", function () {
|
describe("The Save action", function () {
|
||||||
var mockLocation,
|
var mockDomainObject,
|
||||||
mockDomainObject,
|
|
||||||
mockEditorCapability,
|
mockEditorCapability,
|
||||||
mockUrlService,
|
|
||||||
actionContext,
|
actionContext,
|
||||||
|
mockActionCapability,
|
||||||
|
capabilities = {},
|
||||||
action;
|
action;
|
||||||
|
|
||||||
function mockPromise(value) {
|
function mockPromise(value) {
|
||||||
@ -41,65 +41,62 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
mockLocation = jasmine.createSpyObj(
|
|
||||||
"$location",
|
|
||||||
[ "path" ]
|
|
||||||
);
|
|
||||||
mockDomainObject = jasmine.createSpyObj(
|
mockDomainObject = jasmine.createSpyObj(
|
||||||
"domainObject",
|
"domainObject",
|
||||||
[ "getCapability", "hasCapability" ]
|
[
|
||||||
|
"getCapability",
|
||||||
|
"hasCapability",
|
||||||
|
"getModel",
|
||||||
|
"getOriginalObject"
|
||||||
|
]
|
||||||
);
|
);
|
||||||
mockEditorCapability = jasmine.createSpyObj(
|
mockEditorCapability = jasmine.createSpyObj(
|
||||||
"editor",
|
"editor",
|
||||||
[ "save", "cancel" ]
|
[ "save", "cancel" ]
|
||||||
);
|
);
|
||||||
mockUrlService = jasmine.createSpyObj(
|
mockActionCapability = jasmine.createSpyObj(
|
||||||
"urlService",
|
"actionCapability",
|
||||||
["urlForLocation"]
|
[ "perform"]
|
||||||
);
|
);
|
||||||
|
capabilities.editor = mockEditorCapability;
|
||||||
|
capabilities.action = mockActionCapability;
|
||||||
|
|
||||||
actionContext = {
|
actionContext = {
|
||||||
domainObject: mockDomainObject
|
domainObject: mockDomainObject
|
||||||
};
|
};
|
||||||
|
|
||||||
mockDomainObject.hasCapability.andReturn(true);
|
mockDomainObject.hasCapability.andReturn(true);
|
||||||
mockDomainObject.getCapability.andReturn(mockEditorCapability);
|
mockDomainObject.getCapability.andCallFake(function (capability) {
|
||||||
|
return capabilities[capability];
|
||||||
|
});
|
||||||
|
mockDomainObject.getModel.andReturn({persisted: 0});
|
||||||
mockEditorCapability.save.andReturn(mockPromise(true));
|
mockEditorCapability.save.andReturn(mockPromise(true));
|
||||||
|
mockDomainObject.getOriginalObject.andReturn(mockDomainObject);
|
||||||
|
|
||||||
action = new SaveAction(mockLocation, mockUrlService, actionContext);
|
action = new SaveAction(actionContext);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("only applies to domain object with an editor capability", function () {
|
it("only applies to domain object with an editor capability", function () {
|
||||||
expect(SaveAction.appliesTo(actionContext)).toBeTruthy();
|
expect(SaveAction.appliesTo(actionContext)).toBe(true);
|
||||||
expect(mockDomainObject.hasCapability).toHaveBeenCalledWith("editor");
|
expect(mockDomainObject.hasCapability).toHaveBeenCalledWith("editor");
|
||||||
|
|
||||||
mockDomainObject.hasCapability.andReturn(false);
|
mockDomainObject.hasCapability.andReturn(false);
|
||||||
mockDomainObject.getCapability.andReturn(undefined);
|
mockDomainObject.getCapability.andReturn(undefined);
|
||||||
expect(SaveAction.appliesTo(actionContext)).toBeFalsy();
|
expect(SaveAction.appliesTo(actionContext)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
//TODO: Disabled for NEM Beta
|
it("only applies to domain object that has already been persisted",
|
||||||
xit("invokes the editor capability's save functionality when performed", function () {
|
function () {
|
||||||
// Verify precondition
|
mockDomainObject.getModel.andReturn({persisted: undefined});
|
||||||
expect(mockEditorCapability.save).not.toHaveBeenCalled();
|
expect(SaveAction.appliesTo(actionContext)).toBe(false);
|
||||||
action.perform();
|
|
||||||
|
|
||||||
// Should have called cancel
|
|
||||||
expect(mockEditorCapability.save).toHaveBeenCalled();
|
|
||||||
|
|
||||||
// Also shouldn't call cancel
|
|
||||||
expect(mockEditorCapability.cancel).not.toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//TODO: Disabled for NEM Beta
|
it("uses the editor capability to save the object",
|
||||||
xit("returns to browse when performed", function () {
|
function () {
|
||||||
action.perform();
|
action.perform();
|
||||||
expect(mockLocation.path).toHaveBeenCalledWith(
|
expect(mockEditorCapability.save).toHaveBeenCalled();
|
||||||
mockUrlService.urlForLocation("browse", mockDomainObject)
|
});
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
173
platform/commonUI/edit/test/actions/SaveAsActionSpec.js
Normal file
173
platform/commonUI/edit/test/actions/SaveAsActionSpec.js
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global describe,it,expect,beforeEach,jasmine*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
["../../src/actions/SaveAsAction"],
|
||||||
|
function (SaveAsAction) {
|
||||||
|
|
||||||
|
describe("The Save As action", function () {
|
||||||
|
var mockDomainObject,
|
||||||
|
mockEditorCapability,
|
||||||
|
mockActionCapability,
|
||||||
|
mockObjectService,
|
||||||
|
mockDialogService,
|
||||||
|
mockCopyService,
|
||||||
|
mockParent,
|
||||||
|
mockUrlService,
|
||||||
|
actionContext,
|
||||||
|
capabilities = {},
|
||||||
|
action;
|
||||||
|
|
||||||
|
function noop () {}
|
||||||
|
|
||||||
|
function mockPromise(value) {
|
||||||
|
return (value || {}).then ? value :
|
||||||
|
{
|
||||||
|
then: function (callback) {
|
||||||
|
return mockPromise(callback(value));
|
||||||
|
},
|
||||||
|
catch: function (callback) {
|
||||||
|
return mockPromise(callback(value));
|
||||||
|
}
|
||||||
|
} ;
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
mockDomainObject = jasmine.createSpyObj(
|
||||||
|
"domainObject",
|
||||||
|
[
|
||||||
|
"getCapability",
|
||||||
|
"hasCapability",
|
||||||
|
"getModel"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
mockDomainObject.hasCapability.andReturn(true);
|
||||||
|
mockDomainObject.getCapability.andCallFake(function (capability) {
|
||||||
|
return capabilities[capability];
|
||||||
|
});
|
||||||
|
mockDomainObject.getModel.andReturn({location: 'a', persisted: undefined});
|
||||||
|
|
||||||
|
mockParent = jasmine.createSpyObj(
|
||||||
|
"parentObject",
|
||||||
|
[
|
||||||
|
"getCapability",
|
||||||
|
"hasCapability",
|
||||||
|
"getModel"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
mockEditorCapability = jasmine.createSpyObj(
|
||||||
|
"editor",
|
||||||
|
[ "save", "cancel" ]
|
||||||
|
);
|
||||||
|
mockEditorCapability.cancel.andReturn(mockPromise(undefined));
|
||||||
|
mockEditorCapability.save.andReturn(mockPromise(true));
|
||||||
|
capabilities.editor = mockEditorCapability;
|
||||||
|
|
||||||
|
mockActionCapability = jasmine.createSpyObj(
|
||||||
|
"action",
|
||||||
|
["perform"]
|
||||||
|
);
|
||||||
|
capabilities.action = mockActionCapability;
|
||||||
|
|
||||||
|
mockObjectService = jasmine.createSpyObj(
|
||||||
|
"objectService",
|
||||||
|
["getObjects"]
|
||||||
|
);
|
||||||
|
mockObjectService.getObjects.andReturn(mockPromise({'a': mockParent}));
|
||||||
|
|
||||||
|
mockDialogService = jasmine.createSpyObj(
|
||||||
|
"dialogService",
|
||||||
|
[
|
||||||
|
"getUserInput"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
mockDialogService.getUserInput.andReturn(mockPromise(undefined));
|
||||||
|
|
||||||
|
mockCopyService = jasmine.createSpyObj(
|
||||||
|
"copyService",
|
||||||
|
[
|
||||||
|
"perform"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
mockUrlService = jasmine.createSpyObj(
|
||||||
|
"urlService",
|
||||||
|
["urlForLocation"]
|
||||||
|
);
|
||||||
|
|
||||||
|
actionContext = {
|
||||||
|
domainObject: mockDomainObject
|
||||||
|
};
|
||||||
|
|
||||||
|
action = new SaveAsAction(undefined, undefined, mockDialogService, undefined, mockCopyService, actionContext);
|
||||||
|
|
||||||
|
spyOn(action, "getObjectService");
|
||||||
|
action.getObjectService.andReturn(mockObjectService);
|
||||||
|
|
||||||
|
spyOn(action, "createWizard");
|
||||||
|
action.createWizard.andReturn({
|
||||||
|
getFormStructure: noop,
|
||||||
|
getInitialFormValue: noop,
|
||||||
|
populateObjectFromInput: function() {
|
||||||
|
return mockDomainObject;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("only applies to domain object with an editor capability", function () {
|
||||||
|
expect(SaveAsAction.appliesTo(actionContext)).toBe(true);
|
||||||
|
expect(mockDomainObject.hasCapability).toHaveBeenCalledWith("editor");
|
||||||
|
|
||||||
|
mockDomainObject.hasCapability.andReturn(false);
|
||||||
|
mockDomainObject.getCapability.andReturn(undefined);
|
||||||
|
expect(SaveAsAction.appliesTo(actionContext)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("only applies to domain object that has not already been" +
|
||||||
|
" persisted", function () {
|
||||||
|
expect(SaveAsAction.appliesTo(actionContext)).toBe(true);
|
||||||
|
expect(mockDomainObject.hasCapability).toHaveBeenCalledWith("editor");
|
||||||
|
|
||||||
|
mockDomainObject.getModel.andReturn({persisted: 0});
|
||||||
|
expect(SaveAsAction.appliesTo(actionContext)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns to browse after save", function () {
|
||||||
|
spyOn(action, "save");
|
||||||
|
action.save.andReturn(mockPromise(mockDomainObject));
|
||||||
|
action.perform();
|
||||||
|
expect(mockActionCapability.perform).toHaveBeenCalledWith(
|
||||||
|
"navigate"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("prompts the user for object details", function () {
|
||||||
|
action.perform();
|
||||||
|
expect(mockDialogService.getUserInput).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -33,19 +33,43 @@ define(
|
|||||||
mockDomainObject,
|
mockDomainObject,
|
||||||
mockEditAction,
|
mockEditAction,
|
||||||
mockPropertiesAction,
|
mockPropertiesAction,
|
||||||
|
mockTypeCapability,
|
||||||
|
mockStatusCapability,
|
||||||
|
capabilities,
|
||||||
|
plotView,
|
||||||
policy;
|
policy;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
mockDomainObject = jasmine.createSpyObj(
|
mockDomainObject = jasmine.createSpyObj(
|
||||||
'domainObject',
|
'domainObject',
|
||||||
[ 'useCapability' ]
|
[
|
||||||
|
'useCapability',
|
||||||
|
'hasCapability',
|
||||||
|
'getCapability'
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
mockStatusCapability = jasmine.createSpyObj('statusCapability', ['get']);
|
||||||
|
mockStatusCapability.get.andReturn(false);
|
||||||
|
mockTypeCapability = jasmine.createSpyObj('type', ['getKey']);
|
||||||
|
capabilities = {
|
||||||
|
'status': mockStatusCapability,
|
||||||
|
'type': mockTypeCapability
|
||||||
|
};
|
||||||
|
|
||||||
mockEditAction = jasmine.createSpyObj('edit', ['getMetadata']);
|
mockEditAction = jasmine.createSpyObj('edit', ['getMetadata']);
|
||||||
mockPropertiesAction = jasmine.createSpyObj('edit', ['getMetadata']);
|
mockPropertiesAction = jasmine.createSpyObj('edit', ['getMetadata']);
|
||||||
|
|
||||||
|
mockDomainObject.getCapability.andCallFake(function(capability){
|
||||||
|
return capabilities[capability];
|
||||||
|
});
|
||||||
|
mockDomainObject.hasCapability.andCallFake(function(capability){
|
||||||
|
return !!capabilities[capability];
|
||||||
|
});
|
||||||
|
|
||||||
editableView = { editable: true };
|
editableView = { editable: true };
|
||||||
nonEditableView = { editable: false };
|
nonEditableView = { editable: false };
|
||||||
undefinedView = { someKey: "some value" };
|
undefinedView = { someKey: "some value" };
|
||||||
|
plotView = { key: "plot", editable: false };
|
||||||
testViews = [];
|
testViews = [];
|
||||||
|
|
||||||
mockDomainObject.useCapability.andCallFake(function (c) {
|
mockDomainObject.useCapability.andCallFake(function (c) {
|
||||||
@ -64,38 +88,53 @@ define(
|
|||||||
policy = new EditActionPolicy();
|
policy = new EditActionPolicy();
|
||||||
});
|
});
|
||||||
|
|
||||||
//TODO: Disabled for NEM Beta
|
it("allows the edit action when there are editable views", function () {
|
||||||
xit("allows the edit action when there are editable views", function () {
|
|
||||||
testViews = [ editableView ];
|
testViews = [ editableView ];
|
||||||
expect(policy.allow(mockEditAction, testContext)).toBeTruthy();
|
expect(policy.allow(mockEditAction, testContext)).toBe(true);
|
||||||
// No edit flag defined; should be treated as editable
|
|
||||||
testViews = [ undefinedView, undefinedView ];
|
|
||||||
expect(policy.allow(mockEditAction, testContext)).toBeTruthy();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//TODO: Disabled for NEM Beta
|
it("allows the edit properties action when there are no editable views", function () {
|
||||||
xit("allows the edit properties action when there are no editable views", function () {
|
|
||||||
testViews = [ nonEditableView, nonEditableView ];
|
testViews = [ nonEditableView, nonEditableView ];
|
||||||
expect(policy.allow(mockPropertiesAction, testContext)).toBeTruthy();
|
expect(policy.allow(mockPropertiesAction, testContext)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
//TODO: Disabled for NEM Beta
|
it("disallows the edit action when there are no editable views", function () {
|
||||||
xit("disallows the edit action when there are no editable views", function () {
|
|
||||||
testViews = [ nonEditableView, nonEditableView ];
|
testViews = [ nonEditableView, nonEditableView ];
|
||||||
expect(policy.allow(mockEditAction, testContext)).toBeFalsy();
|
expect(policy.allow(mockEditAction, testContext)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
//TODO: Disabled for NEM Beta
|
it("disallows the edit properties action when there are" +
|
||||||
xit("disallows the edit properties action when there are" +
|
|
||||||
" editable views", function () {
|
" editable views", function () {
|
||||||
testViews = [ editableView ];
|
testViews = [ editableView ];
|
||||||
expect(policy.allow(mockPropertiesAction, testContext)).toBeFalsy();
|
expect(policy.allow(mockPropertiesAction, testContext)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("disallows the edit action when object is already being" +
|
||||||
|
" edited", function () {
|
||||||
|
testViews = [ editableView ];
|
||||||
|
mockStatusCapability.get.andReturn(true);
|
||||||
|
expect(policy.allow(mockEditAction, testContext)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows editing of panels in plot view", function () {
|
||||||
|
testViews = [ plotView ];
|
||||||
|
mockTypeCapability.getKey.andReturn('telemetry.panel');
|
||||||
|
|
||||||
|
expect(policy.allow(mockEditAction, testContext)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("disallows editing of plot view when object not a panel type", function () {
|
||||||
|
testViews = [ plotView ];
|
||||||
|
mockTypeCapability.getKey.andReturn('something.else');
|
||||||
|
|
||||||
|
expect(policy.allow(mockEditAction, testContext)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
it("allows the edit properties outside of the 'view-control' category", function () {
|
it("allows the edit properties outside of the 'view-control' category", function () {
|
||||||
testViews = [ nonEditableView ];
|
testViews = [ nonEditableView ];
|
||||||
testContext.category = "something-else";
|
testContext.category = "something-else";
|
||||||
expect(policy.allow(mockPropertiesAction, testContext)).toBeTruthy();
|
expect(policy.allow(mockPropertiesAction, testContext)).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,108 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
/*global describe,it,expect,beforeEach,jasmine*/
|
||||||
|
|
||||||
|
define(
|
||||||
|
["../../src/policies/EditContextualActionPolicy"],
|
||||||
|
function (EditContextualActionPolicy) {
|
||||||
|
|
||||||
|
describe("The Edit contextual action policy", function () {
|
||||||
|
var policy,
|
||||||
|
navigationService,
|
||||||
|
mockAction,
|
||||||
|
context,
|
||||||
|
navigatedObject,
|
||||||
|
mockDomainObject,
|
||||||
|
metadata,
|
||||||
|
editModeBlacklist = ["copy", "follow", "window", "link", "locate"],
|
||||||
|
nonEditContextBlacklist = ["copy", "follow", "properties", "move", "link", "remove", "locate"];
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
navigatedObject = jasmine.createSpyObj("navigatedObject", ["hasCapability"]);
|
||||||
|
navigatedObject.hasCapability.andReturn(false);
|
||||||
|
|
||||||
|
mockDomainObject = jasmine.createSpyObj("domainObject", ["hasCapability", "getCapability"]);
|
||||||
|
mockDomainObject.hasCapability.andReturn(false);
|
||||||
|
|
||||||
|
navigationService = jasmine.createSpyObj("navigationService", ["getNavigation"]);
|
||||||
|
navigationService.getNavigation.andReturn(navigatedObject);
|
||||||
|
|
||||||
|
metadata = {key: "move"};
|
||||||
|
mockAction = jasmine.createSpyObj("action", ["getMetadata"]);
|
||||||
|
mockAction.getMetadata.andReturn(metadata);
|
||||||
|
|
||||||
|
context = {domainObject: mockDomainObject};
|
||||||
|
|
||||||
|
policy = new EditContextualActionPolicy(navigationService, editModeBlacklist, nonEditContextBlacklist);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Allows all actions when navigated object not in edit mode', function() {
|
||||||
|
expect(policy.allow(mockAction, context)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Allows "window" action when navigated object in edit mode,' +
|
||||||
|
' but selected object not in edit mode ', function() {
|
||||||
|
navigatedObject.hasCapability.andReturn(true);
|
||||||
|
metadata.key = "window";
|
||||||
|
expect(policy.allow(mockAction, context)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Allows "remove" action when navigated object in edit mode,' +
|
||||||
|
' and selected object not editable, but its parent is.',
|
||||||
|
function() {
|
||||||
|
var mockParent = jasmine.createSpyObj("parentObject", ["hasCapability"]),
|
||||||
|
mockContextCapability = jasmine.createSpyObj("contextCapability", ["getParent"]);
|
||||||
|
|
||||||
|
mockParent.hasCapability.andReturn(true);
|
||||||
|
mockContextCapability.getParent.andReturn(mockParent);
|
||||||
|
navigatedObject.hasCapability.andReturn(true);
|
||||||
|
|
||||||
|
mockDomainObject.getCapability.andReturn(mockContextCapability);
|
||||||
|
mockDomainObject.hasCapability.andCallFake(function (capability) {
|
||||||
|
switch (capability) {
|
||||||
|
case "editor": return false;
|
||||||
|
case "context": return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
metadata.key = "remove";
|
||||||
|
|
||||||
|
expect(policy.allow(mockAction, context)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Disallows "move" action when navigated object in edit mode,' +
|
||||||
|
' but selected object not in edit mode ', function() {
|
||||||
|
navigatedObject.hasCapability.andReturn(true);
|
||||||
|
metadata.key = "move";
|
||||||
|
expect(policy.allow(mockAction, context)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Disallows copy action when navigated object and' +
|
||||||
|
' selected object in edit mode', function() {
|
||||||
|
navigatedObject.hasCapability.andReturn(true);
|
||||||
|
mockDomainObject.hasCapability.andReturn(true);
|
||||||
|
metadata.key = "copy";
|
||||||
|
expect(policy.allow(mockAction, context)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
@ -25,7 +25,6 @@ define([
|
|||||||
"./src/services/PopupService",
|
"./src/services/PopupService",
|
||||||
"./src/SplashScreenManager",
|
"./src/SplashScreenManager",
|
||||||
"./src/StyleSheetLoader",
|
"./src/StyleSheetLoader",
|
||||||
"./src/UnsupportedBrowserWarning",
|
|
||||||
"./src/controllers/TimeRangeController",
|
"./src/controllers/TimeRangeController",
|
||||||
"./src/controllers/DateTimePickerController",
|
"./src/controllers/DateTimePickerController",
|
||||||
"./src/controllers/DateTimeFieldController",
|
"./src/controllers/DateTimeFieldController",
|
||||||
@ -74,7 +73,6 @@ define([
|
|||||||
PopupService,
|
PopupService,
|
||||||
SplashScreenManager,
|
SplashScreenManager,
|
||||||
StyleSheetLoader,
|
StyleSheetLoader,
|
||||||
UnsupportedBrowserWarning,
|
|
||||||
TimeRangeController,
|
TimeRangeController,
|
||||||
DateTimePickerController,
|
DateTimePickerController,
|
||||||
DateTimeFieldController,
|
DateTimeFieldController,
|
||||||
@ -151,13 +149,6 @@ define([
|
|||||||
"THEME"
|
"THEME"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"implementation": UnsupportedBrowserWarning,
|
|
||||||
"depends": [
|
|
||||||
"notificationService",
|
|
||||||
"agentService"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"implementation": SplashScreenManager,
|
"implementation": SplashScreenManager,
|
||||||
"depends": [
|
"depends": [
|
||||||
@ -266,8 +257,8 @@ define([
|
|||||||
"key": "ClickAwayController",
|
"key": "ClickAwayController",
|
||||||
"implementation": ClickAwayController,
|
"implementation": ClickAwayController,
|
||||||
"depends": [
|
"depends": [
|
||||||
"$scope",
|
"$document",
|
||||||
"$document"
|
"$timeout"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -84,7 +84,11 @@ p {
|
|||||||
margin-bottom: $interiorMarginLg;
|
margin-bottom: $interiorMarginLg;
|
||||||
}
|
}
|
||||||
|
|
||||||
ol, ul { padding-left: 0; }
|
ol, ul {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
mct-container {
|
mct-container {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -76,6 +76,11 @@ $pad: $interiorMargin * $baseRatio;
|
|||||||
font-family: symbolsfont;
|
font-family: symbolsfont;
|
||||||
margin-right: $interiorMarginSm;
|
margin-right: $interiorMarginSm;
|
||||||
}
|
}
|
||||||
|
&.t-save-as:before {
|
||||||
|
content:'\e612';
|
||||||
|
font-family: symbolsfont;
|
||||||
|
margin-right: $interiorMarginSm;
|
||||||
|
}
|
||||||
&.t-cancel {
|
&.t-cancel {
|
||||||
.title-label { display: none; }
|
.title-label { display: none; }
|
||||||
&:before {
|
&:before {
|
||||||
|
@ -1,54 +1,42 @@
|
|||||||
@mixin toiLineHovEffects() {
|
@mixin toiLineHovEffects() {
|
||||||
//@include pulse(.25s);
|
|
||||||
&:before,
|
&:before,
|
||||||
&:after {
|
&:after {
|
||||||
background-color: $timeControllerToiLineColorHov;
|
background-color: $timeControllerToiLineColorHov;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mct-include.l-time-controller {
|
.l-time-controller {
|
||||||
$minW: 500px;
|
$minW: 500px;
|
||||||
$knobHOffset: 0px;
|
$knobHOffset: 0px;
|
||||||
$knobM: ($sliderKnobW + $knobHOffset) * -1;
|
$knobM: ($sliderKnobW + $knobHOffset) * -1;
|
||||||
$rangeValPad: $interiorMargin;
|
$rangeValPad: $interiorMargin;
|
||||||
$rangeValOffset: $sliderKnobW;
|
$rangeValOffset: $sliderKnobW;
|
||||||
//$knobCr: $sliderKnobW;
|
|
||||||
$timeRangeSliderLROffset: 130px + $sliderKnobW + $rangeValOffset;
|
$timeRangeSliderLROffset: 130px + $sliderKnobW + $rangeValOffset;
|
||||||
$r1H: nth($ueTimeControlH,1);
|
$r1H: nth($ueTimeControlH,1);
|
||||||
$r2H: nth($ueTimeControlH,2);
|
$r2H: nth($ueTimeControlH,2);
|
||||||
$r3H: nth($ueTimeControlH,3);
|
$r3H: nth($ueTimeControlH,3);
|
||||||
|
|
||||||
//@include absPosDefault();
|
|
||||||
//@include test();
|
|
||||||
display: block;
|
display: block;
|
||||||
//top: auto;
|
|
||||||
height: $r1H + $r2H + $r3H + ($interiorMargin * 2);
|
height: $r1H + $r2H + $r3H + ($interiorMargin * 2);
|
||||||
min-width: $minW;
|
min-width: $minW;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
|
|
||||||
.l-time-range-inputs-holder,
|
|
||||||
.l-time-range-slider {
|
|
||||||
//font-size: 0.8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.l-time-range-inputs-holder,
|
.l-time-range-inputs-holder,
|
||||||
.l-time-range-slider-holder,
|
.l-time-range-slider-holder,
|
||||||
.l-time-range-ticks-holder
|
.l-time-range-ticks-holder
|
||||||
{
|
{
|
||||||
//@include test();
|
|
||||||
@include absPosDefault(0, visible);
|
@include absPosDefault(0, visible);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
top: auto;
|
top: auto;
|
||||||
}
|
}
|
||||||
.l-time-range-slider,
|
.l-time-range-slider,
|
||||||
.l-time-range-ticks {
|
.l-time-range-ticks {
|
||||||
//@include test(red, 0.1);
|
|
||||||
@include absPosDefault(0, visible);
|
@include absPosDefault(0, visible);
|
||||||
left: $timeRangeSliderLROffset; right: $timeRangeSliderLROffset;
|
left: $timeRangeSliderLROffset; right: $timeRangeSliderLROffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.l-time-range-inputs-holder {
|
.l-time-range-inputs-holder {
|
||||||
//@include test(red);
|
|
||||||
height: $r1H; bottom: $r2H + $r3H + ($interiorMarginSm * 2);
|
height: $r1H; bottom: $r2H + $r3H + ($interiorMarginSm * 2);
|
||||||
padding-top: $interiorMargin;
|
padding-top: $interiorMargin;
|
||||||
border-top: 1px solid $colorInteriorBorder;
|
border-top: 1px solid $colorInteriorBorder;
|
||||||
@ -70,7 +58,6 @@ mct-include.l-time-controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.l-time-range-slider-holder {
|
.l-time-range-slider-holder {
|
||||||
//@include test(green);
|
|
||||||
height: $r2H; bottom: $r3H + ($interiorMarginSm * 1);
|
height: $r2H; bottom: $r3H + ($interiorMarginSm * 1);
|
||||||
.range-holder {
|
.range-holder {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
@ -82,7 +69,6 @@ mct-include.l-time-controller {
|
|||||||
$myW: 8px;
|
$myW: 8px;
|
||||||
@include transform(translateX(50%));
|
@include transform(translateX(50%));
|
||||||
position: absolute;
|
position: absolute;
|
||||||
//@include test();
|
|
||||||
top: 0; right: 0; bottom: 0px; left: auto;
|
top: 0; right: 0; bottom: 0px; left: auto;
|
||||||
width: $myW;
|
width: $myW;
|
||||||
height: auto;
|
height: auto;
|
||||||
@ -97,7 +83,6 @@ mct-include.l-time-controller {
|
|||||||
// Vert line
|
// Vert line
|
||||||
top: 0; right: auto; bottom: -10px; left: floor($myW/2) - 1;
|
top: 0; right: auto; bottom: -10px; left: floor($myW/2) - 1;
|
||||||
width: 2px;
|
width: 2px;
|
||||||
//top: 0; right: 3px; bottom: 0; left: 3px;
|
|
||||||
}
|
}
|
||||||
&:after {
|
&:after {
|
||||||
// Circle element
|
// Circle element
|
||||||
@ -114,7 +99,6 @@ mct-include.l-time-controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:not(:active) {
|
&:not(:active) {
|
||||||
//@include test(#ff00cc);
|
|
||||||
.knob,
|
.knob,
|
||||||
.range {
|
.range {
|
||||||
@include transition-property(left, right);
|
@include transition-property(left, right);
|
||||||
@ -155,7 +139,6 @@ mct-include.l-time-controller {
|
|||||||
.knob {
|
.knob {
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
.range-value {
|
.range-value {
|
||||||
//@include test($sliderColorRange);
|
|
||||||
@include trans-prop-nice-fade(.25s);
|
@include trans-prop-nice-fade(.25s);
|
||||||
padding: 0 $rangeValOffset;
|
padding: 0 $rangeValOffset;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -167,7 +150,6 @@ mct-include.l-time-controller {
|
|||||||
color: $sliderColorKnobHov;
|
color: $sliderColorKnobHov;
|
||||||
}
|
}
|
||||||
&.knob-l {
|
&.knob-l {
|
||||||
//border-bottom-left-radius: $knobCr; // MOVED TO _CONTROLS.SCSS
|
|
||||||
margin-left: $knobM;
|
margin-left: $knobM;
|
||||||
.range-value {
|
.range-value {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
@ -175,7 +157,6 @@ mct-include.l-time-controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.knob-r {
|
&.knob-r {
|
||||||
//border-bottom-right-radius: $knobCr;
|
|
||||||
margin-right: $knobM;
|
margin-right: $knobM;
|
||||||
.range-value {
|
.range-value {
|
||||||
left: $rangeValOffset;
|
left: $rangeValOffset;
|
||||||
@ -185,15 +166,189 @@ mct-include.l-time-controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.l-time-domain-selector {
|
||||||
|
position: absolute;
|
||||||
|
right: 0px;
|
||||||
|
bottom: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//.slot.range-holder {
|
|
||||||
// background-color: $sliderColorRangeHolder;
|
|
||||||
//}
|
|
||||||
|
|
||||||
.s-time-range-val {
|
.s-time-range-val {
|
||||||
//@include test();
|
|
||||||
border-radius: $controlCr;
|
border-radius: $controlCr;
|
||||||
background-color: $colorInputBg;
|
background-color: $colorInputBg;
|
||||||
padding: 1px 1px 0 $interiorMargin;
|
padding: 1px 1px 0 $interiorMargin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@include phoneandtablet {
|
||||||
|
.l-time-controller, .l-time-range-inputs-holder {
|
||||||
|
min-width: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.l-time-controller {
|
||||||
|
|
||||||
|
.l-time-domain-selector {
|
||||||
|
select {
|
||||||
|
height: 25px;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.l-time-range-slider-holder, .l-time-range-ticks-holder {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-range-start, .time-range-end, {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.l-time-range-inputs-holder {
|
||||||
|
.l-time-range-input {
|
||||||
|
display: block;
|
||||||
|
.s-btn {
|
||||||
|
padding-right: 18px;
|
||||||
|
white-space: nowrap;
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.l-time-range-inputs-elem {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include phone {
|
||||||
|
.l-time-controller {
|
||||||
|
height: 48px;
|
||||||
|
|
||||||
|
.l-time-range-inputs-holder {
|
||||||
|
bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.l-time-domain-selector {
|
||||||
|
width: 33%;
|
||||||
|
bottom: -9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.l-time-range-inputs-holder {
|
||||||
|
.l-time-range-input {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
.s-btn {
|
||||||
|
width: 66%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.l-time-range-inputs-elem {
|
||||||
|
&.ui-symbol {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.lbl {
|
||||||
|
width: 33%;
|
||||||
|
right: 0px;
|
||||||
|
top: 5px;
|
||||||
|
display: block;
|
||||||
|
height: 25px;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 25px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@include tablet {
|
||||||
|
.l-time-controller {
|
||||||
|
height: 17px;
|
||||||
|
|
||||||
|
.l-time-range-inputs-holder {
|
||||||
|
bottom: -7px;
|
||||||
|
left: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.l-time-domain-selector {
|
||||||
|
width: 23%;
|
||||||
|
right: -4px;
|
||||||
|
bottom: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.l-time-range-inputs-holder {
|
||||||
|
.l-time-range-input {
|
||||||
|
float: left;
|
||||||
|
.s-btn {
|
||||||
|
width: 100%;
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include tabletLandscape {
|
||||||
|
.l-time-controller {
|
||||||
|
height: 17px;
|
||||||
|
|
||||||
|
.l-time-range-inputs-holder {
|
||||||
|
bottom: -7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.l-time-domain-selector {
|
||||||
|
width: 23%;
|
||||||
|
right: auto;
|
||||||
|
bottom: -10px;
|
||||||
|
left: 391px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.l-time-range-inputs-holder {
|
||||||
|
.l-time-range-inputs-elem {
|
||||||
|
&.ui-symbol, &.lbl {
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
line-height: 25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pane-tree-hidden .l-time-controller {
|
||||||
|
.l-time-domain-selector {
|
||||||
|
left: 667px;
|
||||||
|
}
|
||||||
|
.l-time-range-inputs-holder {
|
||||||
|
padding-left: 277px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@include tabletPortrait {
|
||||||
|
.l-time-controller {
|
||||||
|
height: 17px;
|
||||||
|
|
||||||
|
.l-time-range-inputs-holder {
|
||||||
|
bottom: -7px;
|
||||||
|
left: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.l-time-domain-selector {
|
||||||
|
width: 23%;
|
||||||
|
right: -4px;
|
||||||
|
bottom: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.l-time-range-inputs-holder {
|
||||||
|
.l-time-range-input {
|
||||||
|
width: 38%;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.l-time-range-inputs-elem {
|
||||||
|
&.ui-symbol, &.lbl {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -19,15 +19,18 @@
|
|||||||
this source code distribution or the Licensing information page available
|
this source code distribution or the Licensing information page available
|
||||||
at runtime from the About dialog for additional information.
|
at runtime from the About dialog for additional information.
|
||||||
-->
|
-->
|
||||||
<div ng-controller="TimeRangeController">
|
<div ng-controller="TimeRangeController as trCtrl">
|
||||||
<form class="l-time-range-inputs-holder"
|
<form class="l-time-range-inputs-holder"
|
||||||
ng-submit="updateBoundsFromForm()">
|
ng-submit="trCtrl.updateBoundsFromForm()">
|
||||||
<span class="l-time-range-inputs-elem ui-symbol type-icon">C</span>
|
<span class="l-time-range-inputs-elem ui-symbol type-icon">C</span>
|
||||||
<span class="l-time-range-input">
|
<span class="l-time-range-input">
|
||||||
<mct-control key="'datetime-field'"
|
<mct-control key="'datetime-field'"
|
||||||
structure="{ format: parameters.format, validate: validateStart }"
|
structure="{
|
||||||
|
format: parameters.format,
|
||||||
|
validate: trCtrl.validateStart
|
||||||
|
}"
|
||||||
ng-model="formModel"
|
ng-model="formModel"
|
||||||
ng-blur="updateBoundsFromForm()"
|
ng-blur="trCtrl.updateBoundsFromForm()"
|
||||||
field="'start'"
|
field="'start'"
|
||||||
class="time-range-start">
|
class="time-range-start">
|
||||||
</mct-control>
|
</mct-control>
|
||||||
@ -37,9 +40,12 @@
|
|||||||
|
|
||||||
<span class="l-time-range-input" ng-controller="ToggleController as t2">
|
<span class="l-time-range-input" ng-controller="ToggleController as t2">
|
||||||
<mct-control key="'datetime-field'"
|
<mct-control key="'datetime-field'"
|
||||||
structure="{ format: parameters.format, validate: validateEnd }"
|
structure="{
|
||||||
|
format: parameters.format,
|
||||||
|
validate: trCtrl.validateEnd
|
||||||
|
}"
|
||||||
ng-model="formModel"
|
ng-model="formModel"
|
||||||
ng-blur="updateBoundsFromForm()"
|
ng-blur="trCtrl.updateBoundsFromForm()"
|
||||||
field="'end'"
|
field="'end'"
|
||||||
class="time-range-end">
|
class="time-range-end">
|
||||||
</mct-control>
|
</mct-control>
|
||||||
@ -53,22 +59,25 @@
|
|||||||
<div class="slider"
|
<div class="slider"
|
||||||
mct-resize="spanWidth = bounds.width">
|
mct-resize="spanWidth = bounds.width">
|
||||||
<div class="knob knob-l"
|
<div class="knob knob-l"
|
||||||
mct-drag-down="startLeftDrag()"
|
mct-drag-down="trCtrl.startLeftDrag()"
|
||||||
mct-drag="leftDrag(delta[0])"
|
mct-drag="trCtrl.leftDrag(delta[0])"
|
||||||
ng-style="{ left: startInnerPct }">
|
ng-style="{ left: startInnerPct }">
|
||||||
<div class="range-value">{{startInnerText}}</div>
|
<div class="range-value">{{startInnerText}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="knob knob-r"
|
<div class="knob knob-r"
|
||||||
mct-drag-down="startRightDrag()"
|
mct-drag-down="trCtrl.startRightDrag()"
|
||||||
mct-drag="rightDrag(delta[0])"
|
mct-drag="trCtrl.rightDrag(delta[0])"
|
||||||
ng-style="{ right: endInnerPct }">
|
ng-style="{ right: endInnerPct }">
|
||||||
<div class="range-value">{{endInnerText}}</div>
|
<div class="range-value">{{endInnerText}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="slot range-holder">
|
<div class="slot range-holder">
|
||||||
<div class="range"
|
<div class="range"
|
||||||
mct-drag-down="startMiddleDrag()"
|
mct-drag-down="trCtrl.startMiddleDrag()"
|
||||||
mct-drag="middleDrag(delta[0])"
|
mct-drag="trCtrl.middleDrag(delta[0])"
|
||||||
ng-style="{ left: startInnerPct, right: endInnerPct}">
|
ng-style="{
|
||||||
|
left: startInnerPct,
|
||||||
|
right: endInnerPct
|
||||||
|
}">
|
||||||
<div class="toi-line"></div>
|
<div class="toi-line"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This bundle provides various general-purpose UI elements, including
|
|
||||||
* platform styling.
|
|
||||||
* @namespace platform/commonUI/general
|
|
||||||
*/
|
|
||||||
define(
|
|
||||||
[],
|
|
||||||
function () {
|
|
||||||
|
|
||||||
var WARNING_TITLE = "Unsupported browser",
|
|
||||||
WARNING_DESCRIPTION = [
|
|
||||||
"This software has been developed and tested",
|
|
||||||
"using the latest Google Chrome,",
|
|
||||||
"and may be unstable in other browsers."
|
|
||||||
].join(" "),
|
|
||||||
MOBILE_BROWSER = "Safari",
|
|
||||||
DESKTOP_BROWSER = "Chrome";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows a warning if a user's browser is unsupported.
|
|
||||||
* @memberof platform/commonUI/general
|
|
||||||
* @constructor
|
|
||||||
* @param {NotificationService} notificationService the notification
|
|
||||||
* service
|
|
||||||
*/
|
|
||||||
function UnsupportedBrowserWarning(notificationService, agentService) {
|
|
||||||
var testToBrowser = agentService.isMobile() ?
|
|
||||||
MOBILE_BROWSER : DESKTOP_BROWSER;
|
|
||||||
|
|
||||||
if (!agentService.isBrowser(testToBrowser)) {
|
|
||||||
notificationService.alert({
|
|
||||||
title: WARNING_TITLE,
|
|
||||||
actionText: WARNING_DESCRIPTION
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return UnsupportedBrowserWarning;
|
|
||||||
}
|
|
||||||
);
|
|
@ -34,20 +34,19 @@ define(
|
|||||||
* @param $scope the scope in which this controller is active
|
* @param $scope the scope in which this controller is active
|
||||||
* @param $document the document element, injected by Angular
|
* @param $document the document element, injected by Angular
|
||||||
*/
|
*/
|
||||||
function ClickAwayController($scope, $document) {
|
function ClickAwayController($document, $timeout) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this.state = false;
|
this.state = false;
|
||||||
this.$scope = $scope;
|
|
||||||
this.$document = $document;
|
this.$document = $document;
|
||||||
|
|
||||||
// Callback used by the document listener. Deactivates;
|
// Callback used by the document listener. Timeout ensures that
|
||||||
// note also $scope.$apply is invoked to indicate that
|
// `clickaway` action occurs after `toggle` if `toggle` is
|
||||||
// the state of this controller has changed.
|
// triggered by a click/mouseup.
|
||||||
this.clickaway = function () {
|
this.clickaway = function () {
|
||||||
self.deactivate();
|
$timeout(function () {
|
||||||
$scope.$apply();
|
self.deactivate();
|
||||||
return false;
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,244 +20,286 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define(
|
define([
|
||||||
[],
|
|
||||||
function () {
|
|
||||||
|
|
||||||
var TICK_SPACING_PX = 150;
|
], function () {
|
||||||
|
|
||||||
|
var TICK_SPACING_PX = 150;
|
||||||
|
|
||||||
/**
|
/* format number as percent; 0.0-1.0 to "0%"-"100%" */
|
||||||
* Controller used by the `time-controller` template.
|
function toPercent(p) {
|
||||||
* @memberof platform/commonUI/general
|
return (100 * p) + "%";
|
||||||
* @constructor
|
|
||||||
* @param $scope the Angular scope for this controller
|
|
||||||
* @param {FormatService} formatService the service to user to format
|
|
||||||
* domain values
|
|
||||||
* @param {string} defaultFormat the format to request when no
|
|
||||||
* format has been otherwise specified
|
|
||||||
* @param {Function} now a function to return current system time
|
|
||||||
*/
|
|
||||||
function TimeRangeController($scope, formatService, defaultFormat, now) {
|
|
||||||
var tickCount = 2,
|
|
||||||
innerMinimumSpan = 1000, // 1 second
|
|
||||||
outerMinimumSpan = 1000, // 1 second
|
|
||||||
initialDragValue,
|
|
||||||
formatter = formatService.getFormat(defaultFormat);
|
|
||||||
|
|
||||||
function formatTimestamp(ts) {
|
|
||||||
return formatter.format(ts);
|
|
||||||
}
|
|
||||||
|
|
||||||
// From 0.0-1.0 to "0%"-"100%"
|
|
||||||
function toPercent(p) {
|
|
||||||
return (100 * p) + "%";
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateTicks() {
|
|
||||||
var i, p, ts, start, end, span;
|
|
||||||
end = $scope.ngModel.outer.end;
|
|
||||||
start = $scope.ngModel.outer.start;
|
|
||||||
span = end - start;
|
|
||||||
$scope.ticks = [];
|
|
||||||
for (i = 0; i < tickCount; i += 1) {
|
|
||||||
p = i / (tickCount - 1);
|
|
||||||
ts = p * span + start;
|
|
||||||
$scope.ticks.push(formatTimestamp(ts));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateSpanWidth(w) {
|
|
||||||
tickCount = Math.max(Math.floor(w / TICK_SPACING_PX), 2);
|
|
||||||
updateTicks();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateViewForInnerSpanFromModel(ngModel) {
|
|
||||||
var span = ngModel.outer.end - ngModel.outer.start;
|
|
||||||
|
|
||||||
// Expose readable dates for the knobs
|
|
||||||
$scope.startInnerText = formatTimestamp(ngModel.inner.start);
|
|
||||||
$scope.endInnerText = formatTimestamp(ngModel.inner.end);
|
|
||||||
|
|
||||||
// And positions for the knobs
|
|
||||||
$scope.startInnerPct =
|
|
||||||
toPercent((ngModel.inner.start - ngModel.outer.start) / span);
|
|
||||||
$scope.endInnerPct =
|
|
||||||
toPercent((ngModel.outer.end - ngModel.inner.end) / span);
|
|
||||||
}
|
|
||||||
|
|
||||||
function defaultBounds() {
|
|
||||||
var t = now();
|
|
||||||
return {
|
|
||||||
start: t - 24 * 3600 * 1000, // One day
|
|
||||||
end: t
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyBounds(bounds) {
|
|
||||||
return { start: bounds.start, end: bounds.end };
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateViewFromModel(ngModel) {
|
|
||||||
ngModel = ngModel || {};
|
|
||||||
ngModel.outer = ngModel.outer || defaultBounds();
|
|
||||||
ngModel.inner = ngModel.inner || copyBounds(ngModel.outer);
|
|
||||||
|
|
||||||
// Stick it back is scope (in case we just set defaults)
|
|
||||||
$scope.ngModel = ngModel;
|
|
||||||
|
|
||||||
updateViewForInnerSpanFromModel(ngModel);
|
|
||||||
updateTicks();
|
|
||||||
}
|
|
||||||
|
|
||||||
function startLeftDrag() {
|
|
||||||
initialDragValue = $scope.ngModel.inner.start;
|
|
||||||
}
|
|
||||||
|
|
||||||
function startRightDrag() {
|
|
||||||
initialDragValue = $scope.ngModel.inner.end;
|
|
||||||
}
|
|
||||||
|
|
||||||
function startMiddleDrag() {
|
|
||||||
initialDragValue = {
|
|
||||||
start: $scope.ngModel.inner.start,
|
|
||||||
end: $scope.ngModel.inner.end
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function toMillis(pixels) {
|
|
||||||
var span =
|
|
||||||
$scope.ngModel.outer.end - $scope.ngModel.outer.start;
|
|
||||||
return (pixels / $scope.spanWidth) * span;
|
|
||||||
}
|
|
||||||
|
|
||||||
function clamp(value, low, high) {
|
|
||||||
return Math.max(low, Math.min(high, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
function leftDrag(pixels) {
|
|
||||||
var delta = toMillis(pixels);
|
|
||||||
$scope.ngModel.inner.start = clamp(
|
|
||||||
initialDragValue + delta,
|
|
||||||
$scope.ngModel.outer.start,
|
|
||||||
$scope.ngModel.inner.end - innerMinimumSpan
|
|
||||||
);
|
|
||||||
updateViewFromModel($scope.ngModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
function rightDrag(pixels) {
|
|
||||||
var delta = toMillis(pixels);
|
|
||||||
$scope.ngModel.inner.end = clamp(
|
|
||||||
initialDragValue + delta,
|
|
||||||
$scope.ngModel.inner.start + innerMinimumSpan,
|
|
||||||
$scope.ngModel.outer.end
|
|
||||||
);
|
|
||||||
updateViewFromModel($scope.ngModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
function middleDrag(pixels) {
|
|
||||||
var delta = toMillis(pixels),
|
|
||||||
edge = delta < 0 ? 'start' : 'end',
|
|
||||||
opposite = delta < 0 ? 'end' : 'start';
|
|
||||||
|
|
||||||
// Adjust the position of the edge in the direction of drag
|
|
||||||
$scope.ngModel.inner[edge] = clamp(
|
|
||||||
initialDragValue[edge] + delta,
|
|
||||||
$scope.ngModel.outer.start,
|
|
||||||
$scope.ngModel.outer.end
|
|
||||||
);
|
|
||||||
// Adjust opposite knob to maintain span
|
|
||||||
$scope.ngModel.inner[opposite] = $scope.ngModel.inner[edge] +
|
|
||||||
initialDragValue[opposite] - initialDragValue[edge];
|
|
||||||
|
|
||||||
updateViewFromModel($scope.ngModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateFormModel() {
|
|
||||||
$scope.formModel = {
|
|
||||||
start: (($scope.ngModel || {}).outer || {}).start,
|
|
||||||
end: (($scope.ngModel || {}).outer || {}).end
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateOuterStart() {
|
|
||||||
var ngModel = $scope.ngModel;
|
|
||||||
|
|
||||||
ngModel.inner.start =
|
|
||||||
Math.max(ngModel.outer.start, ngModel.inner.start);
|
|
||||||
ngModel.inner.end = Math.max(
|
|
||||||
ngModel.inner.start + innerMinimumSpan,
|
|
||||||
ngModel.inner.end
|
|
||||||
);
|
|
||||||
|
|
||||||
updateFormModel();
|
|
||||||
updateViewForInnerSpanFromModel(ngModel);
|
|
||||||
updateTicks();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateOuterEnd() {
|
|
||||||
var ngModel = $scope.ngModel;
|
|
||||||
|
|
||||||
ngModel.inner.end =
|
|
||||||
Math.min(ngModel.outer.end, ngModel.inner.end);
|
|
||||||
ngModel.inner.start = Math.min(
|
|
||||||
ngModel.inner.end - innerMinimumSpan,
|
|
||||||
ngModel.inner.start
|
|
||||||
);
|
|
||||||
|
|
||||||
updateFormModel();
|
|
||||||
updateViewForInnerSpanFromModel(ngModel);
|
|
||||||
updateTicks();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateFormat(key) {
|
|
||||||
formatter = formatService.getFormat(key || defaultFormat);
|
|
||||||
updateViewForInnerSpanFromModel($scope.ngModel);
|
|
||||||
updateTicks();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateBoundsFromForm() {
|
|
||||||
var start = $scope.formModel.start,
|
|
||||||
end = $scope.formModel.end;
|
|
||||||
if (end >= start + outerMinimumSpan) {
|
|
||||||
$scope.ngModel = $scope.ngModel || {};
|
|
||||||
$scope.ngModel.outer = { start: start, end: end };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateStart(startValue) {
|
|
||||||
return startValue <= $scope.formModel.end - outerMinimumSpan;
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateEnd(endValue) {
|
|
||||||
return endValue >= $scope.formModel.start + outerMinimumSpan;
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.startLeftDrag = startLeftDrag;
|
|
||||||
$scope.startRightDrag = startRightDrag;
|
|
||||||
$scope.startMiddleDrag = startMiddleDrag;
|
|
||||||
$scope.leftDrag = leftDrag;
|
|
||||||
$scope.rightDrag = rightDrag;
|
|
||||||
$scope.middleDrag = middleDrag;
|
|
||||||
|
|
||||||
$scope.updateBoundsFromForm = updateBoundsFromForm;
|
|
||||||
|
|
||||||
$scope.validateStart = validateStart;
|
|
||||||
$scope.validateEnd = validateEnd;
|
|
||||||
|
|
||||||
$scope.ticks = [];
|
|
||||||
|
|
||||||
// Initialize scope to defaults
|
|
||||||
updateViewFromModel($scope.ngModel);
|
|
||||||
updateFormModel();
|
|
||||||
|
|
||||||
$scope.$watchCollection("ngModel", updateViewFromModel);
|
|
||||||
$scope.$watch("spanWidth", updateSpanWidth);
|
|
||||||
$scope.$watch("ngModel.outer.start", updateOuterStart);
|
|
||||||
$scope.$watch("ngModel.outer.end", updateOuterEnd);
|
|
||||||
$scope.$watch("parameters.format", updateFormat);
|
|
||||||
}
|
|
||||||
|
|
||||||
return TimeRangeController;
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
function clamp(value, low, high) {
|
||||||
|
return Math.max(low, Math.min(high, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyBounds(bounds) {
|
||||||
|
return {
|
||||||
|
start: bounds.start,
|
||||||
|
end: bounds.end
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller used by the `time-controller` template.
|
||||||
|
* @memberof platform/commonUI/general
|
||||||
|
* @constructor
|
||||||
|
* @param $scope the Angular scope for this controller
|
||||||
|
* @param {FormatService} formatService the service to user to format
|
||||||
|
* domain values
|
||||||
|
* @param {string} defaultFormat the format to request when no
|
||||||
|
* format has been otherwise specified
|
||||||
|
* @param {Function} now a function to return current system time
|
||||||
|
*/
|
||||||
|
function TimeRangeController($scope, formatService, defaultFormat, now) {
|
||||||
|
this.$scope = $scope;
|
||||||
|
this.formatService = formatService;
|
||||||
|
this.defaultFormat = defaultFormat;
|
||||||
|
this.now = now;
|
||||||
|
|
||||||
|
this.tickCount = 2;
|
||||||
|
this.innerMinimumSpan = 1000; // 1 second
|
||||||
|
this.outerMinimumSpan = 1000; // 1 second
|
||||||
|
this.initialDragValue = undefined;
|
||||||
|
this.formatter = formatService.getFormat(defaultFormat);
|
||||||
|
this.formStartChanged = false;
|
||||||
|
this.formEndChanged = false;
|
||||||
|
|
||||||
|
this.$scope.ticks = [];
|
||||||
|
|
||||||
|
this.updateViewFromModel(this.$scope.ngModel);
|
||||||
|
this.updateFormModel();
|
||||||
|
|
||||||
|
[
|
||||||
|
'updateViewFromModel',
|
||||||
|
'updateSpanWidth',
|
||||||
|
'updateOuterStart',
|
||||||
|
'updateOuterEnd',
|
||||||
|
'updateFormat',
|
||||||
|
'validateStart',
|
||||||
|
'validateEnd',
|
||||||
|
'onFormStartChange',
|
||||||
|
'onFormEndChange'
|
||||||
|
].forEach(function (boundFn) {
|
||||||
|
this[boundFn] = this[boundFn].bind(this);
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
this.$scope.$watchCollection("ngModel", this.updateViewFromModel);
|
||||||
|
this.$scope.$watch("spanWidth", this.updateSpanWidth);
|
||||||
|
this.$scope.$watch("ngModel.outer.start", this.updateOuterStart);
|
||||||
|
this.$scope.$watch("ngModel.outer.end", this.updateOuterEnd);
|
||||||
|
this.$scope.$watch("parameters.format", this.updateFormat);
|
||||||
|
this.$scope.$watch("formModel.start", this.onFormStartChange);
|
||||||
|
this.$scope.$watch("formModel.end", this.onFormEndChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeRangeController.prototype.formatTimestamp = function (ts) {
|
||||||
|
return this.formatter.format(ts);
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeRangeController.prototype.updateTicks = function () {
|
||||||
|
var i, p, ts, start, end, span;
|
||||||
|
end = this.$scope.ngModel.outer.end;
|
||||||
|
start = this.$scope.ngModel.outer.start;
|
||||||
|
span = end - start;
|
||||||
|
this.$scope.ticks = [];
|
||||||
|
for (i = 0; i < this.tickCount; i += 1) {
|
||||||
|
p = i / (this.tickCount - 1);
|
||||||
|
ts = p * span + start;
|
||||||
|
this.$scope.ticks.push(this.formatTimestamp(ts));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeRangeController.prototype.updateSpanWidth = function (w) {
|
||||||
|
this.tickCount = Math.max(Math.floor(w / TICK_SPACING_PX), 2);
|
||||||
|
this.updateTicks();
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeRangeController.prototype.updateViewForInnerSpanFromModel = function (
|
||||||
|
ngModel
|
||||||
|
) {
|
||||||
|
var span = ngModel.outer.end - ngModel.outer.start;
|
||||||
|
|
||||||
|
// Expose readable dates for the knobs
|
||||||
|
this.$scope.startInnerText = this.formatTimestamp(ngModel.inner.start);
|
||||||
|
this.$scope.endInnerText = this.formatTimestamp(ngModel.inner.end);
|
||||||
|
|
||||||
|
// And positions for the knobs
|
||||||
|
this.$scope.startInnerPct =
|
||||||
|
toPercent((ngModel.inner.start - ngModel.outer.start) / span);
|
||||||
|
this.$scope.endInnerPct =
|
||||||
|
toPercent((ngModel.outer.end - ngModel.inner.end) / span);
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeRangeController.prototype.defaultBounds = function () {
|
||||||
|
var t = this.now();
|
||||||
|
return {
|
||||||
|
start: t - 24 * 3600 * 1000, // One day
|
||||||
|
end: t
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
TimeRangeController.prototype.updateViewFromModel = function (ngModel) {
|
||||||
|
ngModel = ngModel || {};
|
||||||
|
ngModel.outer = ngModel.outer || this.defaultBounds();
|
||||||
|
ngModel.inner = ngModel.inner || copyBounds(ngModel.outer);
|
||||||
|
|
||||||
|
// Stick it back is scope (in case we just set defaults)
|
||||||
|
this.$scope.ngModel = ngModel;
|
||||||
|
|
||||||
|
this.updateViewForInnerSpanFromModel(ngModel);
|
||||||
|
this.updateTicks();
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeRangeController.prototype.startLeftDrag = function () {
|
||||||
|
this.initialDragValue = this.$scope.ngModel.inner.start;
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeRangeController.prototype.startRightDrag = function () {
|
||||||
|
this.initialDragValue = this.$scope.ngModel.inner.end;
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeRangeController.prototype.startMiddleDrag = function () {
|
||||||
|
this.initialDragValue = {
|
||||||
|
start: this.$scope.ngModel.inner.start,
|
||||||
|
end: this.$scope.ngModel.inner.end
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeRangeController.prototype.toMillis = function (pixels) {
|
||||||
|
var span =
|
||||||
|
this.$scope.ngModel.outer.end - this.$scope.ngModel.outer.start;
|
||||||
|
return (pixels / this.$scope.spanWidth) * span;
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeRangeController.prototype.leftDrag = function (pixels) {
|
||||||
|
var delta = this.toMillis(pixels);
|
||||||
|
this.$scope.ngModel.inner.start = clamp(
|
||||||
|
this.initialDragValue + delta,
|
||||||
|
this.$scope.ngModel.outer.start,
|
||||||
|
this.$scope.ngModel.inner.end - this.innerMinimumSpan
|
||||||
|
);
|
||||||
|
this.updateViewFromModel(this.$scope.ngModel);
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeRangeController.prototype.rightDrag = function (pixels) {
|
||||||
|
var delta = this.toMillis(pixels);
|
||||||
|
this.$scope.ngModel.inner.end = clamp(
|
||||||
|
this.initialDragValue + delta,
|
||||||
|
this.$scope.ngModel.inner.start + this.innerMinimumSpan,
|
||||||
|
this.$scope.ngModel.outer.end
|
||||||
|
);
|
||||||
|
this.updateViewFromModel(this.$scope.ngModel);
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeRangeController.prototype.middleDrag = function (pixels) {
|
||||||
|
var delta = this.toMillis(pixels),
|
||||||
|
edge = delta < 0 ? 'start' : 'end',
|
||||||
|
opposite = delta < 0 ? 'end' : 'start';
|
||||||
|
|
||||||
|
// Adjust the position of the edge in the direction of drag
|
||||||
|
this.$scope.ngModel.inner[edge] = clamp(
|
||||||
|
this.initialDragValue[edge] + delta,
|
||||||
|
this.$scope.ngModel.outer.start,
|
||||||
|
this.$scope.ngModel.outer.end
|
||||||
|
);
|
||||||
|
// Adjust opposite knob to maintain span
|
||||||
|
this.$scope.ngModel.inner[opposite] =
|
||||||
|
this.$scope.ngModel.inner[edge] +
|
||||||
|
this.initialDragValue[opposite] -
|
||||||
|
this.initialDragValue[edge];
|
||||||
|
|
||||||
|
this.updateViewFromModel(this.$scope.ngModel);
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeRangeController.prototype.updateFormModel = function () {
|
||||||
|
this.$scope.formModel = {
|
||||||
|
start: ((this.$scope.ngModel || {}).outer || {}).start,
|
||||||
|
end: ((this.$scope.ngModel || {}).outer || {}).end
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeRangeController.prototype.updateOuterStart = function () {
|
||||||
|
var ngModel = this.$scope.ngModel;
|
||||||
|
|
||||||
|
ngModel.inner.start =
|
||||||
|
Math.max(ngModel.outer.start, ngModel.inner.start);
|
||||||
|
ngModel.inner.end = Math.max(
|
||||||
|
ngModel.inner.start + this.innerMinimumSpan,
|
||||||
|
ngModel.inner.end
|
||||||
|
);
|
||||||
|
|
||||||
|
this.updateFormModel();
|
||||||
|
this.updateViewForInnerSpanFromModel(ngModel);
|
||||||
|
this.updateTicks();
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeRangeController.prototype.updateOuterEnd = function () {
|
||||||
|
var ngModel = this.$scope.ngModel;
|
||||||
|
|
||||||
|
ngModel.inner.end =
|
||||||
|
Math.min(ngModel.outer.end, ngModel.inner.end);
|
||||||
|
ngModel.inner.start = Math.min(
|
||||||
|
ngModel.inner.end - this.innerMinimumSpan,
|
||||||
|
ngModel.inner.start
|
||||||
|
);
|
||||||
|
|
||||||
|
this.updateFormModel();
|
||||||
|
this.updateViewForInnerSpanFromModel(ngModel);
|
||||||
|
this.updateTicks();
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeRangeController.prototype.updateFormat = function (key) {
|
||||||
|
this.formatter = this.formatService.getFormat(key || this.defaultFormat);
|
||||||
|
this.updateViewForInnerSpanFromModel(this.$scope.ngModel);
|
||||||
|
this.updateTicks();
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeRangeController.prototype.updateBoundsFromForm = function () {
|
||||||
|
if (this.formStartChanged) {
|
||||||
|
this.$scope.ngModel.outer.start =
|
||||||
|
this.$scope.ngModel.inner.start =
|
||||||
|
this.$scope.formModel.start;
|
||||||
|
this.formStartChanged = false;
|
||||||
|
}
|
||||||
|
if (this.formEndChanged) {
|
||||||
|
this.$scope.ngModel.outer.end =
|
||||||
|
this.$scope.ngModel.inner.end =
|
||||||
|
this.$scope.formModel.end;
|
||||||
|
this.formEndChanged = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeRangeController.prototype.onFormStartChange = function (
|
||||||
|
newValue,
|
||||||
|
oldValue
|
||||||
|
) {
|
||||||
|
if (!this.formStartChanged && newValue !== oldValue) {
|
||||||
|
this.formStartChanged = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeRangeController.prototype.onFormEndChange = function (
|
||||||
|
newValue,
|
||||||
|
oldValue
|
||||||
|
) {
|
||||||
|
if (!this.formEndChanged && newValue !== oldValue) {
|
||||||
|
this.formEndChanged = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeRangeController.prototype.validateStart = function (startValue) {
|
||||||
|
return startValue <=
|
||||||
|
this.$scope.formModel.end - this.outerMinimumSpan;
|
||||||
|
};
|
||||||
|
|
||||||
|
TimeRangeController.prototype.validateEnd = function (endValue) {
|
||||||
|
return endValue >=
|
||||||
|
this.$scope.formModel.start + this.outerMinimumSpan;
|
||||||
|
};
|
||||||
|
|
||||||
|
return TimeRangeController;
|
||||||
|
});
|
||||||
|
@ -1,96 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
["../src/UnsupportedBrowserWarning"],
|
|
||||||
function (UnsupportedBrowserWarning) {
|
|
||||||
|
|
||||||
var MOBILE_BROWSER = "Safari",
|
|
||||||
DESKTOP_BROWSER = "Chrome",
|
|
||||||
UNSUPPORTED_BROWSERS = [
|
|
||||||
"Firefox",
|
|
||||||
"IE",
|
|
||||||
"Opera",
|
|
||||||
"Iceweasel"
|
|
||||||
];
|
|
||||||
|
|
||||||
describe("The unsupported browser warning", function () {
|
|
||||||
var mockNotificationService,
|
|
||||||
mockAgentService,
|
|
||||||
testAgent;
|
|
||||||
|
|
||||||
function instantiateWith(browser) {
|
|
||||||
testAgent = "Mozilla/5.0 " + browser + "/12.34.56";
|
|
||||||
return new UnsupportedBrowserWarning(
|
|
||||||
mockNotificationService,
|
|
||||||
mockAgentService
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
testAgent = "chrome";
|
|
||||||
mockNotificationService = jasmine.createSpyObj(
|
|
||||||
"notificationService",
|
|
||||||
[ "alert" ]
|
|
||||||
);
|
|
||||||
mockAgentService = jasmine.createSpyObj(
|
|
||||||
"agentService",
|
|
||||||
[ "isMobile", "isBrowser" ]
|
|
||||||
);
|
|
||||||
mockAgentService.isBrowser.andCallFake(function (substr) {
|
|
||||||
substr = substr.toLowerCase();
|
|
||||||
return testAgent.toLowerCase().indexOf(substr) !== -1;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
[ false, true ].forEach(function (isMobile) {
|
|
||||||
var deviceType = isMobile ? "mobile" : "desktop",
|
|
||||||
goodBrowser = isMobile ? MOBILE_BROWSER : DESKTOP_BROWSER,
|
|
||||||
badBrowsers = UNSUPPORTED_BROWSERS.concat([
|
|
||||||
isMobile ? DESKTOP_BROWSER : MOBILE_BROWSER
|
|
||||||
]);
|
|
||||||
|
|
||||||
describe("on " + deviceType + " devices", function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
mockAgentService.isMobile.andReturn(isMobile);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("is not shown for " + goodBrowser, function () {
|
|
||||||
instantiateWith(goodBrowser);
|
|
||||||
expect(mockNotificationService.alert)
|
|
||||||
.not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
badBrowsers.forEach(function (badBrowser) {
|
|
||||||
it("is shown for " + badBrowser, function () {
|
|
||||||
instantiateWith(badBrowser);
|
|
||||||
expect(mockNotificationService.alert)
|
|
||||||
.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
@ -25,20 +25,20 @@ define(
|
|||||||
function (ClickAwayController) {
|
function (ClickAwayController) {
|
||||||
|
|
||||||
describe("The click-away controller", function () {
|
describe("The click-away controller", function () {
|
||||||
var mockScope,
|
var mockDocument,
|
||||||
mockDocument,
|
mockTimeout,
|
||||||
controller;
|
controller;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
mockScope = jasmine.createSpyObj(
|
|
||||||
"$scope",
|
|
||||||
[ "$apply" ]
|
|
||||||
);
|
|
||||||
mockDocument = jasmine.createSpyObj(
|
mockDocument = jasmine.createSpyObj(
|
||||||
"$document",
|
"$document",
|
||||||
[ "on", "off" ]
|
[ "on", "off" ]
|
||||||
);
|
);
|
||||||
controller = new ClickAwayController(mockScope, mockDocument);
|
mockTimeout = jasmine.createSpy('timeout');
|
||||||
|
controller = new ClickAwayController(
|
||||||
|
mockDocument,
|
||||||
|
mockTimeout
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("is initially inactive", function () {
|
it("is initially inactive", function () {
|
||||||
@ -77,10 +77,12 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("deactivates and detaches listener on document click", function () {
|
it("deactivates and detaches listener on document click", function () {
|
||||||
var callback;
|
var callback, timeout;
|
||||||
controller.setState(true);
|
controller.setState(true);
|
||||||
callback = mockDocument.on.mostRecentCall.args[1];
|
callback = mockDocument.on.mostRecentCall.args[1];
|
||||||
callback();
|
callback();
|
||||||
|
timeout = mockTimeout.mostRecentCall.args[0];
|
||||||
|
timeout();
|
||||||
expect(controller.isActive()).toEqual(false);
|
expect(controller.isActive()).toEqual(false);
|
||||||
expect(mockDocument.off).toHaveBeenCalledWith("mouseup", callback);
|
expect(mockDocument.off).toHaveBeenCalledWith("mouseup", callback);
|
||||||
});
|
});
|
||||||
@ -89,4 +91,4 @@ define(
|
|||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -92,18 +92,18 @@ define(
|
|||||||
it("exposes start time validator", function () {
|
it("exposes start time validator", function () {
|
||||||
var testValue = 42000000;
|
var testValue = 42000000;
|
||||||
mockScope.formModel = { end: testValue };
|
mockScope.formModel = { end: testValue };
|
||||||
expect(mockScope.validateStart(testValue + 1))
|
expect(controller.validateStart(testValue + 1))
|
||||||
.toBe(false);
|
.toBe(false);
|
||||||
expect(mockScope.validateStart(testValue - 60 * 60 * 1000 - 1))
|
expect(controller.validateStart(testValue - 60 * 60 * 1000 - 1))
|
||||||
.toBe(true);
|
.toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("exposes end time validator", function () {
|
it("exposes end time validator", function () {
|
||||||
var testValue = 42000000;
|
var testValue = 42000000;
|
||||||
mockScope.formModel = { start: testValue };
|
mockScope.formModel = { start: testValue };
|
||||||
expect(mockScope.validateEnd(testValue - 1))
|
expect(controller.validateEnd(testValue - 1))
|
||||||
.toBe(false);
|
.toBe(false);
|
||||||
expect(mockScope.validateEnd(testValue + 60 * 60 * 1000 + 1))
|
expect(controller.validateEnd(testValue + 60 * 60 * 1000 + 1))
|
||||||
.toBe(true);
|
.toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -117,25 +117,87 @@ define(
|
|||||||
start: DAY * 10000,
|
start: DAY * 10000,
|
||||||
end: DAY * 11000
|
end: DAY * 11000
|
||||||
};
|
};
|
||||||
// These watches may not exist, but Angular would fire
|
});
|
||||||
// them if they did.
|
|
||||||
|
it('updates all changed bounds when requested', function () {
|
||||||
fireWatchCollection("formModel", mockScope.formModel);
|
fireWatchCollection("formModel", mockScope.formModel);
|
||||||
fireWatch("formModel.start", mockScope.formModel.start);
|
fireWatch("formModel.start", mockScope.formModel.start);
|
||||||
fireWatch("formModel.end", mockScope.formModel.end);
|
fireWatch("formModel.end", mockScope.formModel.end);
|
||||||
});
|
|
||||||
|
|
||||||
it("does not immediately make changes to the model", function () {
|
|
||||||
expect(mockScope.ngModel.outer.start)
|
expect(mockScope.ngModel.outer.start)
|
||||||
.not.toEqual(mockScope.formModel.start);
|
.not.toEqual(mockScope.formModel.start);
|
||||||
|
expect(mockScope.ngModel.inner.start)
|
||||||
|
.not.toEqual(mockScope.formModel.start);
|
||||||
|
|
||||||
expect(mockScope.ngModel.outer.end)
|
expect(mockScope.ngModel.outer.end)
|
||||||
.not.toEqual(mockScope.formModel.end);
|
.not.toEqual(mockScope.formModel.end);
|
||||||
|
expect(mockScope.ngModel.inner.end)
|
||||||
|
.not.toEqual(mockScope.formModel.end);
|
||||||
|
|
||||||
|
controller.updateBoundsFromForm();
|
||||||
|
|
||||||
|
expect(mockScope.ngModel.outer.start)
|
||||||
|
.toEqual(mockScope.formModel.start);
|
||||||
|
expect(mockScope.ngModel.inner.start)
|
||||||
|
.toEqual(mockScope.formModel.start);
|
||||||
|
|
||||||
|
expect(mockScope.ngModel.outer.end)
|
||||||
|
.toEqual(mockScope.formModel.end);
|
||||||
|
expect(mockScope.ngModel.inner.end)
|
||||||
|
.toEqual(mockScope.formModel.end);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates changed start bound when requested', function () {
|
||||||
|
fireWatchCollection("formModel", mockScope.formModel);
|
||||||
|
fireWatch("formModel.start", mockScope.formModel.start);
|
||||||
|
|
||||||
|
expect(mockScope.ngModel.outer.start)
|
||||||
|
.not.toEqual(mockScope.formModel.start);
|
||||||
|
expect(mockScope.ngModel.inner.start)
|
||||||
|
.not.toEqual(mockScope.formModel.start);
|
||||||
|
|
||||||
|
expect(mockScope.ngModel.outer.end)
|
||||||
|
.not.toEqual(mockScope.formModel.end);
|
||||||
|
expect(mockScope.ngModel.inner.end)
|
||||||
|
.not.toEqual(mockScope.formModel.end);
|
||||||
|
|
||||||
|
controller.updateBoundsFromForm();
|
||||||
|
|
||||||
|
expect(mockScope.ngModel.outer.start)
|
||||||
|
.toEqual(mockScope.formModel.start);
|
||||||
|
expect(mockScope.ngModel.inner.start)
|
||||||
|
.toEqual(mockScope.formModel.start);
|
||||||
|
|
||||||
|
expect(mockScope.ngModel.outer.end)
|
||||||
|
.not.toEqual(mockScope.formModel.end);
|
||||||
|
expect(mockScope.ngModel.inner.end)
|
||||||
|
.not.toEqual(mockScope.formModel.end);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("updates model bounds on request", function () {
|
it('updates changed end bound when requested', function () {
|
||||||
mockScope.updateBoundsFromForm();
|
fireWatchCollection("formModel", mockScope.formModel);
|
||||||
|
fireWatch("formModel.end", mockScope.formModel.end);
|
||||||
|
|
||||||
expect(mockScope.ngModel.outer.start)
|
expect(mockScope.ngModel.outer.start)
|
||||||
.toEqual(mockScope.formModel.start);
|
.not.toEqual(mockScope.formModel.start);
|
||||||
|
expect(mockScope.ngModel.inner.start)
|
||||||
|
.not.toEqual(mockScope.formModel.start);
|
||||||
|
|
||||||
expect(mockScope.ngModel.outer.end)
|
expect(mockScope.ngModel.outer.end)
|
||||||
|
.not.toEqual(mockScope.formModel.end);
|
||||||
|
expect(mockScope.ngModel.inner.end)
|
||||||
|
.not.toEqual(mockScope.formModel.end);
|
||||||
|
|
||||||
|
controller.updateBoundsFromForm();
|
||||||
|
|
||||||
|
expect(mockScope.ngModel.outer.start)
|
||||||
|
.not.toEqual(mockScope.formModel.start);
|
||||||
|
expect(mockScope.ngModel.inner.start)
|
||||||
|
.not.toEqual(mockScope.formModel.start);
|
||||||
|
|
||||||
|
expect(mockScope.ngModel.outer.end)
|
||||||
|
.toEqual(mockScope.formModel.end);
|
||||||
|
expect(mockScope.ngModel.inner.end)
|
||||||
.toEqual(mockScope.formModel.end);
|
.toEqual(mockScope.formModel.end);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -158,27 +220,27 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("updates the start time for left drags", function () {
|
it("updates the start time for left drags", function () {
|
||||||
mockScope.startLeftDrag();
|
controller.startLeftDrag();
|
||||||
mockScope.leftDrag(250);
|
controller.leftDrag(250);
|
||||||
expect(mockScope.ngModel.inner.start)
|
expect(mockScope.ngModel.inner.start)
|
||||||
.toEqual(DAY * 1000 + HOUR * 9);
|
.toEqual(DAY * 1000 + HOUR * 9);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("updates the end time for right drags", function () {
|
it("updates the end time for right drags", function () {
|
||||||
mockScope.startRightDrag();
|
controller.startRightDrag();
|
||||||
mockScope.rightDrag(-250);
|
controller.rightDrag(-250);
|
||||||
expect(mockScope.ngModel.inner.end)
|
expect(mockScope.ngModel.inner.end)
|
||||||
.toEqual(DAY * 1000 + HOUR * 15);
|
.toEqual(DAY * 1000 + HOUR * 15);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("updates both start and end for middle drags", function () {
|
it("updates both start and end for middle drags", function () {
|
||||||
mockScope.startMiddleDrag();
|
controller.startMiddleDrag();
|
||||||
mockScope.middleDrag(-125);
|
controller.middleDrag(-125);
|
||||||
expect(mockScope.ngModel.inner).toEqual({
|
expect(mockScope.ngModel.inner).toEqual({
|
||||||
start: DAY * 1000,
|
start: DAY * 1000,
|
||||||
end: DAY * 1000 + HOUR * 18
|
end: DAY * 1000 + HOUR * 18
|
||||||
});
|
});
|
||||||
mockScope.middleDrag(250);
|
controller.middleDrag(250);
|
||||||
expect(mockScope.ngModel.inner).toEqual({
|
expect(mockScope.ngModel.inner).toEqual({
|
||||||
start: DAY * 1000 + HOUR * 6,
|
start: DAY * 1000 + HOUR * 6,
|
||||||
end: DAY * 1001
|
end: DAY * 1001
|
||||||
@ -186,8 +248,8 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("enforces a minimum inner span", function () {
|
it("enforces a minimum inner span", function () {
|
||||||
mockScope.startRightDrag();
|
controller.startRightDrag();
|
||||||
mockScope.rightDrag(-9999999);
|
controller.rightDrag(-9999999);
|
||||||
expect(mockScope.ngModel.inner.end)
|
expect(mockScope.ngModel.inner.end)
|
||||||
.toBeGreaterThan(mockScope.ngModel.inner.start);
|
.toBeGreaterThan(mockScope.ngModel.inner.start);
|
||||||
});
|
});
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
/*global define,describe,beforeEach,jasmine,it,expect*/
|
/*global describe,beforeEach,jasmine,it,expect*/
|
||||||
|
|
||||||
define([
|
define([
|
||||||
'../../src/ui/TreeView',
|
'../../src/ui/TreeView',
|
||||||
|
@ -26,7 +26,6 @@
|
|||||||
define(
|
define(
|
||||||
[],
|
[],
|
||||||
function () {
|
function () {
|
||||||
var DISALLOWED_ACTIONS = ["copy", "window", "follow"];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ActionCapability allows applicable Actions to be retrieved and
|
* The ActionCapability allows applicable Actions to be retrieved and
|
||||||
@ -53,37 +52,22 @@ define(
|
|||||||
this.domainObject = domainObject;
|
this.domainObject = domainObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isEditable(domainObject){
|
|
||||||
return domainObject.getCapability('status').get('editing');
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasEditableAncestor(domainObject){
|
|
||||||
return domainObject.hasCapability('context') &&
|
|
||||||
domainObject
|
|
||||||
.getCapability('context')
|
|
||||||
.getPath()
|
|
||||||
.some(function isEditable (ancestor){
|
|
||||||
return ancestor.getCapability('status').get('editing');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the actions applicable to the domain object in the given
|
* Perform an action. This will find and perform the
|
||||||
* context.
|
* first matching action available for the specified
|
||||||
|
* context or key.
|
||||||
*
|
*
|
||||||
* @param {ActionContext|string} context the context in which
|
* @param {ActionContext|string} context the context in which
|
||||||
* to assess the applicability of the available actions; this is
|
* to perform the action; this is passed along to
|
||||||
* passed along to the action service to match against available
|
* the action service to match against available
|
||||||
* actions. The "domainObject" field will automatically
|
* actions. The "domainObject" field will automatically
|
||||||
* be populated with the domain object that exposed
|
* be populated with the domain object that exposed
|
||||||
* this capability. If given as a string, this will
|
* this capability. If given as a string, this will
|
||||||
* be taken as the "key" field to match against
|
* be taken as the "key" field to match against
|
||||||
* specific actions.
|
* specific actions.
|
||||||
*
|
* @returns {Promise} the result of the action that was
|
||||||
* Additionally, this function will limit the actions
|
* performed, or undefined if no matching action
|
||||||
* available for an object in Edit Mode
|
* was found.
|
||||||
* @returns {Array<Action>} The actions applicable to this domain
|
|
||||||
* object in the given context
|
|
||||||
* @memberof platform/core.ActionCapability#
|
* @memberof platform/core.ActionCapability#
|
||||||
*/
|
*/
|
||||||
ActionCapability.prototype.getActions = function (context) {
|
ActionCapability.prototype.getActions = function (context) {
|
||||||
@ -92,19 +76,11 @@ define(
|
|||||||
// but additionally adds a domainObject field.
|
// but additionally adds a domainObject field.
|
||||||
var baseContext = typeof context === 'string' ?
|
var baseContext = typeof context === 'string' ?
|
||||||
{ key: context } : (context || {}),
|
{ key: context } : (context || {}),
|
||||||
actionContext = Object.create(baseContext),
|
actionContext = Object.create(baseContext);
|
||||||
actions;
|
|
||||||
|
|
||||||
actionContext.domainObject = this.domainObject;
|
actionContext.domainObject = this.domainObject;
|
||||||
|
|
||||||
actions = this.actionService.getActions(actionContext) || [];
|
return this.actionService.getActions(actionContext);
|
||||||
if (isEditable(this.domainObject) || hasEditableAncestor(this.domainObject)){
|
|
||||||
return actions.filter(function(action){
|
|
||||||
return DISALLOWED_ACTIONS.indexOf(action.getMetadata().key) === -1;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return actions;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,8 +26,8 @@
|
|||||||
define(
|
define(
|
||||||
["../../src/actions/ActionCapability"],
|
["../../src/actions/ActionCapability"],
|
||||||
function (ActionCapability) {
|
function (ActionCapability) {
|
||||||
//TODO: Disabled for NEM beta
|
|
||||||
xdescribe("The action capability", function () {
|
describe("The action capability", function () {
|
||||||
var mockQ,
|
var mockQ,
|
||||||
mockAction,
|
mockAction,
|
||||||
mockActionService,
|
mockActionService,
|
||||||
@ -95,4 +95,4 @@ define(
|
|||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -6,6 +6,6 @@
|
|||||||
ng-model='ngModel'
|
ng-model='ngModel'
|
||||||
field="'domain'"
|
field="'domain'"
|
||||||
options="ngModel.options"
|
options="ngModel.options"
|
||||||
style="position: absolute; right: 0px; bottom: 46px;"
|
class="l-time-domain-selector"
|
||||||
>
|
>
|
||||||
</mct-control>
|
</mct-control>
|
||||||
|
@ -22,16 +22,16 @@
|
|||||||
|
|
||||||
define([
|
define([
|
||||||
"./src/directives/MCTTable",
|
"./src/directives/MCTTable",
|
||||||
"./src/controllers/RTTelemetryTableController",
|
"./src/controllers/RealtimeTableController",
|
||||||
"./src/controllers/TelemetryTableController",
|
"./src/controllers/HistoricalTableController",
|
||||||
"./src/controllers/TableOptionsController",
|
"./src/controllers/TableOptionsController",
|
||||||
'../../commonUI/regions/src/Region',
|
'../../commonUI/regions/src/Region',
|
||||||
'../../commonUI/browse/src/InspectorRegion',
|
'../../commonUI/browse/src/InspectorRegion',
|
||||||
"legacyRegistry"
|
"legacyRegistry"
|
||||||
], function (
|
], function (
|
||||||
MCTTable,
|
MCTTable,
|
||||||
RTTelemetryTableController,
|
RealtimeTableController,
|
||||||
TelemetryTableController,
|
HistoricalTableController,
|
||||||
TableOptionsController,
|
TableOptionsController,
|
||||||
Region,
|
Region,
|
||||||
InspectorRegion,
|
InspectorRegion,
|
||||||
@ -107,13 +107,13 @@ define([
|
|||||||
],
|
],
|
||||||
"controllers": [
|
"controllers": [
|
||||||
{
|
{
|
||||||
"key": "TelemetryTableController",
|
"key": "HistoricalTableController",
|
||||||
"implementation": TelemetryTableController,
|
"implementation": HistoricalTableController,
|
||||||
"depends": ["$scope", "telemetryHandler", "telemetryFormatter"]
|
"depends": ["$scope", "telemetryHandler", "telemetryFormatter"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "RTTelemetryTableController",
|
"key": "RealtimeTableController",
|
||||||
"implementation": RTTelemetryTableController,
|
"implementation": RealtimeTableController,
|
||||||
"depends": ["$scope", "telemetryHandler", "telemetryFormatter"]
|
"depends": ["$scope", "telemetryHandler", "telemetryFormatter"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -128,7 +128,7 @@ define([
|
|||||||
"name": "Historical Table",
|
"name": "Historical Table",
|
||||||
"key": "table",
|
"key": "table",
|
||||||
"glyph": "\ue604",
|
"glyph": "\ue604",
|
||||||
"templateUrl": "templates/table.html",
|
"templateUrl": "templates/historical-table.html",
|
||||||
"needs": [
|
"needs": [
|
||||||
"telemetry"
|
"telemetry"
|
||||||
],
|
],
|
||||||
@ -159,6 +159,12 @@ define([
|
|||||||
"key": "table-options-edit",
|
"key": "table-options-edit",
|
||||||
"templateUrl": "templates/table-options-edit.html"
|
"templateUrl": "templates/table-options-edit.html"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"stylesheets": [
|
||||||
|
{
|
||||||
|
"stylesheetUrl": "css/table.css",
|
||||||
|
"priority": "mandatory"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -20,28 +20,43 @@
|
|||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
define([
|
.sizing-table {
|
||||||
"./src/CachingPersistenceDecorator",
|
min-width: 100%;
|
||||||
'legacyRegistry'
|
z-index: -1;
|
||||||
], function (
|
visibility: hidden;
|
||||||
CachingPersistenceDecorator,
|
position: absolute;
|
||||||
legacyRegistry
|
|
||||||
) {
|
|
||||||
|
|
||||||
legacyRegistry.register("platform/persistence/cache", {
|
//Add some padding to allow for decorations such as limits indicator
|
||||||
"name": "Persistence cache",
|
td {
|
||||||
"description": "Cache to improve availability of persisted objects.",
|
padding-right: 15px;
|
||||||
"extensions": {
|
padding-left: 10px;
|
||||||
"components": [
|
white-space: nowrap;
|
||||||
{
|
}
|
||||||
"provides": "persistenceService",
|
}
|
||||||
"type": "decorator",
|
.mct-table {
|
||||||
"implementation": CachingPersistenceDecorator,
|
table-layout: fixed;
|
||||||
"depends": [
|
thead {
|
||||||
"PERSISTENCE_SPACE"
|
display: block;
|
||||||
]
|
tr {
|
||||||
}
|
display: block;
|
||||||
]
|
white-space: nowrap;
|
||||||
|
th {
|
||||||
|
display: inline-block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
});
|
tbody {
|
||||||
|
tr {
|
||||||
|
position: absolute;
|
||||||
|
white-space: nowrap;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
<div ng-controller="TelemetryTableController">
|
<div ng-controller="HistoricalTableController">
|
||||||
<mct-table
|
<mct-table
|
||||||
headers="headers"
|
headers="headers"
|
||||||
rows="rows"
|
rows="rows"
|
@ -1,22 +1,25 @@
|
|||||||
<div class="l-view-section scrolling"
|
<div class="l-view-section scrolling" style="overflow: auto;">
|
||||||
ng-style="overrideRowPositioning ?
|
<table class="sizing-table">
|
||||||
{'overflow': 'auto'} :
|
<tbody>
|
||||||
{'overflow': 'scroll'}"
|
<tr>
|
||||||
>
|
<td ng-repeat="header in displayHeaders">{{header}}</td>
|
||||||
<table class="filterable"
|
</tr>
|
||||||
ng-style="overrideRowPositioning && {
|
<tr><td ng-repeat="header in displayHeaders" >
|
||||||
|
{{sizingRow[header].text}}
|
||||||
|
</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<table class="filterable mct-table"
|
||||||
|
ng-style="{
|
||||||
height: totalHeight + 'px',
|
height: totalHeight + 'px',
|
||||||
'table-layout': overrideRowPositioning ? 'fixed' : 'auto',
|
|
||||||
'max-width': totalWidth
|
'max-width': totalWidth
|
||||||
}">
|
}">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th ng-repeat="header in displayHeaders"
|
<th ng-repeat="header in displayHeaders"
|
||||||
ng-style="overrideRowPositioning && {
|
ng-style="{
|
||||||
width: columnWidths[$index] + 'px',
|
width: columnWidths[$index] + 'px',
|
||||||
'max-width': columnWidths[$index] + 'px',
|
'max-width': columnWidths[$index] + 'px',
|
||||||
overflow: 'none',
|
|
||||||
'box-sizing': 'border-box'
|
|
||||||
}"
|
}"
|
||||||
ng-class="[
|
ng-class="[
|
||||||
enableSort ? 'sortable' : '',
|
enableSort ? 'sortable' : '',
|
||||||
@ -29,11 +32,9 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr ng-if="enableFilter" class="s-filters">
|
<tr ng-if="enableFilter" class="s-filters">
|
||||||
<th ng-repeat="header in displayHeaders"
|
<th ng-repeat="header in displayHeaders"
|
||||||
ng-style="overrideRowPositioning && {
|
ng-style="{
|
||||||
width: columnWidths[$index] + 'px',
|
width: columnWidths[$index] + 'px',
|
||||||
'max-width': columnWidths[$index] + 'px',
|
'max-width': columnWidths[$index] + 'px',
|
||||||
overflow: 'none',
|
|
||||||
'box-sizing': 'border-box'
|
|
||||||
}">
|
}">
|
||||||
<input type="text"
|
<input type="text"
|
||||||
ng-model="filters[header]"/>
|
ng-model="filters[header]"/>
|
||||||
@ -41,21 +42,15 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody ng-style="overrideRowPositioning ? '' : {
|
<tbody>
|
||||||
'opacity': '0.0'
|
|
||||||
}">
|
|
||||||
<tr ng-repeat="visibleRow in visibleRows track by visibleRow.rowIndex"
|
<tr ng-repeat="visibleRow in visibleRows track by visibleRow.rowIndex"
|
||||||
ng-style="overrideRowPositioning && {
|
ng-style="{
|
||||||
position: 'absolute',
|
|
||||||
top: visibleRow.offsetY + 'px',
|
top: visibleRow.offsetY + 'px',
|
||||||
}">
|
}">
|
||||||
<td ng-repeat="header in displayHeaders"
|
<td ng-repeat="header in displayHeaders"
|
||||||
ng-style="overrideRowPositioning && {
|
ng-style=" {
|
||||||
width: columnWidths[$index] + 'px',
|
width: columnWidths[$index] + 'px',
|
||||||
'white-space': 'nowrap',
|
|
||||||
'max-width': columnWidths[$index] + 'px',
|
'max-width': columnWidths[$index] + 'px',
|
||||||
overflow: 'hidden',
|
|
||||||
'box-sizing': 'border-box'
|
|
||||||
}"
|
}"
|
||||||
class="{{visibleRow.contents[header].cssClass}}">
|
class="{{visibleRow.contents[header].cssClass}}">
|
||||||
{{ visibleRow.contents[header].text }}
|
{{ visibleRow.contents[header].text }}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<div ng-controller="RTTelemetryTableController">
|
<div ng-controller="RealtimeTableController">
|
||||||
<mct-table
|
<mct-table
|
||||||
headers="headers"
|
headers="headers"
|
||||||
rows="rows"
|
rows="rows"
|
||||||
|
@ -45,7 +45,7 @@ define(
|
|||||||
* @param metadata Metadata describing the domains and ranges available
|
* @param metadata Metadata describing the domains and ranges available
|
||||||
* @returns {TableConfiguration} This object
|
* @returns {TableConfiguration} This object
|
||||||
*/
|
*/
|
||||||
TableConfiguration.prototype.buildColumns = function (metadata) {
|
TableConfiguration.prototype.populateColumns = function (metadata) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this.columns = [];
|
this.columns = [];
|
||||||
@ -138,8 +138,7 @@ define(
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
TableConfiguration.prototype.defaultColumnConfiguration = function () {
|
TableConfiguration.prototype.defaultColumnConfiguration = function () {
|
||||||
return ((this.domainObject.getModel().configuration || {}).table ||
|
return ((this.domainObject.getModel().configuration || {}).table || {}).columns || {};
|
||||||
{}).columns || {};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -154,6 +153,16 @@ define(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function configChanged(config1, config2) {
|
||||||
|
var config1Keys = Object.keys(config1),
|
||||||
|
config2Keys = Object.keys(config2);
|
||||||
|
|
||||||
|
return (config1Keys.length !== config2Keys.length) ||
|
||||||
|
config1Keys.some(function(key){
|
||||||
|
return config1[key] !== config2[key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* As part of the process of building the table definition, extract
|
* As part of the process of building the table definition, extract
|
||||||
* configuration from column definitions.
|
* configuration from column definitions.
|
||||||
@ -161,7 +170,7 @@ define(
|
|||||||
* pairs where the key is the column title, and the value is a
|
* pairs where the key is the column title, and the value is a
|
||||||
* boolean indicating whether the column should be shown.
|
* boolean indicating whether the column should be shown.
|
||||||
*/
|
*/
|
||||||
TableConfiguration.prototype.getColumnConfiguration = function () {
|
TableConfiguration.prototype.buildColumnConfiguration = function () {
|
||||||
var configuration = {},
|
var configuration = {},
|
||||||
//Use existing persisted config, or default it
|
//Use existing persisted config, or default it
|
||||||
defaultConfig = this.defaultColumnConfiguration();
|
defaultConfig = this.defaultColumnConfiguration();
|
||||||
@ -177,6 +186,11 @@ define(
|
|||||||
defaultConfig[columnTitle];
|
defaultConfig[columnTitle];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//Synchronize column configuration with model
|
||||||
|
if (configChanged(configuration, defaultConfig)) {
|
||||||
|
this.saveColumnConfiguration(configuration);
|
||||||
|
}
|
||||||
|
|
||||||
return configuration;
|
return configuration;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||||
|
* as represented by the Administrator of the National Aeronautics and Space
|
||||||
|
* Administration. All rights reserved.
|
||||||
|
*
|
||||||
|
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*
|
||||||
|
* Open MCT Web includes source code licensed under additional open source
|
||||||
|
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||||
|
* this source code distribution or the Licensing information page available
|
||||||
|
* at runtime from the About dialog for additional information.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
define(
|
||||||
|
[
|
||||||
|
'./TelemetryTableController'
|
||||||
|
],
|
||||||
|
function (TableController) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extends TelemetryTableController and adds real-time streaming
|
||||||
|
* support.
|
||||||
|
* @memberof platform/features/table
|
||||||
|
* @param $scope
|
||||||
|
* @param telemetryHandler
|
||||||
|
* @param telemetryFormatter
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function HistoricalTableController($scope, telemetryHandler, telemetryFormatter) {
|
||||||
|
TableController.call(this, $scope, telemetryHandler, telemetryFormatter);
|
||||||
|
}
|
||||||
|
|
||||||
|
HistoricalTableController.prototype = Object.create(TableController.prototype);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates historical data on scope when it becomes available from
|
||||||
|
* the telemetry API
|
||||||
|
*/
|
||||||
|
HistoricalTableController.prototype.addHistoricalData = function () {
|
||||||
|
var rowData = [],
|
||||||
|
self = this;
|
||||||
|
|
||||||
|
this.handle.getTelemetryObjects().forEach(function (telemetryObject){
|
||||||
|
var series = self.handle.getSeries(telemetryObject) || {},
|
||||||
|
pointCount = series.getPointCount ? series.getPointCount() : 0,
|
||||||
|
i = 0;
|
||||||
|
|
||||||
|
for (; i < pointCount; i++) {
|
||||||
|
rowData.push(self.table.getRowValues(telemetryObject,
|
||||||
|
self.handle.makeDatum(telemetryObject, series, i)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$scope.rows = rowData;
|
||||||
|
};
|
||||||
|
|
||||||
|
return HistoricalTableController;
|
||||||
|
}
|
||||||
|
);
|
@ -21,10 +21,13 @@ define(
|
|||||||
this.maxDisplayRows = 50;
|
this.maxDisplayRows = 50;
|
||||||
|
|
||||||
this.scrollable = element.find('div');
|
this.scrollable = element.find('div');
|
||||||
|
this.thead = element.find('thead');
|
||||||
|
this.tbody = element.find('tbody');
|
||||||
|
this.$scope.sizingRow = {};
|
||||||
|
|
||||||
this.scrollable.on('scroll', this.onScroll.bind(this));
|
this.scrollable.on('scroll', this.onScroll.bind(this));
|
||||||
|
|
||||||
$scope.visibleRows = [];
|
$scope.visibleRows = [];
|
||||||
$scope.overrideRowPositioning = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set default values for optional parameters on a given scope
|
* Set default values for optional parameters on a given scope
|
||||||
@ -56,22 +59,22 @@ define(
|
|||||||
$scope.sortColumn = undefined;
|
$scope.sortColumn = undefined;
|
||||||
$scope.sortDirection = undefined;
|
$scope.sortDirection = undefined;
|
||||||
}
|
}
|
||||||
self.updateRows($scope.rows);
|
self.setRows($scope.rows);
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Define watches to listen for changes to headers and rows.
|
* Define watches to listen for changes to headers and rows.
|
||||||
*/
|
*/
|
||||||
$scope.$watchCollection('filters', function () {
|
$scope.$watchCollection('filters', function () {
|
||||||
self.updateRows($scope.rows);
|
self.setRows($scope.rows);
|
||||||
});
|
});
|
||||||
$scope.$watch('headers', this.updateHeaders.bind(this));
|
$scope.$watch('headers', this.setHeaders.bind(this));
|
||||||
$scope.$watch('rows', this.updateRows.bind(this));
|
$scope.$watch('rows', this.setRows.bind(this));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Listen for rows added individually (eg. for real-time tables)
|
* Listen for rows added individually (eg. for real-time tables)
|
||||||
*/
|
*/
|
||||||
$scope.$on('add:row', this.newRow.bind(this));
|
$scope.$on('add:row', this.addRow.bind(this));
|
||||||
$scope.$on('remove:row', this.removeRow.bind(this));
|
$scope.$on('remove:row', this.removeRow.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,23 +97,27 @@ define(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles a row add event. Rows can be added as needed using the
|
* Handles a row add event. Rows can be added as needed using the
|
||||||
* `addRow` broadcast event.
|
* `add:row` broadcast event.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
MCTTableController.prototype.newRow = function (event, rowIndex) {
|
MCTTableController.prototype.addRow = function (event, rowIndex) {
|
||||||
var row = this.$scope.rows[rowIndex];
|
var row = this.$scope.rows[rowIndex];
|
||||||
//Add row to the filtered, sorted list of all rows
|
|
||||||
if (this.filterRows([row]).length > 0) {
|
|
||||||
this.insertSorted(this.$scope.displayRows, row);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$timeout(this.setElementSizes.bind(this))
|
//Does the row pass the current filter?
|
||||||
.then(this.scrollToBottom.bind(this));
|
if (this.filterRows([row]).length === 1) {
|
||||||
|
//Insert the row into the correct position in the array
|
||||||
|
this.insertSorted(this.$scope.displayRows, row);
|
||||||
|
|
||||||
|
//Resize the columns , then update the rows visible in the table
|
||||||
|
this.resize([this.$scope.sizingRow, row])
|
||||||
|
.then(this.setVisibleRows.bind(this))
|
||||||
|
.then(this.scrollToBottom.bind(this));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles a row add event. Rows can be added as needed using the
|
* Handles a row remove event. Rows can be removed as needed using the
|
||||||
* `addRow` broadcast event.
|
* `remove:row` broadcast event.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
MCTTableController.prototype.removeRow = function (event, rowIndex) {
|
MCTTableController.prototype.removeRow = function (event, rowIndex) {
|
||||||
@ -223,7 +230,7 @@ define(
|
|||||||
* enabled, reset filters. If sorting is enabled, reset
|
* enabled, reset filters. If sorting is enabled, reset
|
||||||
* sorting.
|
* sorting.
|
||||||
*/
|
*/
|
||||||
MCTTableController.prototype.updateHeaders = function (newHeaders) {
|
MCTTableController.prototype.setHeaders = function (newHeaders) {
|
||||||
if (!newHeaders){
|
if (!newHeaders){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -239,7 +246,7 @@ define(
|
|||||||
this.$scope.sortColumn = undefined;
|
this.$scope.sortColumn = undefined;
|
||||||
this.$scope.sortDirection = undefined;
|
this.$scope.sortDirection = undefined;
|
||||||
}
|
}
|
||||||
this.updateRows(this.$scope.rows);
|
this.setRows(this.$scope.rows);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -247,12 +254,12 @@ define(
|
|||||||
* for individual rows.
|
* for individual rows.
|
||||||
*/
|
*/
|
||||||
MCTTableController.prototype.setElementSizes = function () {
|
MCTTableController.prototype.setElementSizes = function () {
|
||||||
var thead = this.element.find('thead'),
|
var thead = this.thead,
|
||||||
tbody = this.element.find('tbody'),
|
tbody = this.tbody,
|
||||||
firstRow = tbody.find('tr'),
|
firstRow = tbody.find('tr'),
|
||||||
column = firstRow.find('td'),
|
column = firstRow.find('td'),
|
||||||
headerHeight = thead.prop('offsetHeight'),
|
headerHeight = thead.prop('offsetHeight'),
|
||||||
rowHeight = 20,
|
rowHeight = firstRow.prop('offsetHeight'),
|
||||||
columnWidth,
|
columnWidth,
|
||||||
tableWidth = 0,
|
tableWidth = 0,
|
||||||
overallHeight = headerHeight + (rowHeight *
|
overallHeight = headerHeight + (rowHeight *
|
||||||
@ -269,15 +276,12 @@ define(
|
|||||||
this.$scope.headerHeight = headerHeight;
|
this.$scope.headerHeight = headerHeight;
|
||||||
this.$scope.rowHeight = rowHeight;
|
this.$scope.rowHeight = rowHeight;
|
||||||
this.$scope.totalHeight = overallHeight;
|
this.$scope.totalHeight = overallHeight;
|
||||||
this.setVisibleRows();
|
|
||||||
|
|
||||||
if (tableWidth > 0) {
|
if (tableWidth > 0) {
|
||||||
this.$scope.totalWidth = tableWidth + 'px';
|
this.$scope.totalWidth = tableWidth + 'px';
|
||||||
} else {
|
} else {
|
||||||
this.$scope.totalWidth = 'none';
|
this.$scope.totalWidth = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$scope.overrideRowPositioning = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -289,21 +293,14 @@ define(
|
|||||||
sortKey = this.$scope.sortColumn;
|
sortKey = this.$scope.sortColumn;
|
||||||
|
|
||||||
function binarySearch(searchArray, searchElement, min, max){
|
function binarySearch(searchArray, searchElement, min, max){
|
||||||
var sampleAt = Math.floor((max - min) / 2) + min,
|
var sampleAt = Math.floor((max - min) / 2) + min;
|
||||||
valA,
|
|
||||||
valB;
|
|
||||||
if (max < min) {
|
if (max < min) {
|
||||||
return min; // Element is not in array, min gives direction
|
return min; // Element is not in array, min gives direction
|
||||||
}
|
}
|
||||||
|
|
||||||
valA = isNaN(searchElement[sortKey].text) ?
|
switch(self.sortComparator(searchElement[sortKey].text,
|
||||||
searchElement[sortKey].text :
|
searchArray[sampleAt][sortKey].text)) {
|
||||||
parseFloat(searchElement[sortKey].text);
|
|
||||||
valB = isNaN(searchArray[sampleAt][sortKey].text) ?
|
|
||||||
searchArray[sampleAt][sortKey].text :
|
|
||||||
parseFloat(searchArray[sampleAt][sortKey].text);
|
|
||||||
|
|
||||||
switch(self.sortComparator(valA, valB)) {
|
|
||||||
case -1:
|
case -1:
|
||||||
return binarySearch(searchArray, searchElement, min,
|
return binarySearch(searchArray, searchElement, min,
|
||||||
sampleAt - 1);
|
sampleAt - 1);
|
||||||
@ -341,8 +338,34 @@ define(
|
|||||||
*/
|
*/
|
||||||
MCTTableController.prototype.sortComparator = function (a, b) {
|
MCTTableController.prototype.sortComparator = function (a, b) {
|
||||||
var result = 0,
|
var result = 0,
|
||||||
sortDirectionMultiplier;
|
sortDirectionMultiplier,
|
||||||
|
numberA,
|
||||||
|
numberB;
|
||||||
|
/**
|
||||||
|
* Given a value, if it is a number, or a string representation of a
|
||||||
|
* number, then return a number representation. Otherwise, return
|
||||||
|
* the original value. It's a little more robust than using just
|
||||||
|
* Number() or parseFloat, or isNaN in isolation, all of which are
|
||||||
|
* fairly inconsistent in their results.
|
||||||
|
* @param value The value to return as a number.
|
||||||
|
* @returns {*} The value cast to a Number, or the original value if
|
||||||
|
* a Number representation is not possible.
|
||||||
|
*/
|
||||||
|
function toNumber (value){
|
||||||
|
var val = !isNaN(Number(value)) && !isNaN(parseFloat(value)) ? Number(value) : value;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
numberA = toNumber(a);
|
||||||
|
numberB = toNumber(b);
|
||||||
|
|
||||||
|
//If they're both numbers, then compare them as numbers
|
||||||
|
if (typeof numberA === "number" && typeof numberB === "number") {
|
||||||
|
a = numberA;
|
||||||
|
b = numberB;
|
||||||
|
}
|
||||||
|
|
||||||
|
//If they're both strings, then ignore case
|
||||||
if (typeof a === "string" && typeof b === "string") {
|
if (typeof a === "string" && typeof b === "string") {
|
||||||
a = a.toLowerCase();
|
a = a.toLowerCase();
|
||||||
b = b.toLowerCase();
|
b = b.toLowerCase();
|
||||||
@ -379,15 +402,7 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return rowsToSort.sort(function (a, b) {
|
return rowsToSort.sort(function (a, b) {
|
||||||
//If the values to compare can be compared as
|
return self.sortComparator(a[sortKey].text, b[sortKey].text);
|
||||||
// numbers, do so. String comparison of number
|
|
||||||
// values can cause inconsistencies
|
|
||||||
var valA = isNaN(a[sortKey].text) ? a[sortKey].text :
|
|
||||||
parseFloat(a[sortKey].text),
|
|
||||||
valB = isNaN(b[sortKey].text) ? b[sortKey].text :
|
|
||||||
parseFloat(b[sortKey].text);
|
|
||||||
|
|
||||||
return self.sortComparator(valA, valB);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -397,74 +412,48 @@ define(
|
|||||||
* pre-calculate optimal column sizes without having to render
|
* pre-calculate optimal column sizes without having to render
|
||||||
* every row.
|
* every row.
|
||||||
*/
|
*/
|
||||||
MCTTableController.prototype.findLargestRow = function (rows) {
|
MCTTableController.prototype.buildLargestRow = function (rows) {
|
||||||
var largestRow = rows.reduce(function (largestRow, row) {
|
var largestRow = rows.reduce(function (prevLargest, row) {
|
||||||
Object.keys(row).forEach(function (key) {
|
Object.keys(row).forEach(function (key) {
|
||||||
var currentColumn = row[key].text,
|
var currentColumn,
|
||||||
|
currentColumnLength,
|
||||||
|
largestColumn,
|
||||||
|
largestColumnLength;
|
||||||
|
if (row[key]){
|
||||||
|
currentColumn = (row[key]).text;
|
||||||
currentColumnLength =
|
currentColumnLength =
|
||||||
(currentColumn && currentColumn.length) ?
|
(currentColumn && currentColumn.length) ?
|
||||||
currentColumn.length :
|
currentColumn.length :
|
||||||
currentColumn,
|
currentColumn;
|
||||||
largestColumn = largestRow[key].text,
|
largestColumn = prevLargest[key] ? prevLargest[key].text : "";
|
||||||
largestColumnLength =
|
largestColumnLength = largestColumn.length;
|
||||||
(largestColumn && largestColumn.length) ?
|
|
||||||
largestColumn.length :
|
|
||||||
largestColumn;
|
|
||||||
|
|
||||||
if (currentColumnLength > largestColumnLength) {
|
if (currentColumnLength > largestColumnLength) {
|
||||||
largestRow[key] = JSON.parse(JSON.stringify(row[key]));
|
prevLargest[key] = JSON.parse(JSON.stringify(row[key]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return largestRow;
|
return prevLargest;
|
||||||
}, JSON.parse(JSON.stringify(rows[0] || {})));
|
}, JSON.parse(JSON.stringify(rows[0] || {})));
|
||||||
|
|
||||||
largestRow = JSON.parse(JSON.stringify(largestRow));
|
|
||||||
|
|
||||||
// Pad with characters to accomodate variable-width fonts,
|
|
||||||
// and remove characters that would allow word-wrapping.
|
|
||||||
Object.keys(largestRow).forEach(function (key) {
|
|
||||||
var padCharacters,
|
|
||||||
i;
|
|
||||||
|
|
||||||
largestRow[key].text = String(largestRow[key].text);
|
|
||||||
padCharacters = largestRow[key].text.length / 10;
|
|
||||||
for (i = 0; i < padCharacters; i++) {
|
|
||||||
largestRow[key].text = largestRow[key].text + 'W';
|
|
||||||
}
|
|
||||||
largestRow[key].text = largestRow[key].text
|
|
||||||
.replace(/[ \-_]/g, 'W');
|
|
||||||
});
|
|
||||||
return largestRow;
|
return largestRow;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the widest row in the table, pads that row, and adds
|
* Calculates the widest row in the table, and if necessary, resizes
|
||||||
* it to the table. Allows the table to size itself, then uses this
|
* the table accordingly
|
||||||
* as basis for column dimensions.
|
*
|
||||||
|
* @param rows the rows on which to resize
|
||||||
|
* @returns {Promise} a promise that will resolve when resizing has
|
||||||
|
* occurred.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
MCTTableController.prototype.resize = function (){
|
MCTTableController.prototype.resize = function (rows) {
|
||||||
var largestRow = this.findLargestRow(this.$scope.displayRows),
|
this.$scope.sizingRow = this.buildLargestRow(rows);
|
||||||
self = this;
|
return this.$timeout(this.setElementSizes.bind(this));
|
||||||
this.$scope.visibleRows = [
|
|
||||||
{
|
|
||||||
rowIndex: 0,
|
|
||||||
offsetY: undefined,
|
|
||||||
contents: largestRow
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
//Wait a timeout to allow digest of previous change to visible
|
|
||||||
// rows to happen.
|
|
||||||
this.$timeout(function () {
|
|
||||||
//Remove temporary padding row used for setting column widths
|
|
||||||
self.$scope.visibleRows = [];
|
|
||||||
self.setElementSizes();
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @priate
|
* @private
|
||||||
*/
|
*/
|
||||||
MCTTableController.prototype.filterAndSort = function (rows) {
|
MCTTableController.prototype.filterAndSort = function (rows) {
|
||||||
var displayRows = rows;
|
var displayRows = rows;
|
||||||
@ -475,26 +464,21 @@ define(
|
|||||||
if (this.$scope.enableSort) {
|
if (this.$scope.enableSort) {
|
||||||
displayRows = this.sortRows(displayRows.slice(0));
|
displayRows = this.sortRows(displayRows.slice(0));
|
||||||
}
|
}
|
||||||
this.$scope.displayRows = displayRows;
|
return displayRows;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update rows with new data. If filtering is enabled, rows
|
* Update rows with new data. If filtering is enabled, rows
|
||||||
* will be sorted before display.
|
* will be sorted before display.
|
||||||
*/
|
*/
|
||||||
MCTTableController.prototype.updateRows = function (newRows) {
|
MCTTableController.prototype.setRows = function (newRows) {
|
||||||
//Reset visible rows because new row data available.
|
|
||||||
this.$scope.visibleRows = [];
|
|
||||||
|
|
||||||
this.$scope.overrideRowPositioning = false;
|
|
||||||
|
|
||||||
//Nothing to show because no columns visible
|
//Nothing to show because no columns visible
|
||||||
if (!this.$scope.displayHeaders) {
|
if (!this.$scope.displayHeaders || !newRows) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.filterAndSort(newRows || []);
|
this.$scope.displayRows = this.filterAndSort(newRows || []);
|
||||||
this.resize();
|
this.resize(newRows).then(this.setVisibleRows.bind(this));
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,7 +35,7 @@ define(
|
|||||||
* @param telemetryFormatter
|
* @param telemetryFormatter
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function RTTelemetryTableController($scope, telemetryHandler, telemetryFormatter) {
|
function RealtimeTableController($scope, telemetryHandler, telemetryFormatter) {
|
||||||
TableController.call(this, $scope, telemetryHandler, telemetryFormatter);
|
TableController.call(this, $scope, telemetryHandler, telemetryFormatter);
|
||||||
|
|
||||||
$scope.autoScroll = false;
|
$scope.autoScroll = false;
|
||||||
@ -64,58 +64,35 @@ define(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
RTTelemetryTableController.prototype = Object.create(TableController.prototype);
|
RealtimeTableController.prototype = Object.create(TableController.prototype);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Override the subscribe function defined on the parent controller in
|
* Overrides method on TelemetryTableController providing handling
|
||||||
order to handle realtime telemetry instead of historical.
|
* for realtime data.
|
||||||
*/
|
*/
|
||||||
RTTelemetryTableController.prototype.subscribe = function () {
|
RealtimeTableController.prototype.addRealtimeData = function() {
|
||||||
var self = this;
|
var self = this,
|
||||||
self.$scope.rows = undefined;
|
datum,
|
||||||
(this.subscriptions || []).forEach(function (unsubscribe){
|
row;
|
||||||
unsubscribe();
|
this.handle.getTelemetryObjects().forEach(function (telemetryObject){
|
||||||
});
|
datum = self.handle.getDatum(telemetryObject);
|
||||||
|
if (datum) {
|
||||||
|
//Populate row values from telemetry datum
|
||||||
|
row = self.table.getRowValues(telemetryObject, datum);
|
||||||
|
self.$scope.rows.push(row);
|
||||||
|
|
||||||
if (this.handle) {
|
//Inform table that a new row has been added
|
||||||
this.handle.unsubscribe();
|
if (self.$scope.rows.length > self.maxRows) {
|
||||||
}
|
self.$scope.$broadcast('remove:row', 0);
|
||||||
|
self.$scope.rows.shift();
|
||||||
function updateData(){
|
|
||||||
var datum,
|
|
||||||
row;
|
|
||||||
self.handle.getTelemetryObjects().forEach(function (telemetryObject){
|
|
||||||
datum = self.handle.getDatum(telemetryObject);
|
|
||||||
if (datum) {
|
|
||||||
row = self.table.getRowValues(telemetryObject, datum);
|
|
||||||
if (!self.$scope.rows){
|
|
||||||
self.$scope.rows = [row];
|
|
||||||
self.$scope.$digest();
|
|
||||||
} else {
|
|
||||||
self.$scope.rows.push(row);
|
|
||||||
|
|
||||||
if (self.$scope.rows.length > self.maxRows) {
|
|
||||||
self.$scope.$broadcast('remove:row', 0);
|
|
||||||
self.$scope.rows.shift();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.$scope.$broadcast('add:row',
|
|
||||||
self.$scope.rows.length - 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
}
|
self.$scope.$broadcast('add:row',
|
||||||
|
self.$scope.rows.length - 1);
|
||||||
this.handle = this.$scope.domainObject && this.telemetryHandler.handle(
|
}
|
||||||
this.$scope.domainObject,
|
});
|
||||||
updateData,
|
|
||||||
true // Lossless
|
|
||||||
);
|
|
||||||
|
|
||||||
this.setup();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return RTTelemetryTableController;
|
return RealtimeTableController;
|
||||||
}
|
}
|
||||||
);
|
);
|
@ -49,13 +49,29 @@ define(
|
|||||||
|
|
||||||
this.$scope = $scope;
|
this.$scope = $scope;
|
||||||
this.domainObject = $scope.domainObject;
|
this.domainObject = $scope.domainObject;
|
||||||
|
this.listeners = [];
|
||||||
|
|
||||||
$scope.columnsForm = {};
|
$scope.columnsForm = {};
|
||||||
|
|
||||||
this.domainObject.getCapability('mutation').listen(function (model) {
|
function unlisten() {
|
||||||
self.populateForm(model);
|
self.listeners.forEach(function (listener) {
|
||||||
|
listener();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.$watch('domainObject', function(domainObject) {
|
||||||
|
unlisten();
|
||||||
|
self.populateForm(domainObject.getModel());
|
||||||
|
|
||||||
|
self.listeners.push(self.domainObject.getCapability('mutation').listen(function (model) {
|
||||||
|
self.populateForm(model);
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maintain a configuration object on scope that stores column
|
||||||
|
* configuration. On change, synchronize with object model.
|
||||||
|
*/
|
||||||
$scope.$watchCollection('configuration.table.columns', function (columns){
|
$scope.$watchCollection('configuration.table.columns', function (columns){
|
||||||
if (columns){
|
if (columns){
|
||||||
self.domainObject.useCapability('mutation', function (model) {
|
self.domainObject.useCapability('mutation', function (model) {
|
||||||
@ -65,6 +81,11 @@ define(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy all mutation listeners
|
||||||
|
*/
|
||||||
|
$scope.$on('$destroy', unlisten);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TableOptionsController.prototype.populateForm = function (model) {
|
TableOptionsController.prototype.populateForm = function (model) {
|
||||||
@ -84,7 +105,7 @@ define(
|
|||||||
'key': key
|
'key': key
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.$scope.configuration = JSON.parse(JSON.stringify(model.configuration));
|
this.$scope.configuration = JSON.parse(JSON.stringify(model.configuration || {}));
|
||||||
};
|
};
|
||||||
|
|
||||||
return TableOptionsController;
|
return TableOptionsController;
|
||||||
|
@ -50,20 +50,15 @@ define(
|
|||||||
this.$scope = $scope;
|
this.$scope = $scope;
|
||||||
this.columns = {}; //Range and Domain columns
|
this.columns = {}; //Range and Domain columns
|
||||||
this.handle = undefined;
|
this.handle = undefined;
|
||||||
//this.pending = false;
|
|
||||||
this.telemetryHandler = telemetryHandler;
|
this.telemetryHandler = telemetryHandler;
|
||||||
this.table = new TableConfiguration($scope.domainObject,
|
this.table = new TableConfiguration($scope.domainObject,
|
||||||
telemetryFormatter);
|
telemetryFormatter);
|
||||||
this.changeListeners = [];
|
this.changeListeners = [];
|
||||||
|
|
||||||
$scope.rows = undefined;
|
$scope.rows = [];
|
||||||
|
|
||||||
// Subscribe to telemetry when a domain object becomes available
|
// Subscribe to telemetry when a domain object becomes available
|
||||||
this.$scope.$watch('domainObject', function(domainObject){
|
this.$scope.$watch('domainObject', function(){
|
||||||
if (!domainObject) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.subscribe();
|
self.subscribe();
|
||||||
self.registerChangeListeners();
|
self.registerChangeListeners();
|
||||||
});
|
});
|
||||||
@ -72,16 +67,24 @@ define(
|
|||||||
this.$scope.$on("$destroy", this.destroy.bind(this));
|
this.$scope.$on("$destroy", this.destroy.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
TelemetryTableController.prototype.unregisterChangeListeners = function () {
|
||||||
|
this.changeListeners.forEach(function (listener) {
|
||||||
|
return listener && listener();
|
||||||
|
});
|
||||||
|
this.changeListeners = [];
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defer registration of change listeners until domain object is
|
* Defer registration of change listeners until domain object is
|
||||||
* available in order to avoid race conditions
|
* available in order to avoid race conditions
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
TelemetryTableController.prototype.registerChangeListeners = function () {
|
TelemetryTableController.prototype.registerChangeListeners = function () {
|
||||||
this.changeListeners.forEach(function (listener) {
|
this.unregisterChangeListeners();
|
||||||
return listener && listener();
|
|
||||||
});
|
|
||||||
this.changeListeners = [];
|
|
||||||
// When composition changes, re-subscribe to the various
|
// When composition changes, re-subscribe to the various
|
||||||
// telemetry subscriptions
|
// telemetry subscriptions
|
||||||
this.changeListeners.push(this.$scope.$watchCollection(
|
this.changeListeners.push(this.$scope.$watchCollection(
|
||||||
@ -102,6 +105,24 @@ define(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function for handling realtime data when it is available. This
|
||||||
|
* will be called by the telemetry framework when new data is
|
||||||
|
* available.
|
||||||
|
*
|
||||||
|
* Method should be overridden by specializing class.
|
||||||
|
*/
|
||||||
|
TelemetryTableController.prototype.addRealtimeData = function () {
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function for handling historical data. Will be called by
|
||||||
|
* telemetry framework when requested historical data is available.
|
||||||
|
* Should be overridden by specializing class.
|
||||||
|
*/
|
||||||
|
TelemetryTableController.prototype.addHistoricalData = function () {
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a new subscription. This can be overridden by children to
|
Create a new subscription. This can be overridden by children to
|
||||||
change default behaviour (which is to retrieve historical telemetry
|
change default behaviour (which is to retrieve historical telemetry
|
||||||
@ -112,13 +133,9 @@ define(
|
|||||||
this.handle.unsubscribe();
|
this.handle.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Noop because not supporting realtime data right now
|
|
||||||
function noop(){
|
|
||||||
}
|
|
||||||
|
|
||||||
this.handle = this.$scope.domainObject && this.telemetryHandler.handle(
|
this.handle = this.$scope.domainObject && this.telemetryHandler.handle(
|
||||||
this.$scope.domainObject,
|
this.$scope.domainObject,
|
||||||
noop,
|
this.addRealtimeData.bind(this),
|
||||||
true // Lossless
|
true // Lossless
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -127,28 +144,6 @@ define(
|
|||||||
this.setup();
|
this.setup();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Populates historical data on scope when it becomes available
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
TelemetryTableController.prototype.addHistoricalData = function () {
|
|
||||||
var rowData = [],
|
|
||||||
self = this;
|
|
||||||
|
|
||||||
this.handle.getTelemetryObjects().forEach(function (telemetryObject){
|
|
||||||
var series = self.handle.getSeries(telemetryObject) || {},
|
|
||||||
pointCount = series.getPointCount ? series.getPointCount() : 0,
|
|
||||||
i = 0;
|
|
||||||
|
|
||||||
for (; i < pointCount; i++) {
|
|
||||||
rowData.push(self.table.getRowValues(telemetryObject,
|
|
||||||
self.handle.makeDatum(telemetryObject, series, i)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$scope.rows = rowData;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup table columns based on domain object metadata
|
* Setup table columns based on domain object metadata
|
||||||
*/
|
*/
|
||||||
@ -159,7 +154,9 @@ define(
|
|||||||
|
|
||||||
if (handle) {
|
if (handle) {
|
||||||
handle.promiseTelemetryObjects().then(function () {
|
handle.promiseTelemetryObjects().then(function () {
|
||||||
table.buildColumns(handle.getMetadata());
|
self.$scope.headers = [];
|
||||||
|
self.$scope.rows = [];
|
||||||
|
table.populateColumns(handle.getMetadata());
|
||||||
|
|
||||||
self.filterColumns();
|
self.filterColumns();
|
||||||
|
|
||||||
@ -173,26 +170,14 @@ define(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @param object The object for which data is available (table may
|
|
||||||
* be composed of multiple objects)
|
|
||||||
* @param datum The data received from the telemetry source
|
|
||||||
*/
|
|
||||||
TelemetryTableController.prototype.updateRows = function (object, datum) {
|
|
||||||
this.$scope.rows.push(this.table.getRowValues(object, datum));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When column configuration changes, update the visible headers
|
* When column configuration changes, update the visible headers
|
||||||
* accordingly.
|
* accordingly.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
TelemetryTableController.prototype.filterColumns = function (columnConfig) {
|
TelemetryTableController.prototype.filterColumns = function () {
|
||||||
if (!columnConfig){
|
var columnConfig = this.table.buildColumnConfiguration();
|
||||||
columnConfig = this.table.getColumnConfiguration();
|
|
||||||
this.table.saveColumnConfiguration(columnConfig);
|
|
||||||
}
|
|
||||||
//Populate headers with visible columns (determined by configuration)
|
//Populate headers with visible columns (determined by configuration)
|
||||||
this.$scope.headers = Object.keys(columnConfig).filter(function (column) {
|
this.$scope.headers = Object.keys(columnConfig).filter(function (column) {
|
||||||
return columnConfig[column];
|
return columnConfig[column];
|
||||||
|
@ -30,6 +30,51 @@ define(
|
|||||||
* Defines a generic 'Table' component. The table can be populated
|
* Defines a generic 'Table' component. The table can be populated
|
||||||
* en-masse by setting the rows attribute, or rows can be added as
|
* en-masse by setting the rows attribute, or rows can be added as
|
||||||
* needed via a broadcast 'addRow' event.
|
* needed via a broadcast 'addRow' event.
|
||||||
|
*
|
||||||
|
* This directive accepts parameters specifying header and row
|
||||||
|
* content, as well as some additional options.
|
||||||
|
*
|
||||||
|
* Two broadcast events for notifying the table that the rows have
|
||||||
|
* changed. For performance reasons, the table does not monitor the
|
||||||
|
* content of `rows` constantly.
|
||||||
|
* - 'add:row': A $broadcast event that will notify the table that
|
||||||
|
* a new row has been added to the table.
|
||||||
|
* eg.
|
||||||
|
* <pre><code>
|
||||||
|
* $scope.rows.push(newRow);
|
||||||
|
* $scope.$broadcast('add:row', $scope.rows.length-1);
|
||||||
|
* </code></pre>
|
||||||
|
* The code above adds a new row, and alerts the table using the
|
||||||
|
* add:row event. Sorting and filtering will be applied
|
||||||
|
* automatically by the table component.
|
||||||
|
*
|
||||||
|
* - 'remove:row': A $broadcast event that will notify the table that a
|
||||||
|
* row should be removed from the table.
|
||||||
|
* eg.
|
||||||
|
* <pre><code>
|
||||||
|
* $scope.rows.slice(5, 1);
|
||||||
|
* $scope.$broadcast('remove:row', 5);
|
||||||
|
* </code></pre>
|
||||||
|
* The code above removes a row from the rows array, and then alerts
|
||||||
|
* the table to its removal.
|
||||||
|
*
|
||||||
|
* @memberof platform/features/table
|
||||||
|
* @param {string[]} headers The column titles to appear at the top
|
||||||
|
* of the table. Corresponding values are specified in the rows
|
||||||
|
* using the header title provided here.
|
||||||
|
* @param {Object[]} rows The row content. Each row is an object
|
||||||
|
* with key-value pairs where the key corresponds to a header
|
||||||
|
* specified in the headers parameter.
|
||||||
|
* @param {boolean} enableFilter If true, values will be searchable
|
||||||
|
* and results filtered
|
||||||
|
* @param {boolean} enableSort If true, sorting will be enabled
|
||||||
|
* allowing sorting by clicking on column headers
|
||||||
|
* @param {boolean} autoScroll If true, table will automatically
|
||||||
|
* scroll to the bottom as new data arrives. Auto-scroll can be
|
||||||
|
* disengaged manually by scrolling away from the bottom of the
|
||||||
|
* table, and can also be enabled manually by scrolling to the bottom of
|
||||||
|
* the table rows.
|
||||||
|
*
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function MCTTable($timeout) {
|
function MCTTable($timeout) {
|
||||||
|
@ -114,10 +114,10 @@ define(
|
|||||||
}];
|
}];
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
table.buildColumns(metadata);
|
table.populateColumns(metadata);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("populates the columns attribute", function() {
|
it("populates columns", function() {
|
||||||
expect(table.columns.length).toBe(5);
|
expect(table.columns.length).toBe(5);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -139,7 +139,7 @@ define(
|
|||||||
|
|
||||||
it("Provides a default configuration with all columns" +
|
it("Provides a default configuration with all columns" +
|
||||||
" visible", function() {
|
" visible", function() {
|
||||||
var configuration = table.getColumnConfiguration();
|
var configuration = table.buildColumnConfiguration();
|
||||||
|
|
||||||
expect(configuration).toBeDefined();
|
expect(configuration).toBeDefined();
|
||||||
expect(Object.keys(configuration).every(function(key){
|
expect(Object.keys(configuration).every(function(key){
|
||||||
@ -158,7 +158,7 @@ define(
|
|||||||
};
|
};
|
||||||
mockModel.configuration = modelConfig;
|
mockModel.configuration = modelConfig;
|
||||||
|
|
||||||
tableConfig = table.getColumnConfiguration();
|
tableConfig = table.buildColumnConfiguration();
|
||||||
|
|
||||||
expect(tableConfig).toBeDefined();
|
expect(tableConfig).toBeDefined();
|
||||||
expect(tableConfig['Range 1']).toBe(false);
|
expect(tableConfig['Range 1']).toBe(false);
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
"../../src/controllers/TelemetryTableController"
|
"../../src/controllers/HistoricalTableController"
|
||||||
],
|
],
|
||||||
function (TableController) {
|
function (TableController) {
|
||||||
|
|
||||||
@ -71,14 +71,14 @@ define(
|
|||||||
|
|
||||||
mockTable = jasmine.createSpyObj('table',
|
mockTable = jasmine.createSpyObj('table',
|
||||||
[
|
[
|
||||||
'buildColumns',
|
'populateColumns',
|
||||||
'getColumnConfiguration',
|
'buildColumnConfiguration',
|
||||||
'getRowValues',
|
'getRowValues',
|
||||||
'saveColumnConfiguration'
|
'saveColumnConfiguration'
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
mockTable.columns = [];
|
mockTable.columns = [];
|
||||||
mockTable.getColumnConfiguration.andReturn(mockConfiguration);
|
mockTable.buildColumnConfiguration.andReturn(mockConfiguration);
|
||||||
|
|
||||||
mockDomainObject= jasmine.createSpyObj('domainObject', [
|
mockDomainObject= jasmine.createSpyObj('domainObject', [
|
||||||
'getCapability',
|
'getCapability',
|
||||||
@ -124,19 +124,18 @@ define(
|
|||||||
expect(mockTelemetryHandle.unsubscribe).toHaveBeenCalled();
|
expect(mockTelemetryHandle.unsubscribe).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('the controller makes use of the table', function () {
|
describe('makes use of the table', function () {
|
||||||
|
|
||||||
it('to create column definitions from telemetry' +
|
it('to create column definitions from telemetry' +
|
||||||
' metadata', function () {
|
' metadata', function () {
|
||||||
controller.setup();
|
controller.setup();
|
||||||
expect(mockTable.buildColumns).toHaveBeenCalled();
|
expect(mockTable.populateColumns).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('to create column configuration, which is written to the' +
|
it('to create column configuration, which is written to the' +
|
||||||
' object model', function () {
|
' object model', function () {
|
||||||
controller.setup();
|
controller.setup();
|
||||||
expect(mockTable.getColumnConfiguration).toHaveBeenCalled();
|
expect(mockTable.buildColumnConfiguration).toHaveBeenCalled();
|
||||||
expect(mockTable.saveColumnConfiguration).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -56,15 +56,18 @@ define(
|
|||||||
|
|
||||||
mockElement = jasmine.createSpyObj('element', [
|
mockElement = jasmine.createSpyObj('element', [
|
||||||
'find',
|
'find',
|
||||||
|
'prop',
|
||||||
'on'
|
'on'
|
||||||
]);
|
]);
|
||||||
mockElement.find.andReturn(mockElement);
|
mockElement.find.andReturn(mockElement);
|
||||||
|
mockElement.prop.andReturn(0);
|
||||||
|
|
||||||
mockScope.displayHeaders = true;
|
mockScope.displayHeaders = true;
|
||||||
mockTimeout = jasmine.createSpy('$timeout');
|
mockTimeout = jasmine.createSpy('$timeout');
|
||||||
mockTimeout.andReturn(promise(undefined));
|
mockTimeout.andReturn(promise(undefined));
|
||||||
|
|
||||||
controller = new MCTTableController(mockScope, mockTimeout, mockElement);
|
controller = new MCTTableController(mockScope, mockTimeout, mockElement);
|
||||||
|
spyOn(controller, 'setVisibleRows');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Reacts to changes to filters, headers, and rows', function() {
|
it('Reacts to changes to filters, headers, and rows', function() {
|
||||||
@ -113,7 +116,7 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Sets rows on scope when rows change', function() {
|
it('Sets rows on scope when rows change', function() {
|
||||||
controller.updateRows(testRows);
|
controller.setRows(testRows);
|
||||||
expect(mockScope.displayRows.length).toBe(3);
|
expect(mockScope.displayRows.length).toBe(3);
|
||||||
expect(mockScope.displayRows).toEqual(testRows);
|
expect(mockScope.displayRows).toEqual(testRows);
|
||||||
});
|
});
|
||||||
@ -125,7 +128,7 @@ define(
|
|||||||
'col2': {'text': 'ghi'},
|
'col2': {'text': 'ghi'},
|
||||||
'col3': {'text': 'row3 col3'}
|
'col3': {'text': 'row3 col3'}
|
||||||
};
|
};
|
||||||
controller.updateRows(testRows);
|
controller.setRows(testRows);
|
||||||
expect(mockScope.displayRows.length).toBe(3);
|
expect(mockScope.displayRows.length).toBe(3);
|
||||||
testRows.push(row4);
|
testRows.push(row4);
|
||||||
addRowFunc(undefined, 3);
|
addRowFunc(undefined, 3);
|
||||||
@ -134,10 +137,8 @@ define(
|
|||||||
|
|
||||||
it('Supports removing rows individually', function() {
|
it('Supports removing rows individually', function() {
|
||||||
var removeRowFunc = mockScope.$on.calls[mockScope.$on.calls.length-1].args[1];
|
var removeRowFunc = mockScope.$on.calls[mockScope.$on.calls.length-1].args[1];
|
||||||
controller.updateRows(testRows);
|
controller.setRows(testRows);
|
||||||
expect(mockScope.displayRows.length).toBe(3);
|
expect(mockScope.displayRows.length).toBe(3);
|
||||||
spyOn(controller, 'setVisibleRows');
|
|
||||||
//controller.setVisibleRows.andReturn(undefined);
|
|
||||||
removeRowFunc(undefined, 2);
|
removeRowFunc(undefined, 2);
|
||||||
expect(mockScope.displayRows.length).toBe(2);
|
expect(mockScope.displayRows.length).toBe(2);
|
||||||
expect(controller.setVisibleRows).toHaveBeenCalled();
|
expect(controller.setVisibleRows).toHaveBeenCalled();
|
||||||
@ -177,7 +178,54 @@ define(
|
|||||||
expect(sortedRows[2].col2.text).toEqual('abc');
|
expect(sortedRows[2].col2.text).toEqual('abc');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Adding new rows', function() {
|
it('correctly sorts rows of differing types', function () {
|
||||||
|
mockScope.sortColumn = 'col2';
|
||||||
|
mockScope.sortDirection = 'desc';
|
||||||
|
|
||||||
|
testRows.push({
|
||||||
|
'col1': {'text': 'row4 col1'},
|
||||||
|
'col2': {'text': '123'},
|
||||||
|
'col3': {'text': 'row4 col3'}
|
||||||
|
});
|
||||||
|
testRows.push({
|
||||||
|
'col1': {'text': 'row5 col1'},
|
||||||
|
'col2': {'text': '456'},
|
||||||
|
'col3': {'text': 'row5 col3'}
|
||||||
|
});
|
||||||
|
testRows.push({
|
||||||
|
'col1': {'text': 'row5 col1'},
|
||||||
|
'col2': {'text': ''},
|
||||||
|
'col3': {'text': 'row5 col3'}
|
||||||
|
});
|
||||||
|
|
||||||
|
sortedRows = controller.sortRows(testRows);
|
||||||
|
expect(sortedRows[0].col2.text).toEqual('ghi');
|
||||||
|
expect(sortedRows[1].col2.text).toEqual('def');
|
||||||
|
expect(sortedRows[2].col2.text).toEqual('abc');
|
||||||
|
|
||||||
|
expect(sortedRows[sortedRows.length-3].col2.text).toEqual('456');
|
||||||
|
expect(sortedRows[sortedRows.length-2].col2.text).toEqual('123');
|
||||||
|
expect(sortedRows[sortedRows.length-1].col2.text).toEqual('');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('The sort comparator', function () {
|
||||||
|
it('Correctly sorts different data types', function () {
|
||||||
|
var val1 = "",
|
||||||
|
val2 = "1",
|
||||||
|
val3 = "2016-04-05 18:41:30.713Z",
|
||||||
|
val4 = "1.1",
|
||||||
|
val5 = "8.945520958175627e-13";
|
||||||
|
mockScope.sortDirection = "asc";
|
||||||
|
|
||||||
|
expect(controller.sortComparator(val1, val2)).toEqual(-1);
|
||||||
|
expect(controller.sortComparator(val3, val1)).toEqual(1);
|
||||||
|
expect(controller.sortComparator(val3, val2)).toEqual(1);
|
||||||
|
expect(controller.sortComparator(val4, val2)).toEqual(1);
|
||||||
|
expect(controller.sortComparator(val2, val5)).toEqual(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Adding new rows', function () {
|
||||||
var row4,
|
var row4,
|
||||||
row5,
|
row5,
|
||||||
row6;
|
row6;
|
||||||
@ -208,20 +256,20 @@ define(
|
|||||||
mockScope.displayRows = controller.sortRows(testRows.slice(0));
|
mockScope.displayRows = controller.sortRows(testRows.slice(0));
|
||||||
|
|
||||||
mockScope.rows.push(row4);
|
mockScope.rows.push(row4);
|
||||||
controller.newRow(undefined, mockScope.rows.length-1);
|
controller.addRow(undefined, mockScope.rows.length-1);
|
||||||
expect(mockScope.displayRows[0].col2.text).toEqual('xyz');
|
expect(mockScope.displayRows[0].col2.text).toEqual('xyz');
|
||||||
|
|
||||||
mockScope.rows.push(row5);
|
mockScope.rows.push(row5);
|
||||||
controller.newRow(undefined, mockScope.rows.length-1);
|
controller.addRow(undefined, mockScope.rows.length-1);
|
||||||
expect(mockScope.displayRows[4].col2.text).toEqual('aaa');
|
expect(mockScope.displayRows[4].col2.text).toEqual('aaa');
|
||||||
|
|
||||||
mockScope.rows.push(row6);
|
mockScope.rows.push(row6);
|
||||||
controller.newRow(undefined, mockScope.rows.length-1);
|
controller.addRow(undefined, mockScope.rows.length-1);
|
||||||
expect(mockScope.displayRows[2].col2.text).toEqual('ggg');
|
expect(mockScope.displayRows[2].col2.text).toEqual('ggg');
|
||||||
|
|
||||||
//Add a duplicate row
|
//Add a duplicate row
|
||||||
mockScope.rows.push(row6);
|
mockScope.rows.push(row6);
|
||||||
controller.newRow(undefined, mockScope.rows.length-1);
|
controller.addRow(undefined, mockScope.rows.length-1);
|
||||||
expect(mockScope.displayRows[2].col2.text).toEqual('ggg');
|
expect(mockScope.displayRows[2].col2.text).toEqual('ggg');
|
||||||
expect(mockScope.displayRows[3].col2.text).toEqual('ggg');
|
expect(mockScope.displayRows[3].col2.text).toEqual('ggg');
|
||||||
});
|
});
|
||||||
@ -237,18 +285,18 @@ define(
|
|||||||
mockScope.displayRows = controller.filterRows(testRows);
|
mockScope.displayRows = controller.filterRows(testRows);
|
||||||
|
|
||||||
mockScope.rows.push(row5);
|
mockScope.rows.push(row5);
|
||||||
controller.newRow(undefined, mockScope.rows.length-1);
|
controller.addRow(undefined, mockScope.rows.length-1);
|
||||||
expect(mockScope.displayRows.length).toBe(2);
|
expect(mockScope.displayRows.length).toBe(2);
|
||||||
expect(mockScope.displayRows[1].col2.text).toEqual('aaa');
|
expect(mockScope.displayRows[1].col2.text).toEqual('aaa');
|
||||||
|
|
||||||
mockScope.rows.push(row6);
|
mockScope.rows.push(row6);
|
||||||
controller.newRow(undefined, mockScope.rows.length-1);
|
controller.addRow(undefined, mockScope.rows.length-1);
|
||||||
expect(mockScope.displayRows.length).toBe(2);
|
expect(mockScope.displayRows.length).toBe(2);
|
||||||
//Row was not added because does not match filter
|
//Row was not added because does not match filter
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Adds new rows at the correct sort position when' +
|
it('Adds new rows at the correct sort position when' +
|
||||||
' not sorted ', function() {
|
' not sorted ', function () {
|
||||||
mockScope.sortColumn = undefined;
|
mockScope.sortColumn = undefined;
|
||||||
mockScope.sortDirection = undefined;
|
mockScope.sortDirection = undefined;
|
||||||
mockScope.filters = {};
|
mockScope.filters = {};
|
||||||
@ -256,14 +304,33 @@ define(
|
|||||||
mockScope.displayRows = testRows.slice(0);
|
mockScope.displayRows = testRows.slice(0);
|
||||||
|
|
||||||
mockScope.rows.push(row5);
|
mockScope.rows.push(row5);
|
||||||
controller.newRow(undefined, mockScope.rows.length-1);
|
controller.addRow(undefined, mockScope.rows.length-1);
|
||||||
expect(mockScope.displayRows[3].col2.text).toEqual('aaa');
|
expect(mockScope.displayRows[3].col2.text).toEqual('aaa');
|
||||||
|
|
||||||
mockScope.rows.push(row6);
|
mockScope.rows.push(row6);
|
||||||
controller.newRow(undefined, mockScope.rows.length-1);
|
controller.addRow(undefined, mockScope.rows.length-1);
|
||||||
expect(mockScope.displayRows[4].col2.text).toEqual('ggg');
|
expect(mockScope.displayRows[4].col2.text).toEqual('ggg');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Resizes columns if length of any columns in new' +
|
||||||
|
' row exceeds corresponding existing column', function() {
|
||||||
|
var row7 = {
|
||||||
|
'col1': {'text': 'row6 col1'},
|
||||||
|
'col2': {'text': 'some longer string'},
|
||||||
|
'col3': {'text': 'row6 col3'}
|
||||||
|
};
|
||||||
|
|
||||||
|
mockScope.sortColumn = undefined;
|
||||||
|
mockScope.sortDirection = undefined;
|
||||||
|
mockScope.filters = {};
|
||||||
|
|
||||||
|
mockScope.displayRows = testRows.slice(0);
|
||||||
|
|
||||||
|
mockScope.rows.push(row7);
|
||||||
|
controller.addRow(undefined, mockScope.rows.length-1);
|
||||||
|
expect(controller.$scope.sizingRow.col2).toEqual({text: 'some longer string'});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
"../../src/controllers/RTTelemetryTableController"
|
"../../src/controllers/RealtimeTableController"
|
||||||
],
|
],
|
||||||
function (TableController) {
|
function (TableController) {
|
||||||
|
|
||||||
@ -75,14 +75,14 @@ define(
|
|||||||
|
|
||||||
mockTable = jasmine.createSpyObj('table',
|
mockTable = jasmine.createSpyObj('table',
|
||||||
[
|
[
|
||||||
'buildColumns',
|
'populateColumns',
|
||||||
'getColumnConfiguration',
|
'buildColumnConfiguration',
|
||||||
'getRowValues',
|
'getRowValues',
|
||||||
'saveColumnConfiguration'
|
'saveColumnConfiguration'
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
mockTable.columns = [];
|
mockTable.columns = [];
|
||||||
mockTable.getColumnConfiguration.andReturn(mockConfiguration);
|
mockTable.buildColumnConfiguration.andReturn(mockConfiguration);
|
||||||
mockTable.getRowValues.andReturn(mockTableRow);
|
mockTable.getRowValues.andReturn(mockTableRow);
|
||||||
|
|
||||||
mockDomainObject= jasmine.createSpyObj('domainObject', [
|
mockDomainObject= jasmine.createSpyObj('domainObject', [
|
||||||
@ -105,13 +105,16 @@ define(
|
|||||||
'unsubscribe',
|
'unsubscribe',
|
||||||
'getDatum',
|
'getDatum',
|
||||||
'promiseTelemetryObjects',
|
'promiseTelemetryObjects',
|
||||||
'getTelemetryObjects'
|
'getTelemetryObjects',
|
||||||
|
'request'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Arbitrary array with non-zero length, contents are not
|
// Arbitrary array with non-zero length, contents are not
|
||||||
// used by mocks
|
// used by mocks
|
||||||
mockTelemetryHandle.getTelemetryObjects.andReturn([{}]);
|
mockTelemetryHandle.getTelemetryObjects.andReturn([{}]);
|
||||||
mockTelemetryHandle.promiseTelemetryObjects.andReturn(promise(undefined));
|
mockTelemetryHandle.promiseTelemetryObjects.andReturn(promise(undefined));
|
||||||
mockTelemetryHandle.getDatum.andReturn({});
|
mockTelemetryHandle.getDatum.andReturn({});
|
||||||
|
mockTelemetryHandle.request.andReturn(promise(undefined));
|
||||||
|
|
||||||
mockTelemetryHandler = jasmine.createSpyObj('telemetryHandler', [
|
mockTelemetryHandler = jasmine.createSpyObj('telemetryHandler', [
|
||||||
'handle'
|
'handle'
|
@ -37,18 +37,36 @@ define(
|
|||||||
'listen'
|
'listen'
|
||||||
]);
|
]);
|
||||||
mockDomainObject = jasmine.createSpyObj('domainObject', [
|
mockDomainObject = jasmine.createSpyObj('domainObject', [
|
||||||
'getCapability'
|
'getCapability',
|
||||||
|
'getModel'
|
||||||
]);
|
]);
|
||||||
mockDomainObject.getCapability.andReturn(mockCapability);
|
mockDomainObject.getCapability.andReturn(mockCapability);
|
||||||
|
mockDomainObject.getModel.andReturn({});
|
||||||
|
|
||||||
mockScope = jasmine.createSpyObj('scope', [
|
mockScope = jasmine.createSpyObj('scope', [
|
||||||
'$watchCollection'
|
'$watchCollection',
|
||||||
|
'$watch',
|
||||||
|
'$on'
|
||||||
]);
|
]);
|
||||||
mockScope.domainObject = mockDomainObject;
|
mockScope.domainObject = mockDomainObject;
|
||||||
|
|
||||||
controller = new TableOptionsController(mockScope);
|
controller = new TableOptionsController(mockScope);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Listens for changing domain object', function() {
|
||||||
|
expect(mockScope.$watch).toHaveBeenCalledWith('domainObject', jasmine.any(Function));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('On destruction of controller, destroys listeners', function() {
|
||||||
|
var unlistenFunc = jasmine.createSpy("unlisten");
|
||||||
|
controller.listeners.push(unlistenFunc);
|
||||||
|
expect(mockScope.$on).toHaveBeenCalledWith('$destroy', jasmine.any(Function));
|
||||||
|
mockScope.$on.mostRecentCall.args[1]();
|
||||||
|
expect(unlistenFunc).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it('Registers a listener for mutation events on the object', function() {
|
it('Registers a listener for mutation events on the object', function() {
|
||||||
|
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||||
expect(mockCapability.listen).toHaveBeenCalled();
|
expect(mockCapability.listen).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
2
platform/persistence/cache/README.md
vendored
2
platform/persistence/cache/README.md
vendored
@ -1,2 +0,0 @@
|
|||||||
This bundle introduces a persistence cache to Open MCT Web to
|
|
||||||
limit the number of persistence requests issued by the platform.
|
|
@ -1,161 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This bundle decorates the persistence service to maintain a local cache
|
|
||||||
* of persisted documents.
|
|
||||||
* @namespace platform/persistence/cache
|
|
||||||
*/
|
|
||||||
define(
|
|
||||||
[],
|
|
||||||
function () {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A caching persistence decorator maintains local copies of persistent objects
|
|
||||||
* that have been loaded, and keeps them in sync after writes. This allows
|
|
||||||
* retrievals to occur more quickly after the first load.
|
|
||||||
*
|
|
||||||
* @memberof platform/persistence/cache
|
|
||||||
* @constructor
|
|
||||||
* @param {string[]} cacheSpaces persistence space names which
|
|
||||||
* should be cached
|
|
||||||
* @param {PersistenceService} persistenceService the service which
|
|
||||||
* implements object persistence, whose inputs/outputs
|
|
||||||
* should be cached.
|
|
||||||
* @implements {PersistenceService}
|
|
||||||
*/
|
|
||||||
function CachingPersistenceDecorator(cacheSpaces, persistenceService) {
|
|
||||||
var spaces = cacheSpaces || [], // List of spaces to cache
|
|
||||||
cache = {}; // Where objects will be stored
|
|
||||||
|
|
||||||
// Arrayify list of spaces to cache, if necessary.
|
|
||||||
spaces = Array.isArray(spaces) ? spaces : [ spaces ];
|
|
||||||
|
|
||||||
// Initialize caches
|
|
||||||
spaces.forEach(function (space) {
|
|
||||||
cache[space] = {};
|
|
||||||
});
|
|
||||||
|
|
||||||
this.spaces = spaces;
|
|
||||||
this.cache = cache;
|
|
||||||
this.persistenceService = persistenceService;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap as a thenable; used instead of $q.when because that
|
|
||||||
// will resolve on a future tick, which can cause latency
|
|
||||||
// issues (which this decorator is intended to address.)
|
|
||||||
function fastPromise(value) {
|
|
||||||
return {
|
|
||||||
then: function (callback) {
|
|
||||||
return fastPromise(callback(value));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the cached instance of an object to a new value
|
|
||||||
function replaceValue(valueHolder, newValue) {
|
|
||||||
var v = valueHolder.value;
|
|
||||||
|
|
||||||
// If it's a JS object, we want to replace contents, so that
|
|
||||||
// everybody gets the same instance.
|
|
||||||
if (typeof v === 'object' && v !== null) {
|
|
||||||
// Only update contents if these are different instances
|
|
||||||
if (v !== newValue) {
|
|
||||||
// Clear prior contents
|
|
||||||
Object.keys(v).forEach(function (k) {
|
|
||||||
delete v[k];
|
|
||||||
});
|
|
||||||
// Shallow-copy contents
|
|
||||||
Object.keys(newValue).forEach(function (k) {
|
|
||||||
v[k] = newValue[k];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Otherwise, just store the new value
|
|
||||||
valueHolder.value = newValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Place value in the cache for space, if there is one.
|
|
||||||
CachingPersistenceDecorator.prototype.addToCache = function (space, key, value) {
|
|
||||||
var cache = this.cache;
|
|
||||||
if (cache[space]) {
|
|
||||||
if (cache[space][key]) {
|
|
||||||
replaceValue(cache[space][key], value);
|
|
||||||
} else {
|
|
||||||
cache[space][key] = { value: value };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create a function for putting value into a cache;
|
|
||||||
// useful for then-chaining.
|
|
||||||
CachingPersistenceDecorator.prototype.putCache = function (space, key) {
|
|
||||||
var self = this;
|
|
||||||
return function (value) {
|
|
||||||
self.addToCache(space, key, value);
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CachingPersistenceDecorator.prototype.listSpaces = function () {
|
|
||||||
return this.persistenceService.listSpaces();
|
|
||||||
};
|
|
||||||
|
|
||||||
CachingPersistenceDecorator.prototype.listObjects = function (space) {
|
|
||||||
return this.persistenceService.listObjects(space);
|
|
||||||
};
|
|
||||||
|
|
||||||
CachingPersistenceDecorator.prototype.createObject = function (space, key, value) {
|
|
||||||
this.addToCache(space, key, value);
|
|
||||||
return this.persistenceService.createObject(space, key, value);
|
|
||||||
};
|
|
||||||
|
|
||||||
CachingPersistenceDecorator.prototype.readObject = function (space, key) {
|
|
||||||
var cache = this.cache;
|
|
||||||
return (cache[space] && cache[space][key]) ?
|
|
||||||
fastPromise(cache[space][key].value) :
|
|
||||||
this.persistenceService.readObject(space, key)
|
|
||||||
.then(this.putCache(space, key));
|
|
||||||
};
|
|
||||||
|
|
||||||
CachingPersistenceDecorator.prototype.updateObject = function (space, key, value) {
|
|
||||||
var self = this;
|
|
||||||
return this.persistenceService.updateObject(space, key, value)
|
|
||||||
.then(function (result) {
|
|
||||||
self.addToCache(space, key, value);
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
CachingPersistenceDecorator.prototype.deleteObject = function (space, key, value) {
|
|
||||||
if (this.cache[space]) {
|
|
||||||
delete this.cache[space][key];
|
|
||||||
}
|
|
||||||
return this.persistenceService.deleteObject(space, key, value);
|
|
||||||
};
|
|
||||||
|
|
||||||
return CachingPersistenceDecorator;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,142 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
["../src/CachingPersistenceDecorator"],
|
|
||||||
function (CachingPersistenceDecorator) {
|
|
||||||
|
|
||||||
var PERSISTENCE_METHODS = [
|
|
||||||
"listSpaces",
|
|
||||||
"listObjects",
|
|
||||||
"createObject",
|
|
||||||
"readObject",
|
|
||||||
"updateObject",
|
|
||||||
"deleteObject"
|
|
||||||
];
|
|
||||||
|
|
||||||
describe("The caching persistence decorator", function () {
|
|
||||||
var testSpace,
|
|
||||||
mockPersistence,
|
|
||||||
mockCallback,
|
|
||||||
decorator;
|
|
||||||
|
|
||||||
function mockPromise(value) {
|
|
||||||
return {
|
|
||||||
then: function (callback) {
|
|
||||||
return mockPromise(callback(value));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
|
|
||||||
|
|
||||||
testSpace = "TEST";
|
|
||||||
mockPersistence = jasmine.createSpyObj(
|
|
||||||
"persistenceService",
|
|
||||||
PERSISTENCE_METHODS
|
|
||||||
);
|
|
||||||
mockCallback = jasmine.createSpy("callback");
|
|
||||||
|
|
||||||
PERSISTENCE_METHODS.forEach(function (m) {
|
|
||||||
mockPersistence[m].andReturn(mockPromise({
|
|
||||||
method: m
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
decorator = new CachingPersistenceDecorator(
|
|
||||||
testSpace,
|
|
||||||
mockPersistence
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("delegates all methods", function () {
|
|
||||||
PERSISTENCE_METHODS.forEach(function (m) {
|
|
||||||
// Reset the callback
|
|
||||||
mockCallback = jasmine.createSpy("callback");
|
|
||||||
// Invoke the method; avoid using a key that will be cached
|
|
||||||
decorator[m](testSpace, "testKey" + m, "testValue")
|
|
||||||
.then(mockCallback);
|
|
||||||
// Should have gotten that method's plain response
|
|
||||||
expect(mockCallback).toHaveBeenCalledWith({ method: m });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does not repeat reads of cached objects", function () {
|
|
||||||
// Perform two reads
|
|
||||||
decorator.readObject(testSpace, "someKey", "someValue")
|
|
||||||
.then(mockCallback);
|
|
||||||
decorator.readObject(testSpace, "someKey", "someValue")
|
|
||||||
.then(mockCallback);
|
|
||||||
|
|
||||||
// Should have only delegated once
|
|
||||||
expect(mockPersistence.readObject.calls.length).toEqual(1);
|
|
||||||
|
|
||||||
// But both promises should have resolved
|
|
||||||
expect(mockCallback.calls.length).toEqual(2);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it("gives a single instance of cached objects", function () {
|
|
||||||
// Perform two reads
|
|
||||||
decorator.readObject(testSpace, "someKey", "someValue")
|
|
||||||
.then(mockCallback);
|
|
||||||
decorator.readObject(testSpace, "someKey", "someValue")
|
|
||||||
.then(mockCallback);
|
|
||||||
|
|
||||||
// Results should have been pointer-identical
|
|
||||||
expect(mockCallback.calls[0].args[0])
|
|
||||||
.toBe(mockCallback.calls[1].args[0]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("maintains the same cached instance between reads/writes", function () {
|
|
||||||
var testObject = { abc: "XYZ!" };
|
|
||||||
|
|
||||||
// Perform two reads with a write in between
|
|
||||||
decorator.readObject(testSpace, "someKey", "someValue")
|
|
||||||
.then(mockCallback);
|
|
||||||
decorator.updateObject(testSpace, "someKey", testObject);
|
|
||||||
decorator.readObject(testSpace, "someKey", "someValue")
|
|
||||||
.then(mockCallback);
|
|
||||||
|
|
||||||
// Results should have been pointer-identical
|
|
||||||
expect(mockCallback.calls[0].args[0])
|
|
||||||
.toBe(mockCallback.calls[1].args[0]);
|
|
||||||
|
|
||||||
// But contents should have been equal to the written object
|
|
||||||
expect(mockCallback).toHaveBeenCalledWith(testObject);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("is capable of reading/writing strings", function () {
|
|
||||||
// Efforts made to keep cached objects pointer-identical
|
|
||||||
// would break on strings - so make sure cache isn't
|
|
||||||
// breaking when we read/write strings.
|
|
||||||
decorator.createObject(testSpace, "someKey", "someValue");
|
|
||||||
decorator.updateObject(testSpace, "someKey", "someOtherValue");
|
|
||||||
decorator.readObject(testSpace, "someKey").then(mockCallback);
|
|
||||||
expect(mockCallback).toHaveBeenCalledWith("someOtherValue");
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
@ -97,9 +97,7 @@ define([
|
|||||||
"implementation": ContextMenuGesture,
|
"implementation": ContextMenuGesture,
|
||||||
"depends": [
|
"depends": [
|
||||||
"$timeout",
|
"$timeout",
|
||||||
"$parse",
|
"agentService"
|
||||||
"agentService",
|
|
||||||
"navigationService"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -39,32 +39,16 @@ define(
|
|||||||
* in the context menu will be performed
|
* in the context menu will be performed
|
||||||
* @implements {Gesture}
|
* @implements {Gesture}
|
||||||
*/
|
*/
|
||||||
function ContextMenuGesture($timeout, $parse, agentService, navigationService, element, domainObject) {
|
function ContextMenuGesture($timeout, agentService, element, domainObject) {
|
||||||
var isPressing,
|
var isPressing,
|
||||||
longTouchTime = 500,
|
longTouchTime = 500;
|
||||||
parameters = element && element.attr('parameters') && $parse(element.attr('parameters'))();
|
|
||||||
|
|
||||||
function suppressMenu() {
|
|
||||||
return parameters &&
|
|
||||||
parameters.suppressMenuOnEdit &&
|
|
||||||
navigationService.getNavigation() &&
|
|
||||||
navigationService.getNavigation().hasCapability('editor');
|
|
||||||
}
|
|
||||||
|
|
||||||
function showMenu(event) {
|
function showMenu(event) {
|
||||||
/**
|
domainObject.getCapability('action').perform({
|
||||||
* Some menu items should have the context menu action
|
key: 'menu',
|
||||||
* suppressed (eg. the navigation menu on the left)
|
domainObject: domainObject,
|
||||||
*/
|
event: event
|
||||||
if (suppressMenu()){
|
});
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
domainObject.getCapability('action').perform({
|
|
||||||
key: 'menu',
|
|
||||||
domainObject: domainObject,
|
|
||||||
event: event
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// When context menu event occurs, show object actions instead
|
// When context menu event occurs, show object actions instead
|
||||||
|
@ -28,16 +28,14 @@ define(
|
|||||||
["../../src/gestures/ContextMenuGesture"],
|
["../../src/gestures/ContextMenuGesture"],
|
||||||
function (ContextMenuGesture) {
|
function (ContextMenuGesture) {
|
||||||
|
|
||||||
var JQLITE_FUNCTIONS = [ "on", "off", "find", "append", "remove", "attr" ],
|
var JQLITE_FUNCTIONS = [ "on", "off", "find", "append", "remove" ],
|
||||||
DOMAIN_OBJECT_METHODS = [ "getId", "getModel", "getCapability", "hasCapability", "useCapability"];
|
DOMAIN_OBJECT_METHODS = [ "getId", "getModel", "getCapability", "hasCapability", "useCapability"];
|
||||||
|
|
||||||
|
|
||||||
describe("The 'context menu' gesture", function () {
|
describe("The 'context menu' gesture", function () {
|
||||||
var mockTimeout,
|
var mockTimeout,
|
||||||
mockParse,
|
|
||||||
mockElement,
|
mockElement,
|
||||||
mockAgentService,
|
mockAgentService,
|
||||||
mockNavigationService,
|
|
||||||
mockDomainObject,
|
mockDomainObject,
|
||||||
mockEvent,
|
mockEvent,
|
||||||
mockTouchEvent,
|
mockTouchEvent,
|
||||||
@ -51,7 +49,6 @@ define(
|
|||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
mockTimeout = jasmine.createSpy("$timeout");
|
mockTimeout = jasmine.createSpy("$timeout");
|
||||||
mockParse = jasmine.createSpy("$parse");
|
|
||||||
mockElement = jasmine.createSpyObj("element", JQLITE_FUNCTIONS);
|
mockElement = jasmine.createSpyObj("element", JQLITE_FUNCTIONS);
|
||||||
mockAgentService = jasmine.createSpyObj("agentService", ["isMobile"]);
|
mockAgentService = jasmine.createSpyObj("agentService", ["isMobile"]);
|
||||||
mockDomainObject = jasmine.createSpyObj("domainObject", DOMAIN_OBJECT_METHODS);
|
mockDomainObject = jasmine.createSpyObj("domainObject", DOMAIN_OBJECT_METHODS);
|
||||||
@ -60,17 +57,14 @@ define(
|
|||||||
"action",
|
"action",
|
||||||
[ "perform", "getActions" ]
|
[ "perform", "getActions" ]
|
||||||
);
|
);
|
||||||
mockActionContext = jasmine.createSpyObj(
|
|
||||||
"actionContext",
|
|
||||||
[ "" ]
|
|
||||||
);
|
|
||||||
|
|
||||||
mockActionContext = {domainObject: mockDomainObject, event: mockEvent};
|
mockActionContext = {domainObject: mockDomainObject, event: mockEvent};
|
||||||
mockDomainObject.getCapability.andReturn(mockContextMenuAction);
|
mockDomainObject.getCapability.andReturn(mockContextMenuAction);
|
||||||
mockContextMenuAction.perform.andReturn(jasmine.any(Function));
|
mockContextMenuAction.perform.andReturn(jasmine.any(Function));
|
||||||
mockAgentService.isMobile.andReturn(false);
|
mockAgentService.isMobile.andReturn(false);
|
||||||
|
|
||||||
gesture = new ContextMenuGesture(mockTimeout, mockParse, mockAgentService, mockNavigationService, mockElement, mockDomainObject);
|
|
||||||
|
gesture = new ContextMenuGesture(mockTimeout, mockAgentService, mockElement, mockDomainObject);
|
||||||
|
|
||||||
// Capture the contextmenu callback
|
// Capture the contextmenu callback
|
||||||
fireGesture = mockElement.on.mostRecentCall.args[1];
|
fireGesture = mockElement.on.mostRecentCall.args[1];
|
||||||
@ -106,7 +100,7 @@ define(
|
|||||||
mockAgentService.isMobile.andReturn(true);
|
mockAgentService.isMobile.andReturn(true);
|
||||||
|
|
||||||
// Then create new (mobile) gesture
|
// Then create new (mobile) gesture
|
||||||
gesture = new ContextMenuGesture(mockTimeout, mockParse, mockAgentService, mockNavigationService, mockElement, mockDomainObject);
|
gesture = new ContextMenuGesture(mockTimeout, mockAgentService, mockElement, mockDomainObject);
|
||||||
|
|
||||||
// Set calls for the touchstart and touchend gestures
|
// Set calls for the touchstart and touchend gestures
|
||||||
fireTouchStartGesture = mockElement.on.calls[1].args[1];
|
fireTouchStartGesture = mockElement.on.calls[1].args[1];
|
||||||
|
@ -23,7 +23,6 @@
|
|||||||
define([
|
define([
|
||||||
"./src/controllers/SearchController",
|
"./src/controllers/SearchController",
|
||||||
"./src/controllers/SearchMenuController",
|
"./src/controllers/SearchMenuController",
|
||||||
"./src/controllers/ClickAwayController",
|
|
||||||
"./src/services/GenericSearchProvider",
|
"./src/services/GenericSearchProvider",
|
||||||
"./src/services/SearchAggregator",
|
"./src/services/SearchAggregator",
|
||||||
"text!./res/templates/search-item.html",
|
"text!./res/templates/search-item.html",
|
||||||
@ -33,7 +32,6 @@ define([
|
|||||||
], function (
|
], function (
|
||||||
SearchController,
|
SearchController,
|
||||||
SearchMenuController,
|
SearchMenuController,
|
||||||
ClickAwayController,
|
|
||||||
GenericSearchProvider,
|
GenericSearchProvider,
|
||||||
SearchAggregator,
|
SearchAggregator,
|
||||||
searchItemTemplate,
|
searchItemTemplate,
|
||||||
@ -71,14 +69,6 @@ define([
|
|||||||
"$scope",
|
"$scope",
|
||||||
"types[]"
|
"types[]"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "ClickAwayController",
|
|
||||||
"implementation": ClickAwayController,
|
|
||||||
"depends": [
|
|
||||||
"$scope",
|
|
||||||
"$document"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"representations": [
|
"representations": [
|
||||||
|
@ -1,103 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Copied from the ClickAwayController in platform/commonUI/general
|
|
||||||
*/
|
|
||||||
|
|
||||||
define(
|
|
||||||
[],
|
|
||||||
function () {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A ClickAwayController is used to toggle things (such as context
|
|
||||||
* menus) where clicking elsewhere in the document while the toggle
|
|
||||||
* is in an active state is intended to dismiss the toggle.
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param $scope the scope in which this controller is active
|
|
||||||
* @param $document the document element, injected by Angular
|
|
||||||
*/
|
|
||||||
function ClickAwayController($scope, $document) {
|
|
||||||
var state = false,
|
|
||||||
clickaway;
|
|
||||||
|
|
||||||
// Track state, but also attach and detach a listener for
|
|
||||||
// mouseup events on the document.
|
|
||||||
function deactivate() {
|
|
||||||
state = false;
|
|
||||||
$document.off("mouseup", clickaway);
|
|
||||||
}
|
|
||||||
|
|
||||||
function activate() {
|
|
||||||
state = true;
|
|
||||||
$document.on("mouseup", clickaway);
|
|
||||||
}
|
|
||||||
|
|
||||||
function changeState() {
|
|
||||||
if (state) {
|
|
||||||
deactivate();
|
|
||||||
} else {
|
|
||||||
activate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callback used by the document listener. Deactivates;
|
|
||||||
// note also $scope.$apply is invoked to indicate that
|
|
||||||
// the state of this controller has changed.
|
|
||||||
clickaway = function () {
|
|
||||||
deactivate();
|
|
||||||
$scope.$apply();
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* Get the current state of the toggle.
|
|
||||||
* @return {boolean} true if active
|
|
||||||
*/
|
|
||||||
isActive: function () {
|
|
||||||
return state;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Set a new state for the toggle.
|
|
||||||
* @return {boolean} true to activate
|
|
||||||
*/
|
|
||||||
setState: function (newState) {
|
|
||||||
if (state !== newState) {
|
|
||||||
changeState();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Toggle the current state; activate if it is inactive,
|
|
||||||
* deactivate if it is active.
|
|
||||||
*/
|
|
||||||
toggle: function () {
|
|
||||||
changeState();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return ClickAwayController;
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,92 +0,0 @@
|
|||||||
/*****************************************************************************
|
|
||||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
|
||||||
* as represented by the Administrator of the National Aeronautics and Space
|
|
||||||
* Administration. All rights reserved.
|
|
||||||
*
|
|
||||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*
|
|
||||||
* Open MCT Web includes source code licensed under additional open source
|
|
||||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
|
||||||
* this source code distribution or the Licensing information page available
|
|
||||||
* at runtime from the About dialog for additional information.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
define(
|
|
||||||
["../../src/controllers/ClickAwayController"],
|
|
||||||
function (ClickAwayController) {
|
|
||||||
|
|
||||||
describe("The click-away controller", function () {
|
|
||||||
var mockScope,
|
|
||||||
mockDocument,
|
|
||||||
controller;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
mockScope = jasmine.createSpyObj(
|
|
||||||
"$scope",
|
|
||||||
[ "$apply" ]
|
|
||||||
);
|
|
||||||
mockDocument = jasmine.createSpyObj(
|
|
||||||
"$document",
|
|
||||||
[ "on", "off" ]
|
|
||||||
);
|
|
||||||
controller = new ClickAwayController(mockScope, mockDocument);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("is initially inactive", function () {
|
|
||||||
expect(controller.isActive()).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does not listen to the document before being toggled", function () {
|
|
||||||
expect(mockDocument.on).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("tracks enabled/disabled state when toggled", function () {
|
|
||||||
controller.toggle();
|
|
||||||
expect(controller.isActive()).toBe(true);
|
|
||||||
controller.toggle();
|
|
||||||
expect(controller.isActive()).toBe(false);
|
|
||||||
controller.toggle();
|
|
||||||
expect(controller.isActive()).toBe(true);
|
|
||||||
controller.toggle();
|
|
||||||
expect(controller.isActive()).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("allows active state to be explictly specified", function () {
|
|
||||||
controller.setState(true);
|
|
||||||
expect(controller.isActive()).toBe(true);
|
|
||||||
controller.setState(true);
|
|
||||||
expect(controller.isActive()).toBe(true);
|
|
||||||
controller.setState(false);
|
|
||||||
expect(controller.isActive()).toBe(false);
|
|
||||||
controller.setState(false);
|
|
||||||
expect(controller.isActive()).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("registers a mouse listener when activated", function () {
|
|
||||||
controller.setState(true);
|
|
||||||
expect(mockDocument.on).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("deactivates and detaches listener on document click", function () {
|
|
||||||
var callback;
|
|
||||||
controller.setState(true);
|
|
||||||
callback = mockDocument.on.mostRecentCall.args[1];
|
|
||||||
callback();
|
|
||||||
expect(controller.isActive()).toEqual(false);
|
|
||||||
expect(mockDocument.off).toHaveBeenCalledWith("mouseup", callback);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
Loading…
x
Reference in New Issue
Block a user