diff --git a/.gitignore b/.gitignore index 26932aec64..217d51a4c9 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,5 @@ node_modules # Protractor logs protractor/logs +# npm-debug log +npm-debug.log diff --git a/docs/src/architecture/Framework.md b/docs/src/architecture/framework.md similarity index 100% rename from docs/src/architecture/Framework.md rename to docs/src/architecture/framework.md diff --git a/docs/src/architecture/index.md b/docs/src/architecture/index.md index fd9e0961e1..7354402d0c 100644 --- a/docs/src/architecture/index.md +++ b/docs/src/architecture/index.md @@ -58,13 +58,13 @@ of the software. These layers are: -* [_Framework_](Framework.md): The framework layer is responsible for +* [_Framework_](framework.md): The framework layer is responsible for managing the interactions between application components. It has no application-specific knowledge; at this layer, we have only established an abstraction by which different software components may communicate and/or interact. -* [_Platform_](Platform.md): The platform layer defines the general look, feel, and - behavior of Open MCT Web. This includes user-facing components like +* [_Platform_](platform.md): The platform layer defines the general look, + feel, and behavior of Open MCT Web. This includes user-facing components like Browse mode and Edit mode, as well as underlying elements of the information model and the general service infrastructure. * _Application_: The application layer defines specific features of diff --git a/docs/src/architecture/Platform.md b/docs/src/architecture/platform.md similarity index 100% rename from docs/src/architecture/Platform.md rename to docs/src/architecture/platform.md diff --git a/docs/src/guide/index.md b/docs/src/guide/index.md index 01896ecfc5..a0159ec672 100644 --- a/docs/src/guide/index.md +++ b/docs/src/guide/index.md @@ -3,8 +3,8 @@ Victor Woeltjen [victor.woeltjen@nasa.gov](mailto:victor.woeltjen@nasa.gov) -September 23, 2015 -Document Version 1.1 +September 23, 2015 +Document Version 1.1 Date | Version | Summary of Changes | Author ------------------- | --------- | ----------------------- | --------------- @@ -20,9 +20,9 @@ MCT Web platform. ## What is Open MCT Web Open MCT Web is a platform for building user interface and display tools, 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 -[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 +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 +create single-page web applications which integrate data and behavior from a variety of sources and domains. Open MCT Web has been developed to support the remote operation of space @@ -36,9 +36,9 @@ Open MCT Web provides: * 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 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 -features of a variety of types. +features of a variety of types. * A set of general-purpose object types and visualizations, as well as some visualizations and infrastructure specific to telemetry display. @@ -70,7 +70,7 @@ Web should behave. ### Technologies Open MCT Web 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 good understanding of Angular is recommended for developers working with Open MCT Web. @@ -90,9 +90,9 @@ chapter) within that directory. To initially clone the Open MCT Web repository: `git clone -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 Web: - cd + cd git checkout open-master git checkout -b @@ -120,7 +120,7 @@ plugins) but to collaborate at run-time. Open MCT Web's framework layer is implemented on top of AngularJS's [dependency 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. ## Framework Overview @@ -129,7 +129,7 @@ The framework's role in the application is to manage connections between bundles. All application-specific behavior is provided by individual bundles, or as the result of their collaboration. -The framework is described in more detail in the [Framework Overview](../architecture/Framework.md#Overview) of the +The framework is described in more detail in the [Framework Overview](../architecture/framework.md#overview) of the architecture guide. ### Tiers @@ -186,16 +186,17 @@ as well as the framework layer's role in mediating between these components. Once the framework layer has wired these software components together, however, the application's logical architecture emerges. -An overview of the logical architecture of the platform is given in the [Platform Architecture](../architecture/Platform.md#PlatformArchitecture) +An overview of the logical architecture of the platform is given in the +[Platform Architecture](../architecture/platform.md#platform-architecture) section of the Platform guide ### Web Services -As mentioned in the Introduction, Open MCT Web is a platform single-page -applications which runs entirely in the browser. Most applications will want to -additionally interact with server-side resources, to (for example) read -telemetry data or store user-created objects. This interaction is handled by -individual bundles using APIs which are supported in browser (such as +As mentioned in the Introduction, Open MCT Web is a platform single-page +applications which runs entirely in the browser. Most applications will want to +additionally interact with server-side resources, to (for example) read +telemetry data or store user-created objects. This interaction is handled by +individual bundles using APIs which are supported in browser (such as `XMLHttpRequest`, typically wrapped by Angular's `$http`.) ```nomnoml @@ -218,213 +219,213 @@ individual bundles using APIs which are supported in browser (such as ] ``` -This architectural approach ensures a loose coupling between applications built -using Open MCT Web and the backends which support them. - -### Glossary - -Certain terms are used throughout Open MCT Web with consistent meanings or -conventions. Other developer documentation, particularly in-line documentation, +This architectural approach ensures a loose coupling between applications built +using Open MCT Web and the backends which support them. + +### Glossary + +Certain terms are used throughout Open MCT Web with consistent meanings or +conventions. Other developer documentation, particularly in-line documentation, may presume an understanding of these terms. -* __bundle__: A bundle is a removable, reusable grouping of software elements. +* __bundle__: A bundle is a removable, reusable grouping of software elements. The application is composed of bundles. Plug-ins are bundles. -* __capability__: A JavaScript object which exposes dynamic behavior or +* __capability__: A JavaScript object which exposes dynamic behavior or non-persistent state associated with a domain object. -* __category__: A machine-readable identifier for a group that something may +* __category__: A machine-readable identifier for a group that something may belong to. -* __composition __: In the context of a domain object, this refers to the set of -other domain objects that compose or are contained by that object. A domain +* __composition__: In the context of a domain object, this refers to the set of +other domain objects that compose or are contained by that object. A domain object's composition is the set of domain objects that should appear immediately - beneath it in a tree hierarchy. A domain object's composition is described in -its model as an array of identifiers; its composition capability provides a -means to retrieve the actual domain object instances associated with these -identifiers asynchronously. + beneath it in a tree hierarchy. A domain object's composition is described in +its model as an array of identifiers; its composition capability provides a +means to retrieve the actual domain object instances associated with these +identifiers asynchronously. * __description__: When used as an object property, this refers to the human- -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 -similar application-specific objects.) -* __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 -domain object. -* __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 -distinguish types of extensions from specific extension instances. -* __id__: A string which uniquely identifies a domain object. -* __key__: When used as an object property, this refers to the machine-readable -identifier for a specific thing in a set of things. (Most often used in the -context of extensions or other similar application-specific object sets.) This -term is chosen to avoid attaching ambiguous meanings to 'id'. -* __model__: The persistent state associated with a domain object. A domain -object's model is a JavaScript object which can be converted to JSON without -losing information (that is, it contains no methods.) -* __name__: When used as an object property, this refers to the human-readable -name for a thing. (Most often used in the context of extensions, domain object -models, or other similar application-specific objects.) -* __navigation__: Refers to the current state of the application with respect to -the user's expressed interest in a specific domain object; e.g. when a user -clicks on a domain object in the tree, they are navigating to it, and it is -thereafter considered the navigated object (until the user makes another such -choice.) This term is used to distinguish navigation from selection, which -occurs in an editing context. -* __space__: A machine-readable name used to identify a persistence store. -Interactions with persistence with generally involve a space parameter in some -form, to distinguish multiple persistence stores from one another (for cases -where there are multiple valid persistence locations available.) -* __source__: A machine-readable name used to identify a source of telemetry -data. Similar to "space", this allows multiple telemetry sources to operate -side-by-side without conflicting. +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 +similar application-specific objects.) +* __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 +domain object. +* __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 +distinguish types of extensions from specific extension instances. +* __id__: A string which uniquely identifies a domain object. +* __key__: When used as an object property, this refers to the machine-readable +identifier for a specific thing in a set of things. (Most often used in the +context of extensions or other similar application-specific object sets.) This +term is chosen to avoid attaching ambiguous meanings to 'id'. +* __model__: The persistent state associated with a domain object. A domain +object's model is a JavaScript object which can be converted to JSON without +losing information (that is, it contains no methods.) +* __name__: When used as an object property, this refers to the human-readable +name for a thing. (Most often used in the context of extensions, domain object +models, or other similar application-specific objects.) +* __navigation__: Refers to the current state of the application with respect to +the user's expressed interest in a specific domain object; e.g. when a user +clicks on a domain object in the tree, they are navigating to it, and it is +thereafter considered the navigated object (until the user makes another such +choice.) This term is used to distinguish navigation from selection, which +occurs in an editing context. +* __space__: A machine-readable name used to identify a persistence store. +Interactions with persistence with generally involve a space parameter in some +form, to distinguish multiple persistence stores from one another (for cases +where there are multiple valid persistence locations available.) +* __source__: A machine-readable name used to identify a source of telemetry +data. Similar to "space", this allows multiple telemetry sources to operate +side-by-side without conflicting. # Framework + +Open MCT Web is built on the [AngularJS framework]( http://www.angularjs.org ). A +good understanding of that framework is recommended. -Open MCT Web is built on the [AngularJS framework]( http://www.angularjs.org ). A -good understanding of that framework is recommended. +Open MCT Web adds an extra layer on top of AngularJS to (a) generalize its +dependency injection mechanism slightly, particularly to handle many-to-one +relationships; and (b) handle script loading. Combined, these features become a +plugin mechanism. + +This framework layer operates on two key concepts: -Open MCT Web adds an extra layer on top of AngularJS to (a) generalize its -dependency injection mechanism slightly, particularly to handle many-to-one -relationships; and (b) handle script loading. Combined, these features become a -plugin mechanism. - -This framework layer operates on two key concepts: - -* __Bundle:__ A bundle is a collection of related functionality that can be -added to the application as a group. More concretely, a bundle is a directory -containing a JSON file declaring its contents, as well as JavaScript sources, -HTML templates, and other resources used to support that functionality. (The -term bundle is borrowed from [OSGi](http://www.osgi.org/) - which has also -inspired many of the concepts used in the framework layer. A familiarity with -OSGi, particularly Declarative Services, may be useful when working with Open +* __Bundle:__ A bundle is a collection of related functionality that can be +added to the application as a group. More concretely, a bundle is a directory +containing a JSON file declaring its contents, as well as JavaScript sources, +HTML templates, and other resources used to support that functionality. (The +term bundle is borrowed from [OSGi](http://www.osgi.org/) - which has also +inspired many of the concepts used in the framework layer. A familiarity with +OSGi, particularly Declarative Services, may be useful when working with Open MCT Web.) -* __Extension:__ An extension is an individual unit of functionality. Extensions -are collected together in bundles, and may interact with other extensions. +* __Extension:__ An extension is an individual unit of functionality. 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 -of entry for an application built on Open MCT Web. It is responsible for wiring -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 +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 +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 through four stages: -1. __Loading definitions:__ JSON declarations are loaded for all bundles which -will constitute the application, and wrapped in a useful API for subsequent -stages. -2. __Resolving extensions:__ Any scripts which provide implementations for -extensions exposed by bundles are loaded, using Require. -3. __Registering extensions__ Resolved extensions are registered with Angular, -such that they can be used by the application at run-time. This stage includes -both registration of Angular built-ins (directives, controllers, routes, -constants, and services) as well as registration of non-Angular extensions. -4. __Bootstrapping__ The Angular application is bootstrapped; at that point, -Angular takes over and populates the body of the page using the extensions that -have been registered. +1. __Loading definitions:__ JSON declarations are loaded for all bundles which +will constitute the application, and wrapped in a useful API for subsequent +stages. +2. __Resolving extensions:__ Any scripts which provide implementations for +extensions exposed by bundles are loaded, using Require. +3. __Registering extensions__ Resolved extensions are registered with Angular, +such that they can be used by the application at run-time. This stage includes +both registration of Angular built-ins (directives, controllers, routes, +constants, and services) as well as registration of non-Angular extensions. +4. __Bootstrapping__ The Angular application is bootstrapped; at that point, +Angular takes over and populates the body of the page using the extensions that +have been registered. -## Bundles +## Bundles -The basic configurable unit of Open MCT Web is the _bundle_. This term has been -used a bit already; now we'll get to a more formal definition. +The basic configurable unit of Open MCT Web is the _bundle_. This term has been +used a bit already; now we'll get to a more formal definition. A bundle is a directory which contains: * A bundle definition; a file named `bundle.json`. -* Subdirectories for sources, resources, and tests. -* Optionally, a `README.md` Markdown file describing its contents (this is not +* Subdirectories for sources, resources, and tests. +* 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.) -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 +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 interact. -A plugin in Open MCT Web is a bundle. The platform itself is also decomposed -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 -use; a plugin is just a bundle that is meant to be easily added or removed. When -developing, it is typically more useful to think in terms of bundles. +A plugin in Open MCT Web is a bundle. The platform itself is also decomposed +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 +use; a plugin is just a bundle that is meant to be easily added or removed. When +developing, it is typically more useful to think in terms of bundles. + +### Configuring Active Bundles + +To decide which bundles should be loaded, the framework loads a file named +`bundles.json` (peer to the `index.html` file which serves the application) to +determine which bundles should be loaded. This file should contain a single JSON +array of strings, where each is the path to a bundle. These paths should not +include bundle.json (this is implicit) or a trailing slash. -### Configuring Active Bundles +For instance, if `bundles.json` contained: -To decide which bundles should be loaded, the framework loads a file named -`bundles.json` (peer to the `index.html` file which serves the application) to -determine which bundles should be loaded. This file should contain a single JSON -array of strings, where each is the path to a bundle. These paths should not -include bundle.json (this is implicit) or a trailing slash. + [ + "example/builtins", + "example/extensions" + ] + +...then the Open MCT Web framework would look for bundle definitions at +`example/builtins/bundle.json` and `example/extensions/bundle.json`, relative +to the path of `index.html`. No other bundles would be loaded. -For instance, if `bundles.json` contained: - - [ - "example/builtins", - "example/extensions" - ] - -...then the Open MCT Web framework would look for bundle definitions at -`example/builtins/bundle.json` and `example/extensions/bundle.json`, relative -to the path of `index.html`. No other bundles would be loaded. - -### Bundle Definition - -A bundle definition (the `bundle.json` file located within a bundle) contains a -description of the bundle itself, as well as the information exposed by the -bundle. - -This definition is expressed as a single JSON object with the following +### Bundle Definition + +A bundle definition (the `bundle.json` file located within a bundle) contains a +description of the bundle itself, as well as the information exposed by the +bundle. + +This definition is expressed as a single JSON object with the following properties (all of which are optional, falling back to reasonable defaults): -* `key`: A machine-readable name for the bundle. (Currently used only in -logging.) -* `name`: A human-readable name for the bundle. (Also only used in logging.) -* `sources`: Names a directory in which source scripts (which will implement -extensions) are located. Defaults to 'src' -* `resources`: Names a directory in which resource files (such as HTML templates, -images, CS files, and other non-JavaScript files needed by this bundle) are -located. Defaults to 'res' -* `libraries`: Names a directory in which third-party libraries are located. -Defaults to 'lib' -* `configuration`: A bundle's configuration object, which should be formatted as -would be passed to require.config (see [RequireJS documentation](http://requirejs.org/docs/api.html ) ); -note that only paths and shim have been tested. -* `extensions`: An object containing key-value pairs, where keys are extension -categories, and values are extension definitions. See the section on Extensions -for more information. +* `key`: A machine-readable name for the bundle. (Currently used only in +logging.) +* `name`: A human-readable name for the bundle. (Also only used in logging.) +* `sources`: Names a directory in which source scripts (which will implement +extensions) are located. Defaults to 'src' +* `resources`: Names a directory in which resource files (such as HTML templates, +images, CS files, and other non-JavaScript files needed by this bundle) are +located. Defaults to 'res' +* `libraries`: Names a directory in which third-party libraries are located. +Defaults to 'lib' +* `configuration`: A bundle's configuration object, which should be formatted as +would be passed to require.config (see [RequireJS documentation](http://requirejs.org/docs/api.html ) ); +note that only paths and shim have been tested. +* `extensions`: An object containing key-value pairs, where keys are extension +categories, and values are extension definitions. See the section on Extensions +for more information. -For example, the bundle definition for example/policy looks like: +For example, the bundle definition for example/policy looks like: { - "name": "Example Policy", - "description": "Provides an example of using policies.", - "sources": "src", - "extensions": { - "policies": [ - { - "implementation": "ExamplePolicy.js", - "category": "action" + "name": "Example Policy", + "description": "Provides an example of using policies.", + "sources": "src", + "extensions": { + "policies": [ + { + "implementation": "ExamplePolicy.js", + "category": "action" } - ] - } + ] + } } -### Bundle Directory Structure - -In addition to the directories defined in the bundle definition, a bundle will -typically contain other directories not used at run-time. Additionally, some -useful development scripts (such as the command line build and the test suite) -expect this directory structure to be in use, and may ignore options chosen by -`b undle.json`. It is recommended that the directory structure described below be +### Bundle Directory Structure + +In addition to the directories defined in the bundle definition, a bundle will +typically contain other directories not used at run-time. Additionally, some +useful development scripts (such as the command line build and the test suite) +expect this directory structure to be in use, and may ignore options chosen by +`b undle.json`. It is recommended that the directory structure described below be used for new bundles. -* `src`: Contains JavaScript sources for this bundle. May contain additional -subdirectories to organize these sources; typically, these subdirectories are -named to correspond to the extension categories they contain and/or support, but -this is only a convention. -* `res`: Contains other files needed by this bundle, such as HTML templates. May -contain additional subdirectories to organize these sources. -* `lib`: Contains JavaScript sources from third-party libraries. These are -separated from bundle sources in order to ignore them during code style checking +* `src`: Contains JavaScript sources for this bundle. May contain additional +subdirectories to organize these sources; typically, these subdirectories are +named to correspond to the extension categories they contain and/or support, but +this is only a convention. +* `res`: Contains other files needed by this bundle, such as HTML templates. May +contain additional subdirectories to organize these sources. +* `lib`: Contains JavaScript sources from third-party libraries. These are +separated from bundle sources in order to ignore them during code style checking from the command line build. -* `test`: Contains JavaScript sources implementing [Jasmine](http://jasmine.github.io/) -tests, as well as a file named `suite.json` describing which files to test. -Should have the same folder structure as the `src` directory; see the section on -automated testing for more information. - -For example, the directory structure for bundle `platform/commonUI/about` looks -like: +* `test`: Contains JavaScript sources implementing [Jasmine](http://jasmine.github.io/) +tests, as well as a file named `suite.json` describing which files to test. +Should have the same folder structure as the `src` directory; see the section on +automated testing for more information. + +For example, the directory structure for bundle `platform/commonUI/about` looks +like: Platform | @@ -442,466 +443,444 @@ like: | +-README.md -## Extensions +## Extensions -While bundles provide groupings of related behaviors, the individual units of -behavior are called extensions. +While bundles provide groupings of related behaviors, the individual units of +behavior are called extensions. -Extensions belong to categories; an extension category is the machine-readable -identifier used to identify groups of extensions. In the `extensions` property -of a bundle definition, the keys are extension categories and the values are -arrays of extension definitions. +Extensions belong to categories; an extension category is the machine-readable +identifier used to identify groups of extensions. In the `extensions` property +of a bundle definition, the keys are extension categories and the values are +arrays of extension definitions. + +### General Extensions -### General Extensions +Extensions are intended as a general-purpose mechanism for adding new types of +functionality to Open MCT Web. -Extensions are intended as a general-purpose mechanism for adding new types of -functionality to Open MCT Web. +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, +generally, any other extension) can access the full set of registered +extensions, from all bundles, by including this string (e.g. `types[]` to get +all type definitions) in a dependency declaration. -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, -generally, any other extension) can access the full set of registered -extensions, from all bundles, by including this string (e.g. `types[]` to get -all type definitions) in a dependency declaration. +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 +platform in any way. For extension categories introduced by external plugins, it +is recommended to prefix the extension category with a vendor identifier (or +similar) followed by a dot, to avoid collisions. + +### Extension Definitions -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 -platform in any way. For extension categories introduced by external plugins, it -is recommended to prefix the extension category with a vendor identifier (or -similar) followed by a dot, to avoid collisions. +The properties used in extension definitions are typically unique to each +category of extension; a few properties have standard interpretations by the +platform. -### Extension Definitions +* `implementation`: Identifies a JavaScript source file (in the sources +folder) which implements this extension. This JavaScript file is expected to +contain an AMD module (see http://requirejs.org/docs/whyamd.html#amd ) which +gives as its result a single constructor function. +* `depends`: An array of dependencies needed by this extension; these will be +passed on to Angular's [dependency injector](https://docs.angularjs.org/guide/di ) . +By default, this is treated as an empty array. Note that depends does not make +sense without `implementation` (since these dependencies will be passed to the +implementation when it is instantiated.) +* `priority`: A number or string indicating the priority order (see below) of +this extension instance. Before an extension category is registered with +AngularJS, the extensions of this category from all bundles will be concatenated +into a single array, and then sorted by priority. -The properties used in extension definitions are typically unique to each -category of extension; a few properties have standard interpretations by the -platform. +Extensions do not need to have an implementation. If no implementation is +provided, consumers of the extension category will receive the extension +definition as a plain JavaScript object. Otherwise, they will receive the +partialized (see below) constructor for that implementation, which will +additionally have all properties from the extension definition attached. -* `implementation`: Identifies a JavaScript source file (in the sources -folder) which implements this extension. This JavaScript file is expected to -contain an AMD module (see http://requirejs.org/docs/whyamd.html#amd ) which -gives as its result a single constructor function. -* `depends`: An array of dependencies needed by this extension; these will be -passed on to Angular's [dependency injector](https://docs.angularjs.org/guide/di ) . -By default, this is treated as an empty array. Note that depends does not make -sense without `implementation` (since these dependencies will be passed to the -implementation when it is instantiated.) -* `priority`: A number or string indicating the priority order (see below) of -this extension instance. Before an extension category is registered with -AngularJS, the extensions of this category from all bundles will be concatenated -into a single array, and then sorted by priority. +#### Partial Construction -Extensions do not need to have an implementation. If no implementation is -provided, consumers of the extension category will receive the extension -definition as a plain JavaScript object. Otherwise, they will receive the -partialized (see below) constructor for that implementation, which will -additionally have all properties from the extension definition attached. +In general, extensions are intended to be implemented as constructor functions, +which will be used elsewhere to instantiate new objects of that type. However, +the Angular-supported method for dependency injection is (effectively) +constructor-style injection; so, both declared dependencies and run-time +arguments are competing for space in a constructor's arguments. -#### Partial Construction +To resolve this, the Open MCT Web framework registers extension instances in a +partially constructed form. That is, the constructor exposed by the extension's +implementation is effectively decomposed into two calls; the first takes the +dependencies, and returns the constructor in its second form, which takes the +remaining arguments. -In general, extensions are intended to be implemented as constructor functions, -which will be used elsewhere to instantiate new objects of that type. However, -the Angular-supported method for dependency injection is (effectively) -constructor-style injection; so, both declared dependencies and run-time -arguments are competing for space in a constructor's arguments. +This means that, when writing implementations, the constructor function should +be written to include all declared dependencies, followed by all run-time +arguments. When using extensions, only the run-time arguments need to be +provided. + +#### Priority -To resolve this, the Open MCT Web framework registers extension instances in a -partially constructed form. That is, the constructor exposed by the extension's -implementation is effectively decomposed into two calls; the first takes the -dependencies, and returns the constructor in its second form, which takes the -remaining arguments. +Within each extension category, registration occurs in priority order. An +extension's priority may be specified as a `priority` property in its extension +definition; this may be a number, or a symbolic string. Extensions are +registered in reverse order (highest-priority first), and symbolic strings are +mapped to the numeric values as follows: -This means that, when writing implementations, the constructor function should -be written to include all declared dependencies, followed by all run-time -arguments. When using extensions, only the run-time arguments need to be -provided. +* `fallback`: Negative infinity. Used for extensions that are not intended for +use (that is, they are meant to be overridden) but are present as an option of +last resort. +* `default`: `-100`. Used for extensions that are expected to be overridden, but +need a useful default. +* `none`: `0`. Also used if no priority is specified, or if an unknown or +malformed priority is specified. +* `optional`: `100`. Used for extensions that are meant to be used, but may be +overridden. +* `preferred`: `1000`. Used for extensions that are specifically intended to be +used, but still may be overridden in principle. +* `mandatory`: Positive infinity. Used when an extension should definitely not +be overridden. -#### Priority - -Within each extension category, registration occurs in priority order. An -extension's priority may be specified as a `priority` property in its extension -definition; this may be a number, or a symbolic string. Extensions are -registered in reverse order (highest-priority first), and symbolic strings are -mapped to the numeric values as follows: - -* `fallback`: Negative infinity. Used for extensions that are not intended for -use (that is, they are meant to be overridden) but are present as an option of -last resort. -* `default`: `-100`. Used for extensions that are expected to be overridden, but -need a useful default. -* `none`: `0`. Also used if no priority is specified, or if an unknown or -malformed priority is specified. -* `optional`: `100`. Used for extensions that are meant to be used, but may be -overridden. -* `preferred`: `1000`. Used for extensions that are specifically intended to be -used, but still may be overridden in principle. -* `mandatory`: Positive infinity. Used when an extension should definitely not -be overridden. - -These symbolic names are chosen to support usage where many extensions may -satisfy a given need, but only one may be used; in this case, as a convention it -should be the lowest-ordered (highest-priority) extensions available. In other -cases, a full set (or multi-element subset) of extensions may be desired, with a -specific ordering; in these cases, it is preferable to specify priority -numerically when declaring extensions, and to understand that extensions will be -sorted according to these conventions when using them. - -### Angular Built-ins - -Several entities supported Angular are expressed and managed as extensions in -Open MCT Web. Specifically, these extension categories are _directives_, -_controllers_, _services_, _constants_, _runs_, and _routes_. +These symbolic names are chosen to support usage where many extensions may +satisfy a given need, but only one may be used; in this case, as a convention it +should be the lowest-ordered (highest-priority) extensions available. In other +cases, a full set (or multi-element subset) of extensions may be desired, with a +specific ordering; in these cases, it is preferable to specify priority +numerically when declaring extensions, and to understand that extensions will be +sorted according to these conventions when using them. + +### Angular Built-ins +Several entities supported Angular are expressed and managed as extensions in +Open MCT Web. Specifically, these extension categories are _directives_, +_controllers_, _services_, _constants_, _runs_, and _routes_. + #### Angular Directives -New [directives]( https://docs.angularjs.org/guide/directive ) may be -registered as extensions of the directives category. Implementations of -directives in this category should take only dependencies as arguments, and -should return a directive definition object. - -The directive's name should be provided as a key property of its extension -definition, in camel-case format. +New [directives]( https://docs.angularjs.org/guide/directive ) may be +registered as extensions of the directives category. Implementations of +directives in this category should take only dependencies as arguments, and +should return a directive definition object. +The directive's name should be provided as a key property of its extension +definition, in camel-case format. + #### Angular Controllers -New [controllers]( https://docs.angularjs.org/guide/controller ) may be registered -as extensions of the controllers category. The implementation is registered -directly as the controller; its only constructor arguments are its declared -dependencies. - -The directive's identifier should be provided as a key property of its extension -definition. - +New [controllers]( https://docs.angularjs.org/guide/controller ) may be registered +as extensions of the controllers category. The implementation is registered +directly as the controller; its only constructor arguments are its declared +dependencies. +The directive's identifier should be provided as a key property of its extension +definition. + + #### Angular Services -New [services](https://docs.angularjs.org/guide/services ) may be registered as -extensions of the services category. The implementation is registered via a -[service call]( https://docs.angularjs.org/api/auto/service/$provide#service ), so -it will be instantiated with the new operator. +New [services](https://docs.angularjs.org/guide/services ) may be registered as +extensions of the services category. The implementation is registered via a +[service call]( https://docs.angularjs.org/api/auto/service/$provide#service ), so +it will be instantiated with the new operator. -#### Angular Constants +#### Angular Constants -Constant values may be registered as extensions of the [ constants category](https://docs.angularjs.org/api/ng/type/angular.Module#constant ). -These extensions have no implementation; instead, they should contain a property - key , which is the name under which the constant will be registered, and a +Constant values may be registered as extensions of the [ constants category](https://docs.angularjs.org/api/ng/type/angular.Module#constant ). +These extensions have no implementation; instead, they should contain a property + key , which is the name under which the constant will be registered, and a property value , which is the constant value that will be registered. -#### Angular Runs +#### Angular Runs -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 ). -Implementations registered in this category will be invoked (with their declared -dependencies) when the Open MCT Web application first starts. (Note that, in -this case, the implementation is better thought of as just a function, as +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 ). +Implementations registered in this category will be invoked (with their declared +dependencies) when the Open MCT Web application first starts. (Note that, in +this case, the implementation is better thought of as just a function, as opposed to a constructor function.) #### Angular Routes -Extensions of category `routes` will be registered with Angular's [route provider](https://docs.angularjs.org/api/ngRoute/provider/$routeProvider ). -Extensions of this category have no implementations, and need only two -properties in their definition: +Extensions of category `routes` will be registered with Angular's [route provider](https://docs.angularjs.org/api/ngRoute/provider/$routeProvider ). +Extensions of this category have no implementations, and need only two +properties in their definition: -* `when`: The value that will be passed as the path argument to `$routeProvider.when`; -specifically, the string that will appear in the trailing -part of the URL corresponding to this route. This property may be omitted, in -which case this extension instance will be treated as the default route. -* `templateUrl`: A path to the template to render for this route. Specified as a -path relative to the bundle's resource directory (`res` by default.) +* `when`: The value that will be passed as the path argument to `$routeProvider.when`; +specifically, the string that will appear in the trailing +part of the URL corresponding to this route. This property may be omitted, in +which case this extension instance will be treated as the default route. +* `templateUrl`: A path to the template to render for this route. Specified as a +path relative to the bundle's resource directory (`res` by default.) ### Composite Services -Composite services are described in the [relevant section](../architecture/Framework.md#Composite-Services) -of the framework guide. +Composite services are described in the [Composite Services](../architecture/framework.md#composite-services) +section of the framework guide. A component should include the following properties in its extension definition: -* `provides`: The symbolic identifier for the service that will be composed. The +* `provides`: The symbolic identifier for the service that will be composed. The fully-composed service will be registered with Angular under this name. -* `type`: One of `provider`, `aggregator` or `decorator` (as above) +* `type`: One of `provider`, `aggregator` or `decorator` (as above) -In addition to any declared dependencies, _aggregators_ and _decorators_ both -receive one more argument (immediately following declared dependencies) that is -provided by the framework. For an aggregator, this will be an array of all -providers of the same service (that is, with matching `provides` properties); -for a decorator, this will be whichever provider, decorator, or aggregator is -next in the sequence of decorators. +In addition to any declared dependencies, _aggregators_ and _decorators_ both +receive one more argument (immediately following declared dependencies) that is +provided by the framework. For an aggregator, this will be an array of all +providers of the same service (that is, with matching `provides` properties); +for a decorator, this will be whichever provider, decorator, or aggregator is +next in the sequence of decorators. -Services exposed by the Open MCT Web platform are often declared as composite -services, as this form is open for a variety of common modifications. +Services exposed by the Open MCT Web platform are often declared as composite +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 -framework; that is, much of developing for Open MCT Web is a matter of adding -extensions which access other parts of the platform by means of dependency -injection. +Most of Open MCT Web'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 +extensions which access other parts of the platform by means of dependency +injection. -The core bundle (`platform/core`) introduces a few additional object types meant -to be passed along by other services. +The core bundle (`platform/core`) introduces a few additional object types meant +to be passed along by other services. -## Domain Objects +## Domain Objects -Domain objects are the most fundamental component of Open MCT Web's information -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 -viewing, browsing, manipulating, and otherwise interacting with a graph of -domain objects. +Domain objects are the most fundamental component of Open MCT Web's information +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 +viewing, browsing, manipulating, and otherwise interacting with a graph of +domain objects. A domain object should be conceived of as the union of the following: -* __Identifier__: A machine-readable string that uniquely identifies the domain -object within this application instance. -* __Model__: The persistent state of the domain object. A domain object's model -is a JavaScript object that can be losslessly converted to JSON. -* __Capabilities__: Dynamic behavior associated with the domain object. -Capabilities are JavaScript objects which provide additional methods for -interacting with the domain objects which expose those capabilities. Not all -domain objects expose all capabilities. +* __Identifier__: A machine-readable string that uniquely identifies the domain +object within this application instance. +* __Model__: The persistent state of the domain object. A domain object's model +is a JavaScript object that can be losslessly converted to JSON. +* __Capabilities__: Dynamic behavior associated with the domain object. +Capabilities are JavaScript objects which provide additional methods for +interacting with the domain objects which expose those capabilities. Not all +domain objects expose all capabilities. At run-time, a domain object has the following interface: -* `getId()`: Get the identifier for this domain object. -* `getModel()`: Get the plain state associated with this domain object. This -will return a JavaScript object that can be losslessly converted to JSON. Note -that the model returned here can be modified directly but should not be; -instead, use the mutation capability. -* `getCapability(key)`: Get the specified capability associated with this domain -object. This will return a JavaScript object whose interface is specific to the -type of capability being requested. If the requested capability is not exposed +* `getId()`: Get the identifier for this domain object. +* `getModel()`: Get the plain state associated with this domain object. This +will return a JavaScript object that can be losslessly converted to JSON. Note +that the model returned here can be modified directly but should not be; +instead, use the mutation capability. +* `getCapability(key)`: Get the specified capability associated with this domain +object. This will return a JavaScript object whose interface is specific to the +type of capability being requested. If the requested capability is not exposed by this domain object, this will return undefined . -* `hasCapability(key)`: Shorthand for checking if a domain object exposes the +* `hasCapability(key)`: Shorthand for checking if a domain object exposes the requested capability. -* `useCapability(key, arguments )`: Shorthand for -`getCapability(key).invoke(arguments)`, with additional checking between calls. -If the provided capability has no invoke method, the return value here functions -as `getCapability` including returning `undefined` if the capability is not +* `useCapability(key, arguments )`: Shorthand for +`getCapability(key).invoke(arguments)`, with additional checking between calls. +If the provided capability has no invoke method, the return value here functions +as `getCapability` including returning `undefined` if the capability is not exposed. -### Identifier Syntax - -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 -should rarely be concerned with its internal structure. - -A domain object identifier has one or two parts, separated by a colon. - -* If two parts are present, the part before the colon refers to the space - in which the domain object resides. This may be a persistence space or - a purely symbolic space recognized by a specific model provider. -* If only one part is present, the domain object has no space specified, - and may presume to reside in the application-configured default space - defined by the `PERSISTENCE_SPACE` constant. - -```bnf - ::= ":" | - ::= + - ::= + - ::= | | "-" | "." | "_" -``` - ## Domain Object Actions -An `Action` is behavior that can be performed upon/using a `DomainObject`. An +An `Action` is behavior that can be performed upon/using a `DomainObject`. An Action has the following interface: -* `perform()`: Do this action. For example, if one had an instance of a -`RemoveAction` invoking its perform method would cause the domain object which +* `perform()`: Do this action. For example, if one had an instance of a +`RemoveAction` invoking its perform method would cause the domain object which exposed it to be removed from its container. -* `getMetadata()`: Get metadata associated with this action. Returns an object -containing: +* `getMetadata()`: Get metadata associated with this action. Returns an object +containing: * `name`: Human-readable name. - * `description`: Human-readable summary of this action. - * `glyph`: Single character to be displayed in Open MCT Web's icon font set. + * `description`: Human-readable summary of this action. + * `glyph`: Single character to be displayed in Open MCT Web's icon font set. * `context`: The context in which this action is being performed (see below) -Action instances are typically obtained via a domain object's `action` -capability. +Action instances are typically obtained via a domain object's `action` +capability. + +### Action Contexts -### Action Contexts +An action context is a JavaScript object with the following properties: -An action context is a JavaScript object with the following properties: - -* `domainObject`: The domain object being acted upon. -* `selectedObject`: Optional; the selection at the time of action (e.g. the +* `domainObject`: The domain object being acted upon. +* `selectedObject`: Optional; the selection at the time of action (e.g. the dragged object in a drag-and-drop operation.) -## Telemetry +## Telemetry -Telemetry series data in Open MCT Web is represented by a common interface, and -packaged in a consistent manner to facilitate passing telemetry updates around -multiple visualizations. +Telemetry series data in Open MCT Web is represented by a common interface, and +packaged in a consistent manner to facilitate passing telemetry updates around +multiple visualizations. + +### Telemetry Requests -### Telemetry Requests +A telemetry request is a JavaScript object containing the following properties: -A telemetry request is a JavaScript object containing the following properties: +* `source`: A machine-readable identifier for the source of this telemetry. This +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 +that source. +* _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._ -* `source`: A machine-readable identifier for the source of this telemetry. This -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 -that source. -* _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._ - -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. ### Telemetry Responses -When returned from the `telemetryService` (see [Services](#Services) section), -telemetry series data will be packaged in a `source -> key -> TelemetrySeries` -fashion. That is, telemetry is passed in an object containing key-value pairs. -Keys identify telemetry sources; values are objects containing additional -key-value pairs. In this object, keys identify individual telemetry series (and -match they `key` property from corresponding requests) and values are -`TelemetrySeries` objects (see below.) +When returned from the `telemetryService` (see [Telemetry Services](#telemetry-service) +section), telemetry series data will be packaged in a `source -> key -> TelemetrySeries` +fashion. That is, telemetry is passed in an object containing key-value pairs. +Keys identify telemetry sources; values are objects containing additional +key-value pairs. In this object, keys identify individual telemetry series (and +match they `key` property from corresponding requests) and values are +`TelemetrySeries` objects (see below.) ### Telemetry Series -A telemetry series is a specific sequence of data, typically associated with a -specific instrument. Telemetry is modeled as an ordered sequence of domain and -range values, where domain values must be non-decreasing but range values do -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 +A telemetry series is a specific sequence of data, typically associated with a +specific instrument. Telemetry is modeled as an ordered sequence of domain and +range values, where domain values must be non-decreasing but range values do +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 range, and may have more than one. -Telemetry series data in Open MCT Web is expressed via the following -`TelemetrySeries` interface: +Telemetry series data in Open MCT Web is expressed via the following +`TelemetrySeries` interface: -* `getPointCount()`: Returns the number of unique points/samples in this series. -* `getDomainValue(index, [domain])`: Get the domain value at the specified index . -If a second domain argument is provided, this is taken as a string identifier -indicating which domain option (of, presumably, multiple) should be returned. -* `getRangeValue(index, [range])`: Get the domain value at the specified index . -If a second range argument is provided, this is taken as a string identifier -indicating which range option (of, presumably, multiple) should be returned. +* `getPointCount()`: Returns the number of unique points/samples in this series. +* `getDomainValue(index, [domain])`: Get the domain value at the specified index . +If a second domain argument is provided, this is taken as a string identifier +indicating which domain option (of, presumably, multiple) should be returned. +* `getRangeValue(index, [range])`: Get the domain value at the specified index . +If a second range argument is provided, this is taken as a string identifier +indicating which range option (of, presumably, multiple) should be returned. + +### Telemetry Metadata -### Telemetry Metadata +Domain objects which have associated telemetry also expose metadata about that +telemetry; this is retrievable via the `getMetadata()` of the telemetry +capability. This will return a single JavaScript object containing the following +properties: -Domain objects which have associated telemetry also expose metadata about that -telemetry; this is retrievable via the `getMetadata()` of the telemetry -capability. This will return a single JavaScript object containing the following -properties: +* `source`: The machine-readable identifier for the source of telemetry data for +this object. +* `key`: The machine-readable identifier for the individual telemetry series. +* `domains`: An array of supported domains (see TelemetrySeries above.) Each +domain should be expressed as an object which includes: + * `key`: Machine-readable identifier for this domain, as will be passed into + a getDomainValue(index, domain) call. + * `name`: Human-readable name for this domain. +* `ranges`: An array of supported ranges; same format as domains . -* `source`: The machine-readable identifier for the source of telemetry data for -this object. -* `key`: The machine-readable identifier for the individual telemetry series. -* `domains`: An array of supported domains (see TelemetrySeries above.) Each -domain should be expressed as an object which includes: - * `key`: Machine-readable identifier for this domain, as will be passed into - a getDomainValue(index, domain) call. - * `name`: Human-readable name for this domain. -* `ranges`: An array of supported ranges; same format as domains . +Note that this metadata is also used as the prototype for telemetry requests +made using this capability. -Note that this metadata is also used as the prototype for telemetry requests -made using this capability. - -## Types -A domain object's type is represented as a Type object, which has the following +## Types +A domain object's type is represented as a Type object, which has the following interface: -* `getKey()`: Get the machine-readable identifier for this type. -* `getName()`: Get the human-readable name for 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 -in Open MCT Web's custom font set. -* `getInitialModel()`: Get a domain object model that represents the initial -state (before user specification of properties) for domain objects of this type. -* `getDefinition()`: Get the extension definition for this type, as a JavaScript -object. -* `instanceOf(type)`: Check if this type is (or inherits from) a specified type . -This type can be either a string, in which case it is taken to be that type's - key , or it may be a `Type` instance. -* `hasFeature(feature)`: Returns a boolean value indicating whether or not this -type supports the specified feature, which is a symbolic string. -* `getProperties()`: Get all properties associated with this type, expressed as -an array of `TypeProperty` instances. +* `getKey()`: Get the machine-readable identifier for this type. +* `getName()`: Get the human-readable name for 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 +in Open MCT Web's custom font set. +* `getInitialModel()`: Get a domain object model that represents the initial +state (before user specification of properties) for domain objects of this type. +* `getDefinition()`: Get the extension definition for this type, as a JavaScript +object. +* `instanceOf(type)`: Check if this type is (or inherits from) a specified type . +This type can be either a string, in which case it is taken to be that type's + key , or it may be a `Type` instance. +* `hasFeature(feature)`: Returns a boolean value indicating whether or not this +type supports the specified feature, which is a symbolic string. +* `getProperties()`: Get all properties associated with this type, expressed as +an array of `TypeProperty` instances. + +### Type Features -### Type Features +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 +uses the creation feature to determine which domain object types should appear +in the Create menu. + +### Type Properties -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 -uses the creation feature to determine which domain object types should appear -in the Create menu. - -### Type Properties - -Types declare the user-editable properties of their domain object instances in -order to allow the forms which appear in the __Create__ and __Edit Properties__ +Types declare the user-editable properties of their domain object instances in +order to allow the forms which appear in the __Create__ and __Edit Properties__ dialogs to be generated by the platform. A `TypeProperty` has the following interface: -* `getValue(model)`: Get the current value for this property, as it appears in -the provided domain object model. -* `setValue(model, value)`: Set a new value for this property in the provided -domain object model . -* `getDefinition()`: Get the raw definition for this property as a JavaScript -object (as it was declared in this type's extension definition.) +* `getValue(model)`: Get the current value for this property, as it appears in +the provided domain object model. +* `setValue(model, value)`: Set a new value for this property in the provided +domain object model . +* `getDefinition()`: Get the raw definition for this property as a JavaScript +object (as it was declared in this type's extension definition.) -# Extension Categories +# Extension Categories -The information in this section is focused on registering new extensions of -specific types; it does not contain a catalog of the extension instances of -these categories provided by the platform. Relevant summaries there are provided +The information in this section is focused on registering new extensions of +specific types; it does not contain a catalog of the extension instances of +these categories provided by the platform. Relevant summaries there are provided in subsequent sections. - + ## Actions Category -An action is a thing that can be done to or using a domain object, typically as -initiated by the user. +An action is a thing that can be done to or using a domain object, typically as +initiated by the user. An action's implementation: -* Should take a single `context` argument in its constructor. (See Action +* Should take a single `context` argument in its constructor. (See Action Contexts, under Core API.) -* Should provide a method `perform` which causes the behavior associated with +* Should provide a method `perform` which causes the behavior associated with the action to occur. -* May provide a method `getMetadata` which provides metadata associated with -the action. If omitted, one will be provided by the platform which includes +* May provide a method `getMetadata` which provides metadata associated with +the action. If omitted, one will be provided by the platform which includes metadata from the action's extension definition. -* May provide a static method `appliesTo(context)` (that is, a function -available as a property of the implementation's constructor itself), which will -be used by the platform to filter out actions from contexts in which they are +* May provide a static method `appliesTo(context)` (that is, a function +available as a property of the implementation's constructor itself), which will +be used by the platform to filter out actions from contexts in which they are inherently inapplicable. An action's bundle definition (and/or `getMetadata()` return value) may include: -* `category`: A string or array of strings identifying which category or -categories an action falls into; used to determine when an action is displayed. -Categories supported by the platform include: - * `contextual`: Actions in a context menu. - * `view-control`: Actions triggered by buttons in the top-right of Browse - view. -* `key`: A machine-readable identifier for this action. -* `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. -* `glyph`: A single character which will be rendered in Open MCT Web's custom +* `category`: A string or array of strings identifying which category or +categories an action falls into; used to determine when an action is displayed. +Categories supported by the platform include: + * `contextual`: Actions in a context menu. + * `view-control`: Actions triggered by buttons in the top-right of Browse + view. +* `key`: A machine-readable identifier for this action. +* `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. +* `glyph`: A single character which will be rendered in Open MCT Web's custom font set as an icon for this action. ## Capabilities Category -Capabilities are exposed by domain objects (e.g. via the `getCapability` method) +Capabilities are exposed by domain objects (e.g. via the `getCapability` method) but most commonly originate as extensions of this category. -Extension definitions for capabilities should include both an implementation, -and a property named key whose value should be a string used as a -machine-readable identifier for that capability, e.g. when passed as the +Extension definitions for capabilities should include both an implementation, +and a property named key whose value should be a string used as a +machine-readable identifier for that capability, e.g. when passed as the argument to a domain object's `getCapability(key)` call. - -A capability's implementation should have methods specific to that capability; -that is, there is no common format for capability implementations, aside from + +A capability's implementation should have methods specific to that capability; +that is, there is no common format for capability implementations, aside from support for invocation via the `useCapability` shorthand. -A capability's implementation will take a single argument (in addition to any -declared dependencies), which is the domain object that will expose that +A capability's implementation will take a single argument (in addition to any +declared dependencies), which is the domain object that will expose that capability. -A capability's implementation may also expose a static method `appliesTo(model)` -which should return a boolean value, and will be used by the platform to filter -down capabilities to those which should be exposed by specific domain objects, -based on their domain object models. - +A capability's implementation may also expose a static method `appliesTo(model)` +which should return a boolean value, and will be used by the platform to filter +down capabilities to those which should be exposed by specific domain objects, +based on their domain object models. + ## Controls Category -Controls provide options for the `mct-control` directive. - +Controls provide options for the `mct-control` directive. + Six standard control types are included in the forms bundle: * `textfield`: An area to enter plain text. @@ -909,94 +888,94 @@ Six standard control types are included in the forms bundle: * `checkbox`: A box which may be checked/unchecked. * `color`: A color picker. * `button`: A button. -* `datetime`: An input for UTC date/time entry; gives result as a UNIX -timestamp, in milliseconds since start of 1970, UTC. +* `datetime`: An input for UTC date/time entry; gives result as a UNIX +timestamp, in milliseconds since start of 1970, UTC. -New controls may be added as extensions of the controls category. Extensions of -this category have two properties: +New controls may be added as extensions of the controls category. Extensions of +this category have two properties: -* `key`: The symbolic name for this control (matched against the control field +* `key`: The symbolic name for this control (matched against the control field in rows of the form structure). -* `templateUrl`: The URL to the control's Angular template, relative to the -resources directory of the bundle which exposes the extension. +* `templateUrl`: The URL to the control's Angular template, relative to the +resources directory of the bundle which exposes the extension. -Within the template for a control, the following variables will be included in +Within the template for a control, the following variables will be included in scope: -* `ngModel`: The model where form input will be stored. Notably we also need to -look at field (see below) to determine which field in the model should be -modified. +* `ngModel`: The model where form input will be stored. Notably we also need to +look at field (see below) to determine which field in the model should be +modified. * `ngRequired`: True if input is required. * `ngPattern`: The pattern to match against (for text entry) -* `options`: The options for this control, as passed from the `options` property -of an individual row definition. -* `field`: Name of the field in `ngModel` which will hold the value for this -control. +* `options`: The options for this control, as passed from the `options` property +of an individual row definition. +* `field`: Name of the field in `ngModel` which will hold the value for this +control. ## Gestures Category -A _gesture_ is a user action which can be taken upon a representation of a -domain object. +A _gesture_ is a user action which can be taken upon a representation of a +domain object. Examples of gestures included in the platform are: -* `drag`: For representations that can be used to initiate drag-and-drop +* `drag`: For representations that can be used to initiate drag-and-drop composition. -* `drop`: For representations that can be drop targets for drag-and-drop -composition. -* `menu`: For representations that can be used to pop up a context menu. - -Gesture definitions have a property `key` which is used as a machine-readable -identifier for the gesture (e.g. `drag`, `drop`, `menu` above.) - -A gesture's implementation is instantiated once per representation that uses the -gesture. This class will receive the jqLite-wrapped `mct-representation` element -and the domain object being represented as arguments, and should do any -necessary "wiring" (e.g. listening for events) during its constructor call. The -gesture's implementation may also expose an optional `destroy()` method which -will be called when the gesture should be removed, to avoid memory leaks by way +* `drop`: For representations that can be drop targets for drag-and-drop +composition. +* `menu`: For representations that can be used to pop up a context menu. + +Gesture definitions have a property `key` which is used as a machine-readable +identifier for the gesture (e.g. `drag`, `drop`, `menu` above.) + +A gesture's implementation is instantiated once per representation that uses the +gesture. This class will receive the jqLite-wrapped `mct-representation` element +and the domain object being represented as arguments, and should do any +necessary "wiring" (e.g. listening for events) during its constructor call. The +gesture's implementation may also expose an optional `destroy()` method which +will be called when the gesture should be removed, to avoid memory leaks by way of unremoved listeners. ## Indicators Category -An indicator is an element that should appear in the status area at the bottom -of a running Open MCT Web client instance. +An indicator is an element that should appear in the status area at the bottom +of a running Open MCT Web client instance. -### Standard Indicators - -Indicators which wish to appear in the common form of an icon-text pair should +### Standard Indicators + +Indicators which wish to appear in the common form of an icon-text pair should provide implementations with the following methods: -* `getText()`: Provides the human-readable text that will be displayed for this -indicator. -* `getGlyph()`: Provides a single-character string that will be displayed as an -icon in Open MCT Web's custom font set. -* `getDescription()`: Provides a human-readable summary of the current state of -this indicator; will be displayed in a tooltip on hover. -* `getClass()`: Get a CSS class that will be applied to this indicator. -* `getTextClass()`: Get a CSS class that will be applied to this indicator's -text portion. -* `getGlyphClass()`: Get a CSS class that will be applied to this indicator's -icon portion. -* `configure()`: If present, a configuration icon will appear to the right of -this indicator, and clicking it will invoke this method. +* `getText()`: Provides the human-readable text that will be displayed for this +indicator. +* `getGlyph()`: Provides a single-character string that will be displayed as an +icon in Open MCT Web's custom font set. +* `getDescription()`: Provides a human-readable summary of the current state of +this indicator; will be displayed in a tooltip on hover. +* `getClass()`: Get a CSS class that will be applied to this indicator. +* `getTextClass()`: Get a CSS class that will be applied to this indicator's +text portion. +* `getGlyphClass()`: Get a CSS class that will be applied to this indicator's +icon portion. +* `configure()`: If present, a configuration icon will appear to the right of +this indicator, and clicking it will invoke this method. + +Note that all methods are optional, and are called directly from an Angular +template, so they should be appropriate to run during digest cycles. -Note that all methods are optional, and are called directly from an Angular -template, so they should be appropriate to run during digest cycles. +### Custom Indicators -### Custom Indicators - -Indicators which wish to have an arbitrary appearance (instead of following the -icon-text convention commonly used) may specify a `template` property in their -extension definition. The value of this property will be used as the `key` for -an `mct-include` directive (so should refer to an extension of category - templates .) This template will be rendered to the status area. Indicators of -this variety do not need to provide an implementation. +Indicators which wish to have an arbitrary appearance (instead of following the +icon-text convention commonly used) may specify a `template` property in their +extension definition. The value of this property will be used as the `key` for +an `mct-include` directive (so should refer to an extension of category + templates .) This template will be rendered to the status area. Indicators of +this variety do not need to provide an implementation. ## Licenses Category -The extension category `licenses` can be used to add entries into the 'Licensing -information' page, reachable from Open MCT Web's About dialog. +The extension category `licenses` can be used to add entries into the 'Licensing +information' page, reachable from Open MCT Web's About dialog. Licenses may have the following properties, all of which are strings: @@ -1005,1179 +984,1181 @@ Licenses may have the following properties, all of which are strings: * `description`: Human-readable summary of the component. * `author`: Name or names of entities to which authorship should be attributed. * `copyright`: Copyright text to display for this component. -* `link`: URL to full license text. +* `link`: URL to full license text. ## Policies Category -Policies are used to handle decisions made using Open MCT Web's `policyService`; -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 -domain object of a different type. See the section on the Policies for an +Policies are used to handle decisions made using Open MCT Web's `policyService`; +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 +domain object of a different type. See the section on the Policies for an overview of Open MCT Web's policy model. A policy's extension definition should include: -* `category`: The machine-readable identifier for the type of policy decision -being supported here. For a list of categories supported by the platform, see -the section on Policies. Plugins may introduce and utilize additional policy -categories not in that list. -* `message`: Optional; a human-readable message describing the policy, intended -for display in situations where this specific policy has disallowed something. - +* `category`: The machine-readable identifier for the type of policy decision +being supported here. For a list of categories supported by the platform, see +the section on Policies. Plugins may introduce and utilize additional policy +categories not in that list. +* `message`: Optional; a human-readable message describing the policy, intended +for display in situations where this specific policy has disallowed something. + A policy's implementation should include a single method, `allow(candidate, -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 -context?' This method should return a boolean value. - -Open MCT Web's policy model requires consensus; a policy decision is allowed -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`) -anything else. +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 +context?' This method should return a boolean value. +Open MCT Web's policy model requires consensus; a policy decision is allowed +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`) +anything else. + ## Representations Category -A representation is an Angular template used to display a domain object. The -`representations` extension category is used to add options for the -`mct-representation` directive. - +A representation is an Angular template used to display a domain object. The +`representations` extension category is used to add options for the +`mct-representation` directive. + A representation definition should include the following properties: -* `key`: The machine-readable name which identifies the representation. -* `templateUrl`: The path to the representation's Angular template. This path is -relative to the bundle's resources directory. -* `uses`: Optional; an array of capability names. Indicates that this -representation intends to use those capabilities of a domain object (via a -`useCapability` call), and expects to find the latest results of that -`useCapability` call in the scope of the presented template (under the same name -as the capability itself.) Note that, if `useCapability` returns a promise, this -will be resolved before being placed in the representation's scope. -* `gestures`: An array of keys identifying gestures (see the `gestures` -extension category) which should be available upon this representation. Examples -of gestures include `drag` (for representations that should act as draggable -sources for drag-drop operations) and `menu` (for representations which should -show a domain-object-specific context menu on right-click.) +* `key`: The machine-readable name which identifies the representation. +* `templateUrl`: The path to the representation's Angular template. This path is +relative to the bundle's resources directory. +* `uses`: Optional; an array of capability names. Indicates that this +representation intends to use those capabilities of a domain object (via a +`useCapability` call), and expects to find the latest results of that +`useCapability` call in the scope of the presented template (under the same name +as the capability itself.) Note that, if `useCapability` returns a promise, this +will be resolved before being placed in the representation's scope. +* `gestures`: An array of keys identifying gestures (see the `gestures` +extension category) which should be available upon this representation. Examples +of gestures include `drag` (for representations that should act as draggable +sources for drag-drop operations) and `menu` (for representations which should +show a domain-object-specific context menu on right-click.) ### Representation Scope -While _representations_ do not have implementations, per se, they do refer to -Angular templates which need to interact with information (e.g. the domain -object being represented) provided by the platform. This information is passed -in through the template's scope, such that simple representations may be created -by providing only templates. (More complex representations will need controllers -which are referenced from templates. See [https://docs.angularjs.org/guide/controller ]() -for more information on controllers in Angular.) - +While _representations_ do not have implementations, per se, they do refer to +Angular templates which need to interact with information (e.g. the domain +object being represented) provided by the platform. This information is passed +in through the template's scope, such that simple representations may be created +by providing only templates. (More complex representations will need controllers +which are referenced from templates. See https://docs.angularjs.org/guide/controller +for more information on controllers in Angular.) + A representation's scope will contain: * `domainObject`: The represented domain object. * `model`: The domain object's model. -* `configuration`: An object containing configuration information for this -representation (an empty object if there is no saved configuration.) The -contents of this object are managed entirely by the view/representation which -receives it. -* `representation`: An empty object, useful as a 'scratch pad' for -representation state. -* `ngModel`: An object passed through the ng-model attribute of the -`mct-representation` , if any. -* `parameters`: An object passed through the parameters attribute of the -`mct-representation`, if any. -* Any capabilities requested by the uses property of the representation +* `configuration`: An object containing configuration information for this +representation (an empty object if there is no saved configuration.) The +contents of this object are managed entirely by the view/representation which +receives it. +* `representation`: An empty object, useful as a 'scratch pad' for +representation state. +* `ngModel`: An object passed through the ng-model attribute of the +`mct-representation` , if any. +* `parameters`: An object passed through the parameters attribute of the +`mct-representation`, if any. +* Any capabilities requested by the uses property of the representation definition. - + ## Representers Category -The `representers` extension category is used to add additional behavior to the -`mct-representation` directive. This extension category is intended primarily -for use internal to the platform. +The `representers` extension category is used to add additional behavior to the +`mct-representation` directive. This extension category is intended primarily +for use internal to the platform. -Unlike _representations_, which describe specific ways to represent domain -objects, _representers_ are used to modify or augment the process of -representing domain objects in general. For example, support for the _gestures_ +Unlike _representations_, which describe specific ways to represent domain +objects, _representers_ are used to modify or augment the process of +representing domain objects in general. For example, support for the _gestures_ extension category is added by a _representer_. -A representer needs only provide an implementation. When an `mct-representation` -is linked (see [https://docs.angularjs.org/guide/directive ]() or when the -domain object being represented changes, a new _representer_ of each declared -type is instantiated. The constructor arguments for a _representer_ are the same -as the arguments to the link function in an Angular directive: `scope` the -Angular scope for this representation; `element` the jqLite-wrapped -`mct-representation` element, and `attrs` a set of key-value pairs of that -element's attributes. _Representers_ may wish to populate the scope, attach +A representer needs only provide an implementation. When an `mct-representation` +is linked (see https://docs.angularjs.org/guide/directive ) or when the +domain object being represented changes, a new _representer_ of each declared +type is instantiated. The constructor arguments for a _representer_ are the same +as the arguments to the link function in an Angular directive: `scope` the +Angular scope for this representation; `element` the jqLite-wrapped +`mct-representation` element, and `attrs` a set of key-value pairs of that +element's attributes. _Representers_ may wish to populate the scope, attach event listeners to the element, etc. -This implementation must provide a single method, `destroy()`, which will be -invoked when the representer is no longer needed. +This implementation must provide a single method, `destroy()`, which will be +invoked when the representer is no longer needed. ## Roots Category -The extension category `roots` is used to provide root-level domain object -models. Root-level domain objects appear at the top-level of the tree hierarchy. -For example, the _My Items_ folder is added as an extension of this category. +The extension category `roots` is used to provide root-level domain object +models. Root-level domain objects appear at the top-level of the tree hierarchy. +For example, the _My Items_ folder is added as an extension of this category. Extensions of this category should have the following properties: * `id`: The machine-readable identifier for the domaiwn object being exposed. -* `model`: The model, as a JSON object, for the domain object being exposed. +* `model`: The model, as a JSON object, for the domain object being exposed. ## Stylesheets Category -The stylesheets extension category is used to add CSS files to style the -application. Extension definitions for this category should include one +The stylesheets extension category is used to add CSS files to style the +application. Extension definitions for this category should include one property: -* `stylesheetUrl`: Path and filename, including extension, for the stylesheet to -include. This path is relative to the bundle's resources folder (by default, -`res`) +* `stylesheetUrl`: Path and filename, including extension, for the stylesheet to +include. This path is relative to the bundle's resources folder (by default, +`res`) * `theme`: Optional; if present, this stylesheet will only be included if this value matches the `THEME` constant. - -To control the order of CSS files, use priority (see the section on Extension -Definitions above.) + +To control the order of CSS files, use priority (see the section on Extension +Definitions above.) ## Templates Category -The `templates` extension category is used to expose Angular templates under -symbolic identifiers. These can then be utilized using the `mct-include` -directive, which behaves similarly to `ng-include` except that it uses these +The `templates` extension category is used to expose Angular templates under +symbolic identifiers. These can then be utilized using the `mct-include` +directive, which behaves similarly to `ng-include` except that it uses these symbolic identifiers instead of paths. A template's extension definition should include the following properties: -* `key`: The machine-readable name which identifies this template, matched +* `key`: The machine-readable name which identifies this template, matched against the value given to the key attribute of the `mct-include` directive. -* `templateUrl`: The path to the relevant Angular template. This path is -relative to the bundle's resources directory. +* `templateUrl`: The path to the relevant Angular template. This path is +relative to the bundle's resources directory. -Note that, when multiple templates are present with the same key , the one with -the highest priority will be used from `mct-include`. This behavior can be used -to override templates exposed by the platform (to change the logo which appears +Note that, when multiple templates are present with the same key , the one with +the highest priority will be used from `mct-include`. This behavior can be used +to override templates exposed by the platform (to change the logo which appears in the bottom right, for instance.) -Templates do not have implementations. +Templates do not have implementations. ## 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. A type's extension definition should have the following properties: -* `key`: The machine-readable identifier for this domain object type. Will be +* `key`: The machine-readable identifier for this domain object type. Will be stored to and matched against the type property of domain object models. * `name`: The human-readable name for 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 -font set. -* `model`: A domain object model, used as the initial state for created domain +* `glyph`: A single character to be rendered as an icon in Open MCT Web's custom +font set. +* `model`: A domain object model, used as the initial state for created domain objects of this type (before any properties are specified.) -* `features`: Optional; an array of strings describing features of this domain -object type. Currently, only creation is recognized by the platform; this is -used to determine that this type should appear in the Create menu. More -generally, this is used to support the `hasFeature(...)` method of the type -capability. +* `features`: Optional; an array of strings describing features of this domain +object type. Currently, only creation is recognized by the platform; this is +used to determine that this type should appear in the Create menu. More +generally, this is used to support the `hasFeature(...)` method of the type +capability. * `properties`: An array describing individual properties of this domain object -(as should appear in the _Create_ or the _Edit Properties_ dialog.) Each +(as should appear in the _Create_ or the _Edit Properties_ dialog.) Each property is described by an object containing the following properties: - * `control`: The key of the control (see `mct-control` and the `controls` - [extension category](#Controls)) to use for editing this property. - * `property`: A string which will be used as the name of the property in the - domain object's model that the value for this property should be stored - under. If this value should be stored in an object nested within the domain - object model, then property should be specified as an array of strings - identifying these nested objects and, finally, the property itself. - * other properties as appropriate for a control of this type (each - property's definition will also be passed in as the structure for its - control.) See documentation of mct-form for more detail on these + * `control`: The key of the control (see `mct-control` and the `controls` + [extension category](#controls-category)) to use for editing this property. + * `property`: A string which will be used as the name of the property in the + domain object's model that the value for this property should be stored + under. If this value should be stored in an object nested within the domain + object model, then property should be specified as an array of strings + identifying these nested objects and, finally, the property itself. + * other properties as appropriate for a control of this type (each + property's definition will also be passed in as the structure for its + control.) See documentation of mct-form for more detail on these properties. - -Types do not have implementations. - + +Types do not have implementations. + ## Versions Category -The versions extension category is used to introduce line items in Open MCT -Web's About dialog. These should have the following properties: +The versions extension category is used to introduce line items in Open MCT +Web's About dialog. These should have the following properties: -* `name`: The name of this line item, as should appear in the left-hand side of +* `name`: The name of this line item, as should appear in the left-hand side of the list of version information in the About dialog. -* `value`: The value which should appear to the right of the name in the About +* `value`: The value which should appear to the right of the name in the About dialog. -To control the ordering of line items within the About dialog, use `priority`. -(See section on [Extension Definitions](#ExtensionDefinitions) above.) - -This extension category does not have implementations. +To control the ordering of line items within the About dialog, use `priority`. +(See section on [Extensions](#extensions) above.) +This extension category does not have implementations. + ## Views Category -The views extension category is used to determine which options appear to the -user as available views of domain objects of specific types. A view's extension -definition has the same properties as a representation (and views can be +The views extension category is used to determine which options appear to the +user as available views of domain objects of specific types. A view's extension +definition has the same properties as a representation (and views can be utilized via `mct-representation`); additionally: * `name`: The human-readable name for 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 Web's custom 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. -* `needs`: Optional array of strings; if present, this representation is only -applicable for domain objects which have the capabilities identified by these -strings. -* `delegation`: Optional boolean, intended to be used in conjunction with -`needs`; if present, allow required capabilities to be satisfied by means of -capability delegation. (See [Delegation](#Delegation)) -* `toolbar`: Optional; a definition for the toolbar which may appear in a -toolbar when using this view in Edit mode. This should be specified as a -structure for mct-toolbar , with additional properties available for each item in -that toolbar: - * `property`: A property name. This will refer to a property in the view's - current selection; that property on the selected object will be modifiable - as the `ng-model` of the displayed control in the toolbar. If the value of - the property is a function, it will be used as a getter-setter (called with - no arguments to use as a getter, called with a value to use as a setter.) - * `method`: A method to invoke (again, on the selected object) from the - toolbar control. Useful particularly for buttons (which don't edit a single +* `needs`: Optional array of strings; if present, this representation is only +applicable for domain objects which have the capabilities identified by these +strings. +* `delegation`: Optional boolean, intended to be used in conjunction with +`needs`; if present, allow required capabilities to be satisfied by means of +capability delegation. (See [Delegation](#delegation-capability)) +* `toolbar`: Optional; a definition for the toolbar which may appear in a +toolbar when using this view in Edit mode. This should be specified as a +structure for mct-toolbar , with additional properties available for each item in +that toolbar: + * `property`: A property name. This will refer to a property in the view's + current selection; that property on the selected object will be modifiable + as the `ng-model` of the displayed control in the toolbar. If the value of + the property is a function, it will be used as a getter-setter (called with + no arguments to use as a getter, called with a value to use as a setter.) + * `method`: A method to invoke (again, on the selected object) from the + toolbar control. Useful particularly for buttons (which don't edit a single property, necessarily.) -### View Scope +### View Scope -Views do not have implementations, but do get the same properties in scope that -are provided for `representations`. +Views do not have implementations, but do get the same properties in scope that +are provided for `representations`. When a view is in Edit mode, this scope will additionally contain: -* `commit()`: A function which can be invoked to mark any changes to the view's +* `commit()`: A function which can be invoked to mark any changes to the view's configuration as ready to persist. -* `selection`: An object representing the current selection state. +* `selection`: An object representing the current selection state. -#### Selection State +#### Selection State -A view's selection state is, conceptually, a set of JavaScript objects. The -presence of methods/properties on these objects determine which toolbar controls -are visible, and what state they manage and/or behavior they invoke. +A view's selection state is, conceptually, a set of JavaScript objects. The +presence of methods/properties on these objects determine which toolbar controls +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 -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 -may support multiple selected objects.) +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 represent some state within the view. (Future versions of Open MCT Web +may support multiple selected objects.) -The `selection` object made available during Edit mode has the following -methods: +The `selection` object made available during Edit mode has the following +methods: -* `proxy([object])`: Get (or set, if called with an argument) the current view -proxy. -* `select(object)`: Make this object the selected object. -* `deselect()`: Clear the currently selected object. -* `get()`: Get the currently selected object. Returns undefined if there is no +* `proxy([object])`: Get (or set, if called with an argument) the current view +proxy. +* `select(object)`: Make this object the selected object. +* `deselect()`: Clear the currently selected object. +* `get()`: Get the currently selected object. Returns undefined if there is no currently selected object. -* `selected(object)`: Check if the JavaScript object is currently in the -selection set. Returns true if the object is either the currently selected -object, or the current view proxy. -* `all()`: Get an array of all objects in the selection state. Will include -either or both of the view proxy and selected object. +* `selected(object)`: Check if the JavaScript object is currently in the +selection set. Returns true if the object is either the currently selected +object, or the current view proxy. +* `all()`: Get an array of all objects in the selection state. Will include +either or both of the view proxy and selected object. # Directives -Open MCT Web defines several Angular directives that are intended for use both -internally within the platform, and by plugins. +Open MCT Web defines several Angular directives that are intended for use both +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 -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 -both `onbeforeunload` event handling as well as route changes from within +The `mct-before-unload` directive is used to listen for (and prompt for user +confirmation) of navigation changes in the browser. This includes reloading, +following links out of Open MCT Web, or changing routes. It is used to hook into +both `onbeforeunload` event handling as well as route changes from within Angular. -This directive is useable as an attribute. Its value should be an Angular -expression. When an action that would trigger an unload and/or route change -occurs, this Angular expression is evaluated. Its result should be a message to -display to the user to confirm their navigation change; if this expression -evaluates to a falsy value, no message will be displayed. +This directive is useable as an attribute. Its value should be an Angular +expression. When an action that would trigger an unload and/or route change +occurs, this Angular expression is evaluated. Its result should be a message to +display to the user to confirm their navigation change; if this expression +evaluates to a falsy value, no message will be displayed. + +## Chart -## Chart - -The `mct-chart` directive is used to support drawing of simple charts. It is -present to support the Plot view, and its functionality is limited to the +The `mct-chart` directive is used to support drawing of simple charts. It is +present to support the Plot view, and its functionality is limited to the functionality that is relevant for that view. -This directive is used at the element level and takes one attribute, `draw` -which is an Angular expression which will should evaluate to a drawing object. +This directive is used at the element level and takes one attribute, `draw` +which is an Angular expression which will should evaluate to a drawing object. This drawing object should contain the following properties: -* `dimensions`: The size, in logical coordinates, of the chart area. A -two-element array or numbers. -* `origin`: The position, in logical coordinates, of the lower-left corner of -the chart area. A two-element array or numbers. -* `lines`: An array of lines (e.g. as a plot line) to draw, where each line is -expressed as an object containing: - * `buffer`: A Float32Array containing points in the line, in logical - coordinates, in sequential x,y pairs. - * `color`: The color of the line, as a four-element RGBA array, where - each element is a number in the range of 0.0-1.0. - * `points`: The number of points in the line. -* `boxes`: An array of rectangles to draw in the chart area. Each is an object -containing: - * `start`: The first corner of the rectangle, as a two-element array of - numbers, in logical coordinates. - * `end`: The opposite corner of the rectangle, as a two-element array of - numbers, in logical coordinates. color : The color of the line, as a - four-element RGBA array, where each element is a number in the range of - 0.0-1.0. +* `dimensions`: The size, in logical coordinates, of the chart area. A +two-element array or numbers. +* `origin`: The position, in logical coordinates, of the lower-left corner of +the chart area. A two-element array or numbers. +* `lines`: An array of lines (e.g. as a plot line) to draw, where each line is +expressed as an object containing: + * `buffer`: A Float32Array containing points in the line, in logical + coordinates, in sequential x,y pairs. + * `color`: The color of the line, as a four-element RGBA array, where + each element is a number in the range of 0.0-1.0. + * `points`: The number of points in the line. +* `boxes`: An array of rectangles to draw in the chart area. Each is an object +containing: + * `start`: The first corner of the rectangle, as a two-element array of + numbers, in logical coordinates. + * `end`: The opposite corner of the rectangle, as a two-element array of + numbers, in logical coordinates. color : The color of the line, as a + four-element RGBA array, where each element is a number in the range of + 0.0-1.0. -While `mct-chart` is intended to support plots specifically, it does perform -some useful management of canvas objects (e.g. choosing between WebGL and Canvas -2D APIs for drawing based on browser support) so its usage is recommended when -its supported drawing primitives are sufficient for other charting tasks. +While `mct-chart` is intended to support plots specifically, it does perform +some useful management of canvas objects (e.g. choosing between WebGL and Canvas +2D APIs for drawing based on browser support) so its usage is recommended when +its supported drawing primitives are sufficient for other charting tasks. + +## Container -## Container - -The `mct-container` is similar to the `mct-include` directive insofar as it allows -templates to be referenced by symbolic keys instead of by URL. Unlike +The `mct-container` is similar to the `mct-include` directive insofar as it allows +templates to be referenced by symbolic keys instead of by URL. Unlike `mct-include` it supports transclusion. -Unlike `mct-include` `mct-container` accepts a key as a plain string attribute, +Unlike `mct-include` `mct-container` accepts a key as a plain string attribute, instead of as an Angular expression. ## Control -The `mct-control` directive is used to display user input elements. Several -controls are included with the platform to wrap default input types. This -directive is primarily intended for internal use by the `mct-form` and -`mct-toolbar` directives. +The `mct-control` directive is used to display user input elements. Several +controls are included with the platform to wrap default input types. This +directive is primarily intended for internal use by the `mct-form` and +`mct-toolbar` directives. -When using `mct-control` the attributes `ng-model` `ng-disabled` -`ng-required` and `ng-pattern` may also be used. These have the usual meaning -(as they would for an input element) except for `ng-model`; when used, it will -actually be `ngModel[field]` (see below) that is two-way bound by this control. -This allows `mct-control` elements to more easily delegate to other -`mct-control` instances, and also facilitates usage for generated forms. +When using `mct-control` the attributes `ng-model` `ng-disabled` +`ng-required` and `ng-pattern` may also be used. These have the usual meaning +(as they would for an input element) except for `ng-model`; when used, it will +actually be `ngModel[field]` (see below) that is two-way bound by this control. +This allows `mct-control` elements to more easily delegate to other +`mct-control` instances, and also facilitates usage for generated forms. -This directive supports the following additional attributes, all specified as +This directive supports the following additional attributes, all specified as Angular expressions: -* `key`: A machine-readable identifier for the specific type of control to +* `key`: A machine-readable identifier for the specific type of control to display. * `options`: A set of options to display in this control. -* `structure`: In practice, contains the definition object which describes this -form row or toolbar item. Used to pass additional control-specific parameters. -* `field`: The field in the `ngModel` under which to read/store the property -associated with this control. +* `structure`: In practice, contains the definition object which describes this +form row or toolbar item. Used to pass additional control-specific parameters. +* `field`: The field in the `ngModel` under which to read/store the property +associated with this control. ## Drag -The `mct-drag` directive is used to support drag-based gestures on HTML -elements. Note that this is not 'drag' in the 'drag-and-drop' sense, but 'drag' -in the more general 'mouse down, mouse move, mouse up' sense. +The `mct-drag` directive is used to support drag-based gestures on HTML +elements. Note that this is not 'drag' in the 'drag-and-drop' sense, but 'drag' +in the more general 'mouse down, mouse move, mouse up' sense. -This takes the form of three attributes: +This takes the form of three attributes: * `mct-drag`: An Angular expression to evaluate during drag movement. * `mct-drag-down`: An Angular expression to evaluate when the drag starts. * `mct-drag-up`: An Angular expression to evaluate when the drag ends. -In each case, a variable `delta` will be provided to the expression; this is a -two-element array or the horizontal and vertical pixel offset of the current -mouse position relative to the mouse position where dragging began. +In each case, a variable `delta` will be provided to the expression; this is a +two-element array or the horizontal and vertical pixel offset of the current +mouse position relative to the mouse position where dragging began. -## Form +## Form -The `mct-form` directive is used to generate forms using a declarative structure, -and to gather back user input. It is applicable at the element level and -supports the following attributes: +The `mct-form` directive is used to generate forms using a declarative structure, +and to gather back user input. It is applicable at the element level and +supports the following attributes: -* `ng-model`: The object which should contain the full form input. Individual -fields in this model are bound to individual controls; the names used for these +* `ng-model`: The object which should contain the full form input. Individual +fields in this model are bound to individual controls; the names used for these fields are provided in the form structure (see below). -* `structure`: The structure of the form; e.g. sections, rows, their names, and -so forth. The value of this attribute should be an Angular expression. -* `name`: The name in the containing scope under which to publish form -"meta-state", e.g. `$valid` `$dirty` etc. This is as the behavior of `ng-form`. -Passed as plain text in the attribute. +* `structure`: The structure of the form; e.g. sections, rows, their names, and +so forth. The value of this attribute should be an Angular expression. +* `name`: The name in the containing scope under which to publish form +"meta-state", e.g. `$valid` `$dirty` etc. This is as the behavior of `ng-form`. +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 -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 -property. Input from this form is two-way bound to the object passed via -`ng-model`. +Forms in Open MCT Web have a common structure to permit consistent display. A +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 +property. Input from this form is two-way bound to the object passed via +`ng-model`. A form's structure is represented by a JavaScript object in the following form: - - { - "name": ... title to display for the form, as a string ..., + + { + "name": ... title to display for the form, as a string ..., "sections": [ - { - "name": ... title to display for the section ..., - "rows": [ - { + { + "name": ... title to display for the section ..., + "rows": [ + { "name": ... title to display for this row ..., - "control": ... symbolic key for the control ..., - "key": ... field name in ng-model ... - "pattern": ... optional, reg exp to match against ... - "required": ... optional boolean ... - "options": [ - "name": ... name to display (e.g. in a select) ..., - "value": ... value to store in the model ... - ] - }, - ... and other rows ... - ] - }, - ... and other sections ... - ] - } + "control": ... symbolic key for the control ..., + "key": ... field name in ng-model ... + "pattern": ... optional, reg exp to match against ... + "required": ... optional boolean ... + "options": [ + "name": ... name to display (e.g. in a select) ..., + "value": ... value to store in the model ... + ] + }, + ... and other rows ... + ] + }, + ... and other sections ... + ] + } -Note that `pattern` may be specified as a string, to simplify storing for -structures as JSON when necessary. The string should be given in a form -appropriate to pass to a `RegExp` constructor. +Note that `pattern` may be specified as a string, to simplify storing for +structures as JSON when necessary. The string should be given in a form +appropriate to pass to a `RegExp` constructor. -### Form Controls +### Form Controls -A few standard control types are included in the platform/forms bundle: +A few standard control types are included in the platform/forms bundle: -* `textfield`: An area to enter plain text. -* `select`: A drop-down list of options. -* `checkbox`: A box which may be checked/unchecked. -* `color`: A color picker. -* `button`: A button. -* `datetime`: An input for UTC date/time entry; gives result as a UNIX -timestamp, in milliseconds since start of 1970, UTC. +* `textfield`: An area to enter plain text. +* `select`: A drop-down list of options. +* `checkbox`: A box which may be checked/unchecked. +* `color`: A color picker. +* `button`: A button. +* `datetime`: An input for UTC date/time entry; gives result as a UNIX +timestamp, in milliseconds since start of 1970, UTC. -## Include +## Include -The `mct-include` directive is similar to ng-include , except that it takes a -symbolic identifier for a template instead of a URL. Additionally, templates -included via mct-include will have an isolated scope. +The `mct-include` directive is similar to ng-include , except that it takes a +symbolic identifier for a template instead of a URL. Additionally, templates +included via mct-include will have an isolated scope. -The directive should be used at the element level and supports the following +The directive should be used at the element level and supports the following +attributes, all of which are specified as Angular expressions: + +* `key`: Machine-readable identifier for the template (of extension category +templates ) to be displayed. +* `ng-model`: _Optional_; will be passed into the template's scope as `ngModel`. +Intended usage is for two-way bound user input. +* `parameters`: _Optional_; will be passed into the template's scope as +parameters. Intended usage is for template-specific display parameters. + +## Representation + +The `mct-representation` directive is used to include templates which +specifically represent domain objects. Usage is similar to `mct-include`. + +The directive should be used at the element level and supports the following attributes, all of which are specified as Angular expressions: -* `key`: Machine-readable identifier for the template (of extension category -templates ) to be displayed. -* `ng-model`: _Optional_; will be passed into the template's scope as `ngModel`. -Intended usage is for two-way bound user input. -* `parameters`: _Optional_; will be passed into the template's scope as -parameters. Intended usage is for template-specific display parameters. +* `key`: Machine-readable identifier for the representation (of extension +category _representations_ or _views_ ) to be displayed. +* `mct-object`: The domain object being represented. +* `ng-model`: Optional; will be passed into the template's scope as `ngModel`. +Intended usage is for two-way bound user input. +* `parameters`: Optional; will be passed into the template's scope as +parameters . Intended usage is for template-specific display parameters. -## Representation +## Resize -The `mct-representation` directive is used to include templates which -specifically represent domain objects. Usage is similar to `mct-include`. - -The directive should be used at the element level and supports the following -attributes, all of which are specified as Angular expressions: - -* `key`: Machine-readable identifier for the representation (of extension -category _representations_ or _views_ ) to be displayed. -* `mct-object`: The domain object being represented. -* `ng-model`: Optional; will be passed into the template's scope as `ngModel`. -Intended usage is for two-way bound user input. -* `parameters`: Optional; will be passed into the template's scope as -parameters . Intended usage is for template-specific display parameters. - -## Resize - -The `mct-resize` directive is used to monitor the size of an HTML element. It is -specified as an attribute whose value is an Angular expression that will be -evaluated when the size of the HTML element changes. This expression will be -provided a single variable, `bounds` which is an object containing two +The `mct-resize` directive is used to monitor the size of an HTML element. It is +specified as an attribute whose value is an Angular expression that will be +evaluated when the size of the HTML element changes. This expression will be +provided a single variable, `bounds` which is an object containing two properties, `width` and `height` describing the size in pixels of the element. -When using this directive, an attribute `mct-resize-interval` may optionally be -provided. Its value is an Angular expression describing the number of -milliseconds to wait before next checking the size of the HTML element; this -expression is evaluated when the directive is linked and reevaluated whenever +When using this directive, an attribute `mct-resize-interval` may optionally be +provided. Its value is an Angular expression describing the number of +milliseconds to wait before next checking the size of the HTML element; this +expression is evaluated when the directive is linked and reevaluated whenever the size is checked. -## Scroll +## Scroll -The `mct-scroll-x` and `mct-scroll-y` directives are used to both monitor and -control the horizontal and vertical scroll bar state of an element, -respectively. They are intended to be used as attributes whose values are +The `mct-scroll-x` and `mct-scroll-y` directives are used to both monitor and +control the horizontal and vertical scroll bar state of an element, +respectively. They are intended to be used as attributes whose values are assignable Angular expressions which two-way bind to the scroll bar state. ## Toolbar -The `mct-toolbar` directive is used to generate toolbars using a declarative -structure, and to gather back user input. It is applicable at the element level -and supports the following attributes: +The `mct-toolbar` directive is used to generate toolbars using a declarative +structure, and to gather back user input. It is applicable at the element level +and supports the following attributes: -* `ng-model`: The object which should contain the full toolbar input. Individual -fields in this model are bound to individual controls; the names used for these -fields are provided in the form structure (see below). -* `structure`: The structure of the toolbar; e.g. sections, rows, their names, and +* `ng-model`: The object which should contain the full toolbar input. Individual +fields in this model are bound to individual controls; the names used for these +fields are provided in the form structure (see below). +* `structure`: The structure of the toolbar; e.g. sections, rows, their names, and so forth. The value of this attribute should be an Angular expression. -* `name`: The name in the containing scope under which to publish form -"meta-state", e.g. `$valid`, `$dirty` etc. This is as the behavior of -`ng-form`. Passed as plain text in the attribute. +* `name`: The name in the containing scope under which to publish form +"meta-state", e.g. `$valid`, `$dirty` etc. This is as the behavior of +`ng-form`. Passed as plain text in the attribute. -Toolbars support the same control options as forms. +Toolbars support the same control options as forms. -### Toolbar Structure +### Toolbar Structure -A toolbar's structure is defined similarly to forms, except instead of rows -there are items . +A toolbar's structure is defined similarly to forms, except instead of rows +there are items . - { - "name": ... title to display for the form, as a string ..., - "sections": [ - { - "name": ... title to display for the section ..., - "items": [ - { - "name": ... title to display for this row ..., - "control": ... symbolic key for the control ..., - "key": ... field name in ng-model ... - "pattern": ... optional, reg exp to match against ... - "required": ... optional boolean ... - "options": [ - "name": ... name to display (e.g. in a select) ..., - "value": ... value to store in the model ... - ], - "disabled": ... true if control should be disabled ... - "size": ... size of the control (for textfields) ... - "click": ... function to invoke (for buttons) ... - "glyph": ... glyph to display (for buttons) ... - "text": ... text within control (for buttons) ... - }, - ... and other rows ... - ] - }, - ... and other sections ... - ] + { + "name": ... title to display for the form, as a string ..., + "sections": [ + { + "name": ... title to display for the section ..., + "items": [ + { + "name": ... title to display for this row ..., + "control": ... symbolic key for the control ..., + "key": ... field name in ng-model ... + "pattern": ... optional, reg exp to match against ... + "required": ... optional boolean ... + "options": [ + "name": ... name to display (e.g. in a select) ..., + "value": ... value to store in the model ... + ], + "disabled": ... true if control should be disabled ... + "size": ... size of the control (for textfields) ... + "click": ... function to invoke (for buttons) ... + "glyph": ... glyph to display (for buttons) ... + "text": ... text within control (for buttons) ... + }, + ... and other rows ... + ] + }, + ... and other sections ... + ] } -# Services +# Services -The Open MCT Web platform provides a variety of services which can be retrieved -and utilized via dependency injection. These services fall into two categories: +The Open MCT Web platform provides a variety of services which can be retrieved +and utilized via dependency injection. These services fall into two categories: -* _Composite Services_ are defined by a set of components extensions; plugins may -introduce additional components with matching interfaces to extend or augment -the functionality of the composed service. (See the Framework section on -Composite Services.) -* _Other services_ which are defined as standalone service objects; these can be -utilized by plugins but are not intended to be modified or augmented. +* _Composite Services_ are defined by a set of components extensions; plugins may +introduce additional components with matching interfaces to extend or augment +the functionality of the composed service. (See the Framework section on +Composite Services.) +* _Other services_ which are defined as standalone service objects; these can be +utilized by plugins but are not intended to be modified or augmented. ## Composite Type Services -This section describes the composite services exposed by Open MCT Web, -specifically focusing on their interface and contract. +This section describes the composite services exposed by Open MCT Web, +specifically focusing on their interface and contract. + +In many cases, the platform will include a provider for a service which consumes +a specific extension category; for instance, the `actionService` depends on +`actions[]` and will expose available actions based on the rules defined for +that extension category. -In many cases, the platform will include a provider for a service which consumes -a specific extension category; for instance, the `actionService` depends on -`actions[]` and will expose available actions based on the rules defined for -that extension category. - -In these cases, it will usually be simpler to add a new extension of a given -category (e.g. of category `actions`) even when the same behavior could be -introduced by a service component (e.g. an extension of category `components` -where `provides` is `actionService` and `type` is `provider`.) - -Occasionally, the extension category does not provide enough expressive power to -achieve a desired result. For instance, the Create menu is populated with -`create` actions, where one such action exists for each creatable type. Since -the framework does not provide a declarative means to introduce a new action per -type declaratively, the platform implements this explicitly in an `actionService` -component of type `provider`. Plugins may use a similar approach when the normal -extension mechanism is insufficient to achieve a desired result. +In these cases, it will usually be simpler to add a new extension of a given +category (e.g. of category `actions`) even when the same behavior could be +introduced by a service component (e.g. an extension of category `components` +where `provides` is `actionService` and `type` is `provider`.) +Occasionally, the extension category does not provide enough expressive power to +achieve a desired result. For instance, the Create menu is populated with +`create` actions, where one such action exists for each creatable type. Since +the framework does not provide a declarative means to introduce a new action per +type declaratively, the platform implements this explicitly in an `actionService` +component of type `provider`. Plugins may use a similar approach when the normal +extension mechanism is insufficient to achieve a desired result. + ### Action Service -The [Action Service](../architecture/platform#action-service) (`actionService`) -provides `Action` instances which are applicable in specific contexts. See Core -API for additional notes on the interface for actions. The `actionService` has -the following interface: +The [Action Service](../architecture/platform.md#action-service) +(`actionService`) +provides `Action` instances which are applicable in specific contexts. See Core +API for additional notes on the interface for actions. The `actionService` has +the following interface: -* `getActions(context)`: Returns an array of Action objects which are applicable -in the specified action context. +* `getActions(context)`: Returns an array of Action objects which are applicable +in the specified action context. -### Capability Service +### Capability Service -The [Capability Service](../architecture/platform#capability-service) (`capabilityService`) -provides constructors for capabilities which will be exposed for a given domain -object. +The [Capability Service](../architecture/platform.md#capability-service) +(`capabilityService`) +provides constructors for capabilities which will be exposed for a given domain +object. -The capabilityService has the following interface: +The capabilityService has the following interface: -* `getCapabilities(model)`: Returns a an object containing key-value pairs, -representing capabilities which should be exposed by the domain object with this -model. Keys in this object are the capability keys (as used in a -`getCapability(...)` call) and values are either: - * Functions, in which case they will be used as constructors, which will - receive the domain object instance to which the capability applies as their - sole argument.The resulting object will be provided as the result of a - domain object's `getCapability(...)` call. Note that these instances are cached - by each object, but may be recreated when an object is mutated. - * Other objects, which will be used directly as the result of a domain - object's `getCapability(...)` call. +* `getCapabilities(model)`: Returns a an object containing key-value pairs, +representing capabilities which should be exposed by the domain object with this +model. Keys in this object are the capability keys (as used in a +`getCapability(...)` call) and values are either: + * Functions, in which case they will be used as constructors, which will + receive the domain object instance to which the capability applies as their + sole argument.The resulting object will be provided as the result of a + domain object's `getCapability(...)` call. Note that these instances are cached + by each object, but may be recreated when an object is mutated. + * Other objects, which will be used directly as the result of a domain + object's `getCapability(...)` call. -### Dialog Service +### Dialog Service -The `dialogService` provides a means for requesting user input via a modal -dialog. It has the following interface: +The `dialogService` provides a means for requesting user input via a modal +dialog. It has the following interface: -* `getUserInput(formStructure, formState)`: Prompt the user to fill out a form. -The first argument describes the form's structure (as will be passed to - mct-form ) while the second argument contains the initial state of that form. -This returns a Promise for the state of the form after the user has filled it -in; this promise will be rejected if the user cancels input. -* `getUserChoice(dialogStructure)`: Prompt the user to make a single choice from -a set of options, which (in the platform implementation) will be expressed as -buttons in the displayed dialog. Returns a Promise for the user's choice, which -will be rejected if the user cancels input. +* `getUserInput(formStructure, formState)`: Prompt the user to fill out a form. +The first argument describes the form's structure (as will be passed to + mct-form ) while the second argument contains the initial state of that form. +This returns a Promise for the state of the form after the user has filled it +in; this promise will be rejected if the user cancels input. +* `getUserChoice(dialogStructure)`: Prompt the user to make a single choice from +a set of options, which (in the platform implementation) will be expressed as +buttons in the displayed dialog. Returns a Promise for the user's choice, which +will be rejected if the user cancels input. -### Dialog Structure +### Dialog Structure -The object passed as the `dialogStructure` to `getUserChoice` should have the +The object passed as the `dialogStructure` to `getUserChoice` should have the following properties: -* `title`: The title to display at the top of the dialog. -* `hint`: Short message to display below the title. -* `template`: Identifying key (as will be passed to mct-include ) for the -template which will be used to populate the inner area of the dialog. -* `model`: Model to pass in the ng-model attribute of mct-include . -* `parameters`: Parameters to pass in the parameters attribute of mct-include . -* `options`: An array of options describing each button at the bottom. Each +* `title`: The title to display at the top of the dialog. +* `hint`: Short message to display below the title. +* `template`: Identifying key (as will be passed to mct-include ) for the +template which will be used to populate the inner area of the dialog. +* `model`: Model to pass in the ng-model attribute of mct-include . +* `parameters`: Parameters to pass in the parameters attribute of mct-include . +* `options`: An array of options describing each button at the bottom. Each option may have the following properties: - * `name`: Human-readable name to display in the button. - * `key`: Machine-readable key, to pass as the result of the resolved promise - when clicked. + * `name`: Human-readable name to display in the button. + * `key`: Machine-readable key, to pass as the result of the resolved promise + when clicked. * `description`: Description to show in tooltip on hover. -### Domain Object Service +### Domain Object Service The [Object Service](../architecture/platform.md#object-service) (`objectService`) provides domain object instances. It has the following interface: -* `getObjects(ids)`: For the provided array of domain object identifiers, -returns a Promise for an object containing key-value pairs, where keys are -domain object identifiers and values are corresponding DomainObject instances. -Note that the result may contain a superset or subset of the objects requested. +* `getObjects(ids)`: For the provided array of domain object identifiers, +returns a Promise for an object containing key-value pairs, where keys are +domain object identifiers and values are corresponding DomainObject instances. +Note that the result may contain a superset or subset of the objects requested. -### Gesture Service +### Gesture Service -The `gestureService` is used to attach gestures (see extension category gestures) +The `gestureService` is used to attach gestures (see extension category gestures) to representations. It has the following interface: -* `attachGestures(element, domainObject, keys)`: Attach gestures specified by -the provided gesture keys (an array of strings) to this jqLite-wrapped HTML -element , which represents the specified domainObject . Returns an object with a -single method `destroy()`, to be invoked when it is time to detach these -gestures. +* `attachGestures(element, domainObject, keys)`: Attach gestures specified by +the provided gesture keys (an array of strings) to this jqLite-wrapped HTML +element , which represents the specified domainObject . Returns an object with a +single method `destroy()`, to be invoked when it is time to detach these +gestures. -### Model Service +### Model Service -The [Model Service](../architecture/platform.md#model-service) (`modelService`) -provides domain object models. It has the following interface: +The [Model Service](../architecture/platform.md#model-service) (`modelService`) +provides domain object models. It has the following interface: -* `getModels(ids)`: For the provided array of domain object identifiers, returns -a Promise for an object containing key-value pairs, where keys are domain object -identifiers and values are corresponding domain object models. Note that the -result may contain a superset or subset of the models requested. +* `getModels(ids)`: For the provided array of domain object identifiers, returns +a Promise for an object containing key-value pairs, where keys are domain object +identifiers and values are corresponding domain object models. Note that the +result may contain a superset or subset of the models requested. -### Persistence Service +### Persistence Service The [Persistence Service](../architecture/platform.md#persistence-service) (`persistenceService`) -provides the ability to load/store JavaScript objects -(presumably serializing/deserializing to JSON in the process.) This is used -primarily to store domain object models. It has the following interface: +provides the ability to load/store JavaScript objects +(presumably serializing/deserializing to JSON in the process.) This is used +primarily to store domain object models. It has the following interface: -* `listSpaces()`: Returns a Promise for an array of strings identifying the -different persistence spaces this service supports. Spaces are intended to be -used to distinguish between different underlying persistence stores, to allow -these to live side by side. -* `listObjects()`: Returns a Promise for an array of strings identifying all -documents stored in this persistence service. -* `createObject(space, key, value)`: Create a new document in the specified -persistence space , identified by the specified key , the contents of which shall -match the specified value . Returns a promise that will be rejected if creation -fails. -* `readObject(space, key)`: Read an existing document in the specified -persistence space , identified by the specified key . Returns a promise for the -specified document; this promise will resolve to undefined if the document does -not exist. -* `updateObject(space, key, value)`: Update an existing document in the -specified persistence space , identified by the specified key , such that its -contents match the specified value . Returns a promise that will be rejected if -the update fails. -* `deleteObject(space, key)`: Delete an existing document from the specified -persistence space , identified by the specified key . Returns a promise which will -be rejected if deletion fails. +* `listSpaces()`: Returns a Promise for an array of strings identifying the +different persistence spaces this service supports. Spaces are intended to be +used to distinguish between different underlying persistence stores, to allow +these to live side by side. +* `listObjects()`: Returns a Promise for an array of strings identifying all +documents stored in this persistence service. +* `createObject(space, key, value)`: Create a new document in the specified +persistence space , identified by the specified key , the contents of which shall +match the specified value . Returns a promise that will be rejected if creation +fails. +* `readObject(space, key)`: Read an existing document in the specified +persistence space , identified by the specified key . Returns a promise for the +specified document; this promise will resolve to undefined if the document does +not exist. +* `updateObject(space, key, value)`: Update an existing document in the +specified persistence space , identified by the specified key , such that its +contents match the specified value . Returns a promise that will be rejected if +the update fails. +* `deleteObject(space, key)`: Delete an existing document from the specified +persistence space , identified by the specified key . Returns a promise which will +be rejected if deletion fails. -### Policy Service +### Policy Service -The [Policy Service](../architecture/platform.md#policy-service) (`policyService`) -may be used to determine whether or not certain behaviors are -allowed within the application. It has the following interface: +The [Policy Service](../architecture/platform.md#policy-service) (`policyService`) +may be used to determine whether or not certain behaviors are +allowed within the application. It has the following interface: + +* `allow(category, candidate, context, [callback])`: Check if this decision +should be allowed. Returns a boolean. Its arguments are interpreted as: + * `category`: A string identifying which kind of decision is being made. See + the [section on Categories](#policy-categories) for categories supported by + the platform; plugins may define and utilize policies of additional + categories, as well. + * `candidate`: An object representing the thing which shall or shall not be + allowed. Usually, this will be an instance of an extension of the category + defined above. This does need to be the case; additional policies which are + not specific to any extension may also be defined and consulted using unique + category identifiers. In this case, the type of the object delivered for the + candidate may be unique to the policy type. + * `context`: An object representing the context in which the decision is + occurring. Its contents are specific to each policy category. + * `callback`: Optional; a function to call if the policy decision is rejected. + This function will be called with the message string (which may be + undefined) of whichever individual policy caused the operation to fail. -* `allow(category, candidate, context, [callback])`: Check if this decision -should be allowed. Returns a boolean. Its arguments are interpreted as: - * `category`: A string identifying which kind of decision is being made. See - the [section on Categories](#PolicyCategories) for categories supported by - the platform; plugins may define and utilize policies of additional - categories, as well. - * `candidate`: An object representing the thing which shall or shall not be - allowed. Usually, this will be an instance of an extension of the category - defined above. This does need to be the case; additional policies which are - not specific to any extension may also be defined and consulted using unique - category identifiers. In this case, the type of the object delivered for the - candidate may be unique to the policy type. - * `context`: An object representing the context in which the decision is - occurring. Its contents are specific to each policy category. - * `callback`: Optional; a function to call if the policy decision is rejected. - This function will be called with the message string (which may be - undefined) of whichever individual policy caused the operation to fail. - -### Telemetry Service +### Telemetry Service The [Telemetry Service](../architecture/platform.md#telemetry-service) (`telemetryService`) -is used to acquire telemetry data. See the section on -Telemetry in Core API for more information on how both the arguments and -responses of this service are structured. +is used to acquire telemetry data. See the section on +Telemetry in Core API for more information on how both the arguments and +responses of this service are structured. -When acquiring telemetry for display, it is recommended that the -`telemetryHandler` service be used instead of this service. The -`telemetryHandler` has additional support for subscribing to and requesting -telemetry data associated with domain objects or groups of domain objects. See -the [Other Services](#Other-Services) section for more information. +When acquiring telemetry for display, it is recommended that the +`telemetryHandler` service be used instead of this service. The +`telemetryHandler` has additional support for subscribing to and requesting +telemetry data associated with domain objects or groups of domain objects. See +the [Other Services](#other-services) section for more information. The `telemetryService` has the following interface: -* `requestTelemetry(requests)`: Issue a request for telemetry, matching the -specified telemetry requests . Returns a _ Promise _ for a telemetry response +* `requestTelemetry(requests)`: Issue a request for telemetry, matching the +specified telemetry requests . Returns a _ Promise _ for a telemetry response object. -* `subscribe(callback, requests)`: Subscribe to real-time updates for telemetry, -matching the specified `requests`. The specified `callback` will be invoked with -telemetry response objects as they become available. This method returns a -function which can be invoked to terminate the subscription. +* `subscribe(callback, requests)`: Subscribe to real-time updates for telemetry, +matching the specified `requests`. The specified `callback` will be invoked with +telemetry response objects as they become available. This method returns a +function which can be invoked to terminate the subscription. -### Type Service +### Type Service -The [Type Service](../architecture/platform.md#type-service) (`typeService`) exposes +The [Type Service](../architecture/platform.md#type-service) (`typeService`) exposes domain object types. It has the following interface: -* `listTypes()`: Returns all domain object types supported in the application, +* `listTypes()`: Returns all domain object types supported in the application, as an array of `Type` instances. -* `getType(key)`: Returns the `Type` instance identified by the provided key, or -undefined if no such type exists. +* `getType(key)`: Returns the `Type` instance identified by the provided key, or +undefined if no such type exists. -### View Service +### View Service -The [View Service](../architecture/platform.md#view-service) (`viewService`) exposes +The [View Service](../architecture/platform.md#view-service) (`viewService`) exposes definitions for views of domain objects. It has the following interface: - -* `getViews(domainObject)`: Get an array of extension definitions of category -`views` which are valid and applicable to the specified `domainObject`. - + +* `getViews(domainObject)`: Get an array of extension definitions of category +`views` which are valid and applicable to the specified `domainObject`. + ## Other Services -### Drag and Drop +### Drag and Drop -The `dndService` provides information about the content of an active -drag-and-drop gesture within the application. It is intended to complement the -`DataTransfer` API of HTML5 drag-and-drop, by providing access to non-serialized -JavaScript objects being dragged, as well as by permitting inspection during -drag (which is normally prohibited by browsers for security reasons.) +The `dndService` provides information about the content of an active +drag-and-drop gesture within the application. It is intended to complement the +`DataTransfer` API of HTML5 drag-and-drop, by providing access to non-serialized +JavaScript objects being dragged, as well as by permitting inspection during +drag (which is normally prohibited by browsers for security reasons.) -The `dndService` has the following methods: +The `dndService` has the following methods: -* `setData(key, value)`: Set drag data associated with a given type, specified -by the `key` argument. -* `getData(key)`: Get drag data associated with a given type, specified by the -`key` argument. -* `removeData(key)`: Clear drag data associated with a given type, specified by -the `key` argument. +* `setData(key, value)`: Set drag data associated with a given type, specified +by the `key` argument. +* `getData(key)`: Get drag data associated with a given type, specified by the +`key` argument. +* `removeData(key)`: Clear drag data associated with a given type, specified by +the `key` argument. -### Navigation - -The _Navigation_ service provides information about the current navigation state -of the application; that is, which object is the user currently viewing? This -service merely tracks this state and notifies listeners; it does not take -immediate action when navigation changes, although its listeners might. +### Navigation + +The _Navigation_ service provides information about the current navigation state +of the application; that is, which object is the user currently viewing? This +service merely tracks this state and notifies listeners; it does not take +immediate action when navigation changes, although its listeners might. The `navigationService` has the following methods: -* `getNavigation()`: Get the current navigation state. Returns a `DomainObject`. -* `setNavigation(domainObject)`: Set the current navigation state. Returns a -`DomainObject`. -* `addListener(callback)`: Listen for changes in navigation state. The provided -`callback` should be a `Function` which takes a single `DomainObject` as an -argument. -* `removeListener(callback)`: Stop listening for changes in navigation state. -The provided `callback` should be a `Function` which has previously been passed +* `getNavigation()`: Get the current navigation state. Returns a `DomainObject`. +* `setNavigation(domainObject)`: Set the current navigation state. Returns a +`DomainObject`. +* `addListener(callback)`: Listen for changes in navigation state. The provided +`callback` should be a `Function` which takes a single `DomainObject` as an +argument. +* `removeListener(callback)`: Stop listening for changes in navigation state. +The provided `callback` should be a `Function` which has previously been passed to addListener . -### Now +### Now -The service now is a function which acts as a simple wrapper for `Date.now()`. -It is present mainly so that this functionality may be more easily mocked in -tests for scripts which use the current time. +The service now is a function which acts as a simple wrapper for `Date.now()`. +It is present mainly so that this functionality may be more easily mocked in +tests for scripts which use the current time. -### Telemetry Formatter +### Telemetry Formatter -The _Telemetry Formatter_ is a utility for formatting domain and range values -read from a telemetry series. +The _Telemetry Formatter_ is a utility for formatting domain and range values +read from a telemetry series. -`telemetryFormatter` has the following methods: +`telemetryFormatter` has the following methods: -* `formatDomainValue(value)`: Format the provided domain value (which will be -assumed to be a timestamp) for display; returns a string. -* `formatRangeValue(value)`: Format the provided range value (a number) for +* `formatDomainValue(value)`: Format the provided domain value (which will be +assumed to be a timestamp) for display; returns a string. +* `formatRangeValue(value)`: Format the provided range value (a number) for display; returns a string. -### Telemetry Handler +### Telemetry Handler -The _Telemetry Handler_ is a utility for retrieving telemetry data associated -with domain objects; it is particularly useful for dealing with cases where the -telemetry capability is delegated to contained objects (as occurs -in _Telemetry Panels_.) +The _Telemetry Handler_ is a utility for retrieving telemetry data associated +with domain objects; it is particularly useful for dealing with cases where the +telemetry capability is delegated to contained objects (as occurs +in _Telemetry Panels_.) -The `telemetryHandler` has the following methods: +The `telemetryHandler` has the following methods: -* `handle(domainObject, callback, [lossless])`: Subscribe to and issue future -requests for telemetry associated with the provided `domainObject`, invoking the -provided callback function when streaming data becomes available. Returns a -`TelemetryHandle` (see below.) +* `handle(domainObject, callback, [lossless])`: Subscribe to and issue future +requests for telemetry associated with the provided `domainObject`, invoking the +provided callback function when streaming data becomes available. Returns a +`TelemetryHandle` (see below.) -#### Telemetry Handle +#### Telemetry Handle -A TelemetryHandle has the following methods: +A TelemetryHandle has the following methods: -* `getTelemetryObjects()`: Get the domain objects (as a `DomainObject[]`) that -have a telemetry capability and are being handled here. Note that these are -looked up asynchronously, so this method may return an empty array if the -initial lookup is not yet completed. -* `promiseTelemetryObjects()`: As `getTelemetryObjects()`, but returns a Promise -that will be fulfilled when the lookup is complete. -* `unsubscribe()`: Unsubscribe to streaming telemetry updates associated with -this handle. -* `getDomainValue(domainObject)`: Get the most recent domain value received via -a streaming update for the specified `domainObject`. -* `getRangeValue(domainObject)`: Get the most recent range value received via a -streaming update for the specified `domainObject`. -* `getMetadata()`: Get metadata (as reported by the `getMetadata()` method of a -telemetry capability) associated with telemetry-providing domain objects. -Returns an array, which is in the same order as getTelemetryObjects() . -* `request(request, callback)`: Issue a new request for historical telemetry -data. The provided callback will be invoked when new data becomes available, -which may occur multiple times (e.g. if there are multiple domain objects.) It -will be invoked with the DomainObject for which a new series is available, and -the TelemetrySeries itself, in that order. -* `getSeries(domainObject)`: Get the latest `TelemetrySeries` (as resulted from +* `getTelemetryObjects()`: Get the domain objects (as a `DomainObject[]`) that +have a telemetry capability and are being handled here. Note that these are +looked up asynchronously, so this method may return an empty array if the +initial lookup is not yet completed. +* `promiseTelemetryObjects()`: As `getTelemetryObjects()`, but returns a Promise +that will be fulfilled when the lookup is complete. +* `unsubscribe()`: Unsubscribe to streaming telemetry updates associated with +this handle. +* `getDomainValue(domainObject)`: Get the most recent domain value received via +a streaming update for the specified `domainObject`. +* `getRangeValue(domainObject)`: Get the most recent range value received via a +streaming update for the specified `domainObject`. +* `getMetadata()`: Get metadata (as reported by the `getMetadata()` method of a +telemetry capability) associated with telemetry-providing domain objects. +Returns an array, which is in the same order as getTelemetryObjects() . +* `request(request, callback)`: Issue a new request for historical telemetry +data. The provided callback will be invoked when new data becomes available, +which may occur multiple times (e.g. if there are multiple domain objects.) It +will be invoked with the DomainObject for which a new series is available, and +the TelemetrySeries itself, in that order. +* `getSeries(domainObject)`: Get the latest `TelemetrySeries` (as resulted from a previous `request(...)` call) available for this domain object. # Models -Domain object models in Open MCT Web are JavaScript objects describing the -persistent state of the domain objects they describe. Their contents include a -mix of commonly understood metadata attributes; attributes which are recognized -by and/or determine the applicability of specific extensions; and properties -specific to given types. +Domain object models in Open MCT Web are JavaScript objects describing the +persistent state of the domain objects they describe. Their contents include a +mix of commonly understood metadata attributes; attributes which are recognized +by and/or determine the applicability of specific extensions; and properties +specific to given types. -## General Metadata +## General Metadata -Some properties of domain object models have a ubiquitous meaning through Open -MCT Web and can be utilized directly: +Some properties of domain object models have a ubiquitous meaning through Open +MCT Web and can be utilized directly: * `name`: The human-readable name of the domain object. -## Extension-specific Properties +## Extension-specific Properties -Other properties of domain object models have specific meaning imposed by other -extensions within the Open MCT Web platform. +Other properties of domain object models have specific meaning imposed by other +extensions within the Open MCT Web platform. -### Capability-specific Properties +### Capability-specific Properties -Some properties either trigger the presence/absence of certain capabilities, or +Some properties either trigger the presence/absence of certain capabilities, or are managed by specific capabilities: -* `composition`: An array of domain object identifiers that represents the -contents of this domain object (e.g. as will appear in the tree hierarchy.) -Understood by the composition capability; the presence or absence of this -property determines the presence or absence of that capability. -* `modified`: The timestamp (in milliseconds since the UNIX epoch) of the last -modification made to this domain object. Managed by the mutation capability. -* `persisted`: The timestamp (in milliseconds since the UNIX epoch) of the last -time when changes to this domain object were persisted. Managed by the - persistence capability. -* `relationships`: An object containing key-value pairs, where keys are symbolic -identifiers for relationship types, and values are arrays of domain object -identifiers. Used by the relationship capability; the presence or absence of -this property determines the presence or absence of that capability. -* `telemetry`: An object which serves as a template for telemetry requests -associated with this domain object (e.g. specifying `source` and `key`; see -Telemetry Requests under Core API.) Used by the telemetry capability; the -presence or absence of this property determines the presence or absence of that -capability. -* `type`: A string identifying the type of this domain object. Used by the `type` +* `composition`: An array of domain object identifiers that represents the +contents of this domain object (e.g. as will appear in the tree hierarchy.) +Understood by the composition capability; the presence or absence of this +property determines the presence or absence of that capability. +* `modified`: The timestamp (in milliseconds since the UNIX epoch) of the last +modification made to this domain object. Managed by the mutation capability. +* `persisted`: The timestamp (in milliseconds since the UNIX epoch) of the last +time when changes to this domain object were persisted. Managed by the + persistence capability. +* `relationships`: An object containing key-value pairs, where keys are symbolic +identifiers for relationship types, and values are arrays of domain object +identifiers. Used by the relationship capability; the presence or absence of +this property determines the presence or absence of that capability. +* `telemetry`: An object which serves as a template for telemetry requests +associated with this domain object (e.g. specifying `source` and `key`; see +Telemetry Requests under Core API.) Used by the telemetry capability; the +presence or absence of this property determines the presence or absence of that capability. +* `type`: A string identifying the type of this domain object. Used by the `type` +capability. + +### View Configurations -### View Configurations +Persistent configurations for specific views of domain objects are stored in the +domain object model under the property configurations . This is an object +containing key-value pairs, where keys identify the view, and values are objects +containing view-specific (and view-managed) configuration properties. + +## Modifying Models +When interacting with a domain object's model, it is possible to make +modifications to it directly. __Don't!__ These changes may not be properly detected +by the platform, meaning that other representations of the domain object may not +be updated, changes may not be saved at the expected times, and generally, that +unexpected behavior may occur. Instead, use the `mutation` capability. -Persistent configurations for specific views of domain objects are stored in the -domain object model under the property configurations . This is an object -containing key-value pairs, where keys identify the view, and values are objects -containing view-specific (and view-managed) configuration properties. +# Capabilities -## Modifying Models -When interacting with a domain object's model, it is possible to make -modifications to it directly. __Don't!__ These changes may not be properly detected -by the platform, meaning that other representations of the domain object may not -be updated, changes may not be saved at the expected times, and generally, that -unexpected behavior may occur. Instead, use the `mutation` capability. +Dynamic behavior associated with a domain object is expressed as capabilities. A +capability is a JavaScript object with an interface that is specific to the type +of capability in use. -# Capabilities - -Dynamic behavior associated with a domain object is expressed as capabilities. A -capability is a JavaScript object with an interface that is specific to the type -of capability in use. - -Often, there is a relationship between capabilities and services. For instance, -there is an action capability and an actionService , and there is a telemetry -capability as well as a `telemetryService`. Typically, the pattern here is that -the capability will utilize the service for the specific domain object. - -When interacting with domain objects, it is generally preferable to use a -capability instead of a service when the option is available. Capability -interfaces are typically easier to use and/or more powerful in these situations. -Additionally, this usage provides a more robust substitutability mechanism; for -instance, one could configure a plugin such that it provided a totally new -implementation of a given capability which might not invoke the underlying -service, while user code which interacts with capabilities remains indifferent -to this detail. +Often, there is a relationship between capabilities and services. For instance, +there is an action capability and an actionService , and there is a telemetry +capability as well as a `telemetryService`. Typically, the pattern here is that +the capability will utilize the service for the specific domain object. +When interacting with domain objects, it is generally preferable to use a +capability instead of a service when the option is available. Capability +interfaces are typically easier to use and/or more powerful in these situations. +Additionally, this usage provides a more robust substitutability mechanism; for +instance, one could configure a plugin such that it provided a totally new +implementation of a given capability which might not invoke the underlying +service, while user code which interacts with capabilities remains indifferent +to this detail. + ## Action Capability -The `action` capability is present for all domain objects. It allows applicable -`Action` instances to be retrieved and performed for specific domain objects. +The `action` capability is present for all domain objects. It allows applicable +`Action` instances to be retrieved and performed for specific domain objects. -For example: +For example: `domainObject.getCapability("action").perform("navigate"); ` - ...will initiate a navigate action upon the domain object, if an action with - key "navigate" is defined. - -This capability has the following interface: -* `getActions(context)`: Get the actions that are applicable in the specified -action `context`; the capability will fill in the `domainObject` field of this -context if necessary. If context is specified as a string, they will instead be + ...will initiate a navigate action upon the domain object, if an action with + key "navigate" is defined. + +This capability has the following interface: +* `getActions(context)`: Get the actions that are applicable in the specified +action `context`; the capability will fill in the `domainObject` field of this +context if necessary. If context is specified as a string, they will instead be used as the `key` of the action context. Returns an array of `Action` instances. -* `perform(context)`: Perform an action. This will find and perform the first -matching action available for the specified action context , filling in the -`domainObject` field as necessary. If `context` is specified as a string, they -will instead be used as the `key` of the action context. Returns a `Promise` for -the result of the action that was performed, or `undefined` if no matching action -was found. +* `perform(context)`: Perform an action. This will find and perform the first +matching action available for the specified action context , filling in the +`domainObject` field as necessary. If `context` is specified as a string, they +will instead be used as the `key` of the action context. Returns a `Promise` for +the result of the action that was performed, or `undefined` if no matching action +was found. ## Composition Capability -The `composition` capability provides access to domain objects that are -contained by this domain object. While the `composition` property of a domain -object's model describes these contents (by their identifiers), the -`composition` capability provides a means to load the corresponding -`DomainObject` instances in the same order. The absence of this property in the -model will result in the absence of this capability in the domain object. +The `composition` capability provides access to domain objects that are +contained by this domain object. While the `composition` property of a domain +object's model describes these contents (by their identifiers), the +`composition` capability provides a means to load the corresponding +`DomainObject` instances in the same order. The absence of this property in the +model will result in the absence of this capability in the domain object. -This capability has the following interface: +This capability has the following interface: * `invoke()`: Returns a `Promise` for an array of `DomainObject` instances. ## Delegation Capability -The delegation capability is used to communicate the intent of a domain object -to delegate responsibilities, which would normally handled by other -capabilities, to the domain objects in its composition. +The delegation capability is used to communicate the intent of a domain object +to delegate responsibilities, which would normally handled by other +capabilities, to the domain objects in its composition. -This capability has the following interface: +This capability has the following interface: -* `getDelegates(key)`: Returns a Promise for an array of DomainObject instances, -to which this domain object wishes to delegate the capability with the specified -key . -* `invoke(key)`: Alias of getDelegates(key) . -* `doesDelegate(key)`: Returns true if the domain object does delegate the -capability with the specified key . +* `getDelegates(key)`: Returns a Promise for an array of DomainObject instances, +to which this domain object wishes to delegate the capability with the specified +key . +* `invoke(key)`: Alias of getDelegates(key) . +* `doesDelegate(key)`: Returns true if the domain object does delegate the +capability with the specified key . -The platform implementation of the delegation capability inspects the domain -object's type definition for a property delegates , whose value is an array of -strings describing which capabilities domain objects of that type wish to -delegate. If this property is not present, the delegation capability will not be -present in domain objects of that type. +The platform implementation of the delegation capability inspects the domain +object's type definition for a property delegates , whose value is an array of +strings describing which capabilities domain objects of that type wish to +delegate. If this property is not present, the delegation capability will not be +present in domain objects of that type. ## Editor Capability -The editor capability is meant primarily for internal use by Edit mode, and -helps to manage the behavior associated with exiting _Edit_ mode via _Save_ or -_Cancel_. Its interface is not intended for general use. However, -`domainObject.hasCapability(editor)` is a useful way of determining whether or +The editor capability is meant primarily for internal use by Edit mode, and +helps to manage the behavior associated with exiting _Edit_ mode via _Save_ or +_Cancel_. Its interface is not intended for general use. However, +`domainObject.hasCapability(editor)` is a useful way of determining whether or not we are looking at an object in _Edit_ mode. - + ## Mutation Capability -The `mutation` capability provides a means by which the contents of a domain -object's model can be modified. This capability is provided by the platform for +The `mutation` capability provides a means by which the contents of a domain +object's model can be modified. This capability is provided by the platform for all domain objects, and has the following interface: -* `mutate(mutator, [timestamp])`: Modify the domain object's model using the -specified `mutator` function. After changes are made, the `modified` property of -the model will be updated with the specified `timestamp` if one was provided, -or with the current system time. +* `mutate(mutator, [timestamp])`: Modify the domain object's model using the +specified `mutator` function. After changes are made, the `modified` property of +the model will be updated with the specified `timestamp` if one was provided, +or with the current system time. * `invoke(...)`: Alias of `mutate`. -Changes to domain object models should only be made via the `mutation` -capability; other platform behavior is likely to break (either by exhibiting -undesired behavior, or failing to exhibit desired behavior) if models are +Changes to domain object models should only be made via the `mutation` +capability; other platform behavior is likely to break (either by exhibiting +undesired behavior, or failing to exhibit desired behavior) if models are modified by other means. - + ### Mutator Function -The mutator argument above is a function which will receive a cloned copy of the -domain object's model as a single argument. It may return: +The mutator argument above is a function which will receive a cloned copy of the +domain object's model as a single argument. It may return: -* A `Promise` in which case the resolved value of the promise will be used to -determine which of the following forms is used. -* Boolean `false` in which case the mutation is cancelled. -* A JavaScript object, in which case this object will be used as the new model +* A `Promise` in which case the resolved value of the promise will be used to +determine which of the following forms is used. +* Boolean `false` in which case the mutation is cancelled. +* A JavaScript object, in which case this object will be used as the new model for this domain object. -* No value (or, equivalently, `undefined`), in which case the cloned copy -(including any changes made in place by the mutator function) will be used as -the new domain object model. +* No value (or, equivalently, `undefined`), in which case the cloned copy +(including any changes made in place by the mutator function) will be used as +the new domain object model. ## Persistence Capability -The persistence capability provides a mean for interacting with the underlying -persistence service which stores this domain object's model. It has the +The persistence capability provides a mean for interacting with the underlying +persistence service which stores this domain object's model. It has the following interface: -* `persist()`: Store the local version of this domain object, including any -changes, to the persistence store. Returns a Promise for a boolean value, which +* `persist()`: Store the local version of this domain object, including any +changes, to the persistence store. Returns a Promise for a boolean value, which will be true when the object was successfully persisted. -* `refresh()`: Replace this domain object's model with the most recent version -from persistence. Returns a Promise which will resolve when the change has +* `refresh()`: Replace this domain object's model with the most recent version +from persistence. Returns a Promise which will resolve when the change has completed. -* `getSpace()`: Return the string which identifies the persistence space which +* `getSpace()`: Return the string which identifies the persistence space which stores this domain object. ## Relationship Capability -The relationship capability provides a means for accessing other domain objects -with which this domain object has some typed relationship. It has the following +The relationship capability provides a means for accessing other domain objects +with which this domain object has some typed relationship. It has the following interface: -* `listRelationships()`: List all types of relationships exposed by this object. +* `listRelationships()`: List all types of relationships exposed by this object. Returns an array of strings identifying the types of relationships. -* `getRelatedObjects(relationship)`: Get all domain objects to which this domain -object has the specified type of relationship, which is a string identifier +* `getRelatedObjects(relationship)`: Get all domain objects to which this domain +object has the specified type of relationship, which is a string identifier (as above.) Returns a `Promise` for an array of `DomainObject` instances. -The platform implementation of the `relationship` capability is present for domain -objects which has a `relationships` property in their model, whose value is an -object containing key-value pairs, where keys are strings identifying +The platform implementation of the `relationship` capability is present for domain +objects which has a `relationships` property in their model, whose value is an +object containing key-value pairs, where keys are strings identifying relationship types, and values are arrays of domain object identifiers. ## Telemetry Capability -The telemetry capability provides a means for accessing telemetry data +The telemetry capability provides a means for accessing telemetry data associated with a domain object. It has the following interface: -* `requestData([request])`: Request telemetry data for this specific domain -object, using telemetry request parameters from the specified request if -provided. This capability will fill in telemetry request properties as-needed +* `requestData([request])`: Request telemetry data for this specific domain +object, using telemetry request parameters from the specified request if +provided. This capability will fill in telemetry request properties as-needed for this domain object. Returns a `Promise` for a `TelemetrySeries`. -* `subscribe(callback, [request])`: Subscribe to telemetry data updates for -this specific domain object, using telemetry request parameters from the -specified request if provided. This capability will fill in telemetry request -properties as-needed for this domain object. The specified callback will be -invoked with TelemetrySeries instances as they arrive. Returns a function which -can be invoked to terminate the subscription, or undefined if no subscription +* `subscribe(callback, [request])`: Subscribe to telemetry data updates for +this specific domain object, using telemetry request parameters from the +specified request if provided. This capability will fill in telemetry request +properties as-needed for this domain object. The specified callback will be +invoked with TelemetrySeries instances as they arrive. Returns a function which +can be invoked to terminate the subscription, or undefined if no subscription could be obtained. * `getMetadata()`: Get metadata associated with this domain object's telemetry. -The platform implementation of the `telemetry` capability is present for domain -objects which has a `telemetry` property in their model and/or type definition; -this object will serve as a template for telemetry requests made using this +The platform implementation of the `telemetry` capability is present for domain +objects which has a `telemetry` property in their model and/or type definition; +this object will serve as a template for telemetry requests made using this object, and will also be returned by `getMetadata()` above. ## Type Capability -The `type` capability exposes information about the domain object's type. It has +The `type` capability exposes information about the domain object's type. It has the same interface as `Type`; see Core API. ## View Capability -The `view` capability exposes views which are applicable to a given domain +The `view` capability exposes views which are applicable to a given domain object. It has the following interface: -* `invoke()`: Returns an array of extension definitions for views which are +* `invoke()`: Returns an array of extension definitions for views which are applicable for this domain object. # Actions -Actions are reusable processes/behaviors performed by users within the system, +Actions are reusable processes/behaviors performed by users within the system, typically upon domain objects. ## Action Categories -The platform understands the following action categories (specifiable as the +The platform understands the following action categories (specifiable as the `category` parameter of an action's extension definition.) * `contextual`: Appears in context menus. * `view-control`: Appears in top-right area of view (as buttons) in Browse mode ## Platform Actions -The platform defines certain actions which can be utilized by way of a domain -object's `action` capability. Unless otherwise specified, these act upon (and -modify) the object described by the `domainObject` property of the action's +The platform defines certain actions which can be utilized by way of a domain +object's `action` capability. Unless otherwise specified, these act upon (and +modify) the object described by the `domainObject` property of the action's context. * `cancel`: Cancel the current editing action (invoked from Edit mode.) -* `compose`: Place an object in another object's composition. The object to be +* `compose`: Place an object in another object's composition. The object to be added should be provided as the `selectedObject` of the action context. * `edit`: Start editing an object (enter Edit mode.) * `fullscreen`: Enter full screen mode. -* `navigate`: Make this object the focus of navigation (e.g. highlight it within +* `navigate`: Make this object the focus of navigation (e.g. highlight it within the tree, display a view of it to the right.) * `properties`: Show the 'Edit Properties' dialog. -* `remove`: Remove this domain object from its parent's composition. (The -parent, in this case, is whichever other domain object exposed this object by +* `remove`: Remove this domain object from its parent's composition. (The +parent, in this case, is whichever other domain object exposed this object by way of its `composition` capability.) * `save`: Save changes (invoked from Edit mode.) * `window`: Open this object in a new window. # Policies -Policies are consulted to determine when certain behavior in Open MCT Web is -allowed. Policy questions are assigned to certain categories, which broadly -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 +Policies are consulted to determine when certain behavior in Open MCT Web is +allowed. Policy questions are assigned to certain categories, which broadly +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 (describing, generally, the context in which the decision is occurring.) -The types of objects passed for 'candidate' and 'context' vary by category; +The types of objects passed for 'candidate' and 'context' vary by category; these types are documented below. ## Policy Categories -The platform understands the following policy categories (specifiable as the +The platform understands the following policy categories (specifiable as the `category` parameter of an policy's extension definition.) -* `action`: Determines whether or not a given action is allowable. The candidate +* `action`: Determines whether or not a given action is allowable. The candidate argument here is an Action; the context is its action context object. -* `composition`: Determines whether or not domain objects of a given type are -allowed to contain domain objects of another type. The candidate argument here -is the container's `Type`; the context argument is the `Type` of the object to be +* `composition`: Determines whether or not domain objects of a given type are +allowed to contain domain objects of another type. The candidate argument here +is the container's `Type`; the context argument is the `Type` of the object to be contained. -* `view`: Determines whether or not a view is applicable for a domain object. -The candidate argument is the view's extension definition; the context argument +* `view`: Determines whether or not a view is applicable for a domain object. +The candidate argument is the view's extension definition; the context argument is the `DomainObject` to be viewed. # Build-Test-Deploy -Open MCT Web is designed to support a broad variety of build and deployment -options. The sources can be deployed in the same directory structure used during +Open MCT Web is designed to support a broad variety of build and deployment +options. The sources can be deployed in the same directory structure used during development. A few utilities are included to support development processes. ## Command-line Build -Open MCT Web includes a script for building via command line using Maven 3.0.4 -[https://maven.apache.org/](). - +Open MCT Web includes a script for building via command line using Maven 3.0.4 +https://maven.apache.org/ . + Invoking mvn clean install will: * Check code style using JSLint. The build will fail if JSLint raises any warnings. @@ -2185,44 +2166,44 @@ Invoking mvn clean install will: * Populate version info (e.g. commit hash, build time.) * Produce a web archive (`.war`) artifact in the `target` directory. -The produced artifact contains a subset of the repository's own folder -hierarchy, omitting tests and example bundles. +The produced artifact contains a subset of the repository's own folder +hierarchy, omitting tests and example bundles. -Note that an internet connection is required to run this build, in order to +Note that an internet connection is required to run this build, in order to download build dependencies. ## Test Suite -Open MCT Web uses Jasmine [http://jasmine.github.io/]() for automated testing. -The file `test.html` included at the top level of the source repository, can be -run from the browser to perform tests for all active bundles, as defined in +Open MCT Web uses Jasmine http://jasmine.github.io/ for automated testing. +The file `test.html` included at the top level of the source repository, can be +run from the browser to perform tests for all active bundles, as defined in `bundle.json`. To define tests for a bundle: * Include a directory named `test` within that bundle. -* In the `test` directory, include a file named `suite.json`. This will identify +* In the `test` directory, include a file named `suite.json`. This will identify which scripts will be tested. -* The file `suite.json` must contain a JSON array of strings, where each string -is the name of a script to be tested. These names should include any directory -paths to the script after (but not including) the `src` folder, and should not -include the file's `.js` extension. (Note that while Open MCT Web's framework -allows a different name to be chosen for the src directory, the test runner +* The file `suite.json` must contain a JSON array of strings, where each string +is the name of a script to be tested. These names should include any directory +paths to the script after (but not including) the `src` folder, and should not +include the file's `.js` extension. (Note that while Open MCT Web's framework +allows a different name to be chosen for the src directory, the test runner does not: This directory must be named `src` for the test runner to find it.) -* For each script to be tested, a corresponding test script should be located in -the bundle's `test` directory. This should include the suffix Spec at the end of -the filename (but before the `.js` extension.) This test script should be an AMD -module which uses the Jasmine API to declare its test behavior. It should +* For each script to be tested, a corresponding test script should be located in +the bundle's `test` directory. This should include the suffix Spec at the end of +the filename (but before the `.js` extension.) This test script should be an AMD +module which uses the Jasmine API to declare its test behavior. It should declare an AMD dependency on the script to be tested, using a relative path. For example, if writing tests for a bundle at example/foo with two scripts: * `example/foo/src/controllers/FooController.js` * `example/foo/src/directives/FooDirective.js` -First, these scripts should be identified in `example/foo/test/suite.json` e.g. +First, these scripts should be identified in `example/foo/test/suite.json` e.g. with contents:`[ "controllers/FooController", "directives/FooDirective" ]` -Then, scripts which describe these tests should be written. For example, test +Then, scripts which describe these tests should be written. For example, test `example/foo/test/controllers/FooControllerSpec.js` could look like: /*global define,Promise,describe,it,expect,beforeEach*/ @@ -2231,8 +2212,8 @@ Then, scripts which describe these tests should be written. For example, test ["../../src/controllers/FooController"], function (FooController) { "use strict"; - - + + describe("The foo controller", function () { it("does something", function () { var controller = new FooController(); @@ -2245,113 +2226,108 @@ Then, scripts which describe these tests should be written. For example, test ## Code Coverage -In addition to running tests, the test runner will also capture code coverage -information using [Blanket.JS](http://blanketjs.org/) and display this at the +In addition to running tests, the test runner will also capture code coverage +information using [Blanket.JS](http://blanketjs.org/) and display this at the bottom of the screen. Currently, only statement coverage is displayed. ## Deployment -Open MCT Web is built to be flexible in terms of the deployment strategies it +Open MCT Web is built to be flexible in terms of the deployment strategies it supports. In order to run in the browser, Open MCT Web 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. -2. Access to any external services utilized by active bundles. (This means that -external services need to support HTTP or some other web-accessible interface, +2. Access to any external services utilized by active bundles. (This means that +external services need to support HTTP or some other web-accessible interface, like WebSockets.) -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 +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 deployment on containers such as Apache Tomcat. -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 -services (and the manner in which they are exposed) that determine how to deploy +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 +services (and the manner in which they are exposed) that determine how to deploy Open MCT Web. -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 -as the client (from the perspective of the browser) then access may be +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 +as the client (from the perspective of the browser) then access may be disallowed. There are two workarounds if this occurs: -* Make the external service appear to be on the same host/port, either by +* Make the external service appear to be on the same host/port, either by actually deploying it there, or by proxying requests to it. -* Enable CORS (cross-origin resource sharing) on the external service. This is -only possible if the external service can be configured to support CORS. Care -should be exercised if choosing this option to ensure that the chosen +* Enable CORS (cross-origin resource sharing) on the external service. This is +only possible if the external service can be configured to support CORS. Care +should be exercised if choosing this option to ensure that the chosen 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: -* If the external services that Open MCT Web will utilize are all running on -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 -`.war` artifact produced by the command line build facilitates this deployment -option. (See [https://tomcat.apache.org/tomcat-8.0-doc/deployer-howto.html() for +* If the external services that Open MCT Web will utilize are all running on +[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 +`.war` artifact produced by the command line build facilitates this deployment +option. (See https://tomcat.apache.org/tomcat-8.0-doc/deployer-howto.html for general information on deploying in Tomcat.) -* If a variety of external services will be running from a variety of -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 -configuration, the HTTP server would be configured to proxy (or reverse proxy) -requests at specific paths to the various external services, while providing +* If a variety of external services will be running from a variety of +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 +configuration, the HTTP server would be configured to proxy (or reverse proxy) +requests at specific paths to the various external services, while providing Open MCT Web as flat files from a different path. -* 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 -flat files) from the same component using an embedded HTTP server such as Nancy -[http://nancyfx.org/](). -* 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 -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 +* 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 +flat files) from the same component using an embedded HTTP server such as +[Nancy](http://nancyfx.org/). +* 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 +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 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 -server that is solely responsible for making Open MCT Web sources/resources -available, and to have Open MCT Web contact these external services directly. -Again, lightweight HTTP servers such as Lighttpd [http://www.lighttpd.net/]() -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, +* 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 +available, and to have Open MCT Web contact these external services directly. +Again, lightweight HTTP servers such as [Lighttpd](http://www.lighttpd.net/) +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, and to ensure that Open MCT Web can correctly locate these services. -Another important consideration is authentication. By design, Open MCT Web does -not handle user authentication. Instead, this should typically be treated as a -deployment-time concern, where authentication is handled by the HTTP server +Another important consideration is authentication. By design, Open MCT Web does +not handle user authentication. Instead, this should typically be treated as a +deployment-time concern, where authentication is handled by the HTTP server which provides Open MCT Web, or an external access management system. ### Configuration -In most of the deployment options above, some level of configuration is likely -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 +In most of the deployment options above, some level of configuration is likely +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 to an external service. -Configurable parameters within Open MCT Web are specified via constants -(literally, as extensions of the `constants` category) and accessed via -dependency injection by the scripts which need them. Reasonable defaults for -these constants are provided in the bundle where they are used. Plugins are +Configurable parameters within Open MCT Web are specified via constants +(literally, as extensions of the `constants` category) and accessed via +dependency injection by the scripts which need them. Reasonable defaults for +these constants are provided in the bundle where they are used. Plugins are encouraged to follow the same pattern. -Constants may be specified in any bundle; if multiple constants are specified -with the same `key` the highest-priority one will be used. This allows default +Constants may be specified in any bundle; if multiple constants are specified +with the same `key` the highest-priority one will be used. This allows default values to be overridden by specifying constants with higher priority. This permits at least three configuration approaches: -* Modify the constants defined in their original bundles when deploying. This is -generally undesirable due to the amount of manual work required and potential +* Modify the constants defined in their original bundles when deploying. This is +generally undesirable due to the amount of manual work required and potential for error, but is viable if there are a small number of constants to change. -* Add a separate configuration bundle which overrides the values of these -constants. This is particularly appropriate when multiple configurations (e.g. -development, test, production) need to be managed easily; these can be swapped +* Add a separate configuration bundle which overrides the values of these +constants. This is particularly appropriate when multiple configurations (e.g. +development, test, production) need to be managed easily; these can be swapped 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 Web and its external services in such a fashion that the default paths to reach external services are all correct. ### Configuration Constants -The following constants have global significance: -* `PERSISTENCE_SPACE`: The space in which domain objects should be persisted - (or read from) when not otherwise specified. Typically this will not need - to be overridden by other bundles, but persistence adapters may wish to - consume this constant in order to provide persistence for that space. The following configuration constants are recognized by Open MCT Web bundles: * Common UI elements - `platform/commonUI/general` @@ -2359,10 +2335,10 @@ The following configuration constants are recognized by Open MCT Web bundles: stylesheets (the `stylesheets` extension category) may specify an optional `theme` property which will be matched against this before inclusion. * CouchDB adapter - `platform/persistence/couch` - * `COUCHDB_PATH`: URL or path to the CouchDB database to be used for domain + * `COUCHDB_PATH`: URL or path to the CouchDB database to be used for domain object persistence. Should not include a trailing slash. * ElasticSearch adapter - `platform/persistence/elastic` - * `ELASTIC_ROOT`: URL or path to the ElasticSearch instance to be used for + * `ELASTIC_ROOT`: URL or path to the ElasticSearch instance to be used for domain object persistence. Should not include a trailing slash. - * `ELASTIC_PATH`: Path relative to the ElasticSearch instance where domain - object models should be persisted. Should take the form `/`. + * `ELASTIC_PATH`: Path relative to the ElasticSearch instance where domain + object models should be persisted. Should take the form `/`. \ No newline at end of file diff --git a/docs/src/tutorials/index.md b/docs/src/tutorials/index.md index d07466abac..26c47fefbd 100644 --- a/docs/src/tutorials/index.md +++ b/docs/src/tutorials/index.md @@ -52,7 +52,7 @@ First step is to check out Open MCT Web from the source repository. This will create a copy of the Open MCT Web source code repository in the folder `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 local filesystem can be used instead. At this point, it will also be useful to branch off of Open MCT Web v0.6.2 @@ -66,10 +66,10 @@ At this point, it will also be useful to branch off of Open MCT Web v0.6.2 In its default configuration, Open MCT Web will try to use ElasticSearch (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 -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 +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 tutorials. To change this configuration, edit bundles.json (at the top level of the Open @@ -77,55 +77,57 @@ MCT Web repository) and replace platform/persistence/elastic with example/persistence. #### Bundle Before +```diff +[ + "platform/framework", + "platform/core", + "platform/representation", + "platform/commonUI/about", + "platform/commonUI/browse", + "platform/commonUI/edit", + "platform/commonUI/dialog", + "platform/commonUI/general", + "platform/containment", + "platform/telemetry", + "platform/features/layout", + "platform/features/pages", + "platform/features/plot", + "platform/features/scrolling", + "platform/forms", + "platform/persistence/queue", +- "platform/persistence/elastic", + "platform/policy", - [ - "platform/framework", - "platform/core", - "platform/representation", - "platform/commonUI/about", - "platform/commonUI/browse", - "platform/commonUI/edit", - "platform/commonUI/dialog", - "platform/commonUI/general", - "platform/containment", - "platform/telemetry", - "platform/features/layout", - "platform/features/pages", - "platform/features/plot", - "platform/features/scrolling", - "platform/forms", - "platform/persistence/queue", - -- "platform/persistence/elastic", - "platform/policy", - - "example/generator" - ] + "example/generator" +] +``` __bundles.json__ #### Bundle After +```diff +[ + "platform/framework", + "platform/core", + "platform/representation", + "platform/commonUI/about", + "platform/commonUI/browse", + "platform/commonUI/edit", + "platform/commonUI/dialog", + "platform/commonUI/general", + "platform/containment", + "platform/telemetry", + "platform/features/layout", + "platform/features/pages", + "platform/features/plot", + "platform/features/scrolling", + "platform/forms", + "platform/persistence/queue", + "platform/policy", - [ - "platform/framework", - "platform/core", - "platform/representation", - "platform/commonUI/about", - "platform/commonUI/browse", - "platform/commonUI/edit", - "platform/commonUI/dialog", - "platform/commonUI/general", - "platform/containment", - "platform/telemetry", - "platform/features/layout", - "platform/features/pages", - "platform/features/plot", - "platform/features/scrolling", - "platform/forms", - "platform/persistence/queue", - "platform/policy", - - ++ "example/persistence", - "example/generator" - ] ++ "example/persistence", + "example/generator" +] +``` __bundles.json__ ### Run a Web Server @@ -143,13 +145,13 @@ To run the tutorial web server ### Viewing in Browser Once running, you should be able to view Open MCT Web 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). [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 Web's "test-to" browser. The browser cache can sometimes interfere with development (masking changes by 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 tab, as shown below. ![Chrome Developer Tools](images/chrome.png) @@ -158,16 +160,16 @@ tab, as shown below. These tutorials cover three of the common tasks in Open MCT Web: -* 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 “data set reader” tutorial illustrates how to integrate with a telemetry +* 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 "data set reader" tutorial illustrates how to integrate with a telemetry backend. ## To-do List The goal of this tutorial is to add a new application feature to Open MCT Web: 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 @@ -179,77 +181,82 @@ will be. The syntax of this file is described in more detail in the Open MCT Web Developer Guide. We will create this file in the directory tutorials/todo (we can hereafter refer -to this plugin as tutorials/todo as well.) We will start with an “empty bundle”, +to this plugin as tutorials/todo as well.) We will start with an "empty bundle", one which exposes no extensions - which looks like: - { - "name": "To-do Plugin", - "description": "Allows creating and editing to-do lists.", - "extensions": { - - } - } +```diff +{ + "name": "To-do Plugin", + "description": "Allows creating and editing to-do lists.", + "extensions": { + } +} +``` __tutorials/todo/bundle.json__ We will also include this in our list of active bundles. #### Before - [ - "platform/framework", - "platform/core", - "platform/representation", - "platform/commonUI/about", - "platform/commonUI/browse", - "platform/commonUI/edit", - "platform/commonUI/dialog", - "platform/commonUI/general", - "platform/containment", - "platform/telemetry", - "platform/features/layout", - "platform/features/pages", - "platform/features/plot", - "platform/features/scrolling", - "platform/forms", - "platform/persistence/queue", - "platform/policy", - - "example/persistence", - "example/generator" - ] +```diff +[ + "platform/framework", + "platform/core", + "platform/representation", + "platform/commonUI/about", + "platform/commonUI/browse", + "platform/commonUI/edit", + "platform/commonUI/dialog", + "platform/commonUI/general", + "platform/containment", + "platform/telemetry", + "platform/features/layout", + "platform/features/pages", + "platform/features/plot", + "platform/features/scrolling", + "platform/forms", + "platform/persistence/queue", + "platform/policy", + + "example/persistence", + "example/generator" +] +``` __bundles.json__ #### After - [ - "platform/framework", - "platform/core", - "platform/representation", - "platform/commonUI/about", - "platform/commonUI/browse", - "platform/commonUI/edit", - "platform/commonUI/dialog", - "platform/commonUI/general", - "platform/containment", - "platform/telemetry", - "platform/features/layout", - "platform/features/pages", - "platform/features/plot", - "platform/features/scrolling", - "platform/forms", - "platform/persistence/queue", - "platform/policy", - - "example/persistence", - "example/generator", - - ++ "tutorials/todo" - ] - + +```diff +[ + "platform/framework", + "platform/core", + "platform/representation", + "platform/commonUI/about", + "platform/commonUI/browse", + "platform/commonUI/edit", + "platform/commonUI/dialog", + "platform/commonUI/general", + "platform/containment", + "platform/telemetry", + "platform/features/layout", + "platform/features/pages", + "platform/features/plot", + "platform/features/scrolling", + "platform/forms", + "platform/persistence/queue", + "platform/policy", + + "example/persistence", + "example/generator", + ++ "tutorials/todo" +] +``` __bundles.json__ -At this point, we can reload Open MCT Web. We haven’t introduced any new -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 +At this point, we can reload Open MCT Web. We haven't introduced any new +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 should see: `Resolving extensions for bundle tutorials/todo(To-do Plugin)` @@ -264,81 +271,85 @@ the work that the Open MCT Web application is meant to support. Domain objects 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.) -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 our bundle definition: - - { - "name": "To-do Plugin", - "description": "Allows creating and editing to-do lists.", - "extensions": { - ++ "types": [ - ++ { - ++ "key": "example.todo", - ++ "name": "To-Do List", - ++ "glyph": "j", - ++ "description": "A list of things that need to be done.", - ++ "features": ["creation"] - ++ } - ] - } +```diff +{ + "name": "To-do Plugin", + "description": "Allows creating and editing to-do lists.", + "extensions": { ++ "types": [ ++ { ++ "key": "example.todo", ++ "name": "To-Do List", ++ "glyph": "j", ++ "description": "A list of things that need to be done.", ++ "features": ["creation"] ++ } + ] } +} +``` __tutorials/todo/bundle.json__ -What have we done here? We’ve stated that this bundle includes extensions of the -category _types_, which is used to describe domain object types. Then, we’ve +What have we done here? We've stated that this bundle includes extensions of the +category _types_, which is used to describe domain object types. Then, we've included a definition for one such extension, which is the to-do list object. -Going through the properties we’ve defined: +Going through the properties we've defined: * The `key` of `example.todo` will be stored as the machine-readable name for 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. -* 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 Web's custom font set; this will be used as an icon. * The `description` is also human-readable, and will be used whenever a longer explanation of what this type is should be shown. * Finally, the `features` property describes some special features of objects of 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 -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 Create menu: ![To-Do List](images/todo.png) -At this point, our to-do list doesn’t do much of anything; we can create them -and give them names, but they don’t have any specific functionality attached, -because we haven’t defined any yet. +At this point, our to-do list doesn't do much of anything; we can create them +and give them names, but they don't have any specific functionality attached, +because we haven't defined any yet. ### Step 3-Add a View 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 Web, 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 right; in Open MCT Web, 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 Web is defined by an Angular template. We'll add that in the directory `tutorials/todo/res/templates` (`res` is, by default, the directory where bundle-related resources are kept, and `templates` is where HTML templates are stored by convention.) - - -
    -
  • - - {{task.description}} -
  • -
+```diff + + +
    +
  • + + {{task.description}} +
  • +
+``` + __tutorials/todo/res/templates/todo.html__ -A summary of what’s included: +A summary of what's included: * At the top, we have some buttons that we will later wire in to allow the user to filter down to either complete or incomplete tasks. @@ -354,40 +365,42 @@ boolean `completed` flag. To expose this view in Open MCT Web, we need to declare it in our bundle definition: - { - "name": "To-do Plugin", - "description": "Allows creating and editing to-do lists.", - "extensions": { - "types": [ - { - "key": "example.todo", - "name": "To-Do List", - "glyph": "j", - "description": "A list of things that need to be done.", - "features": ["creation"] - } - ], - ++ "views": [ - ++ { - ++ "key": "example.todo", - ++ "type": "example.todo", - ++ "glyph": "j", - ++ "name": "List", - ++ "templateUrl": "templates/todo.html" - ++ } - ++ ] - } +```diff +{ + "name": "To-do Plugin", + "description": "Allows creating and editing to-do lists.", + "extensions": { + "types": [ + { + "key": "example.todo", + "name": "To-Do List", + "glyph": "j", + "description": "A list of things that need to be done.", + "features": ["creation"] + } + ], ++ "views": [ ++ { ++ "key": "example.todo", ++ "type": "example.todo", ++ "glyph": "j", ++ "name": "List", ++ "templateUrl": "templates/todo.html" ++ } ++ ] } +} +``` __tutorials/todo/bundle.json__ -Here, we’ve added another extension, this time belonging to category `views`. It +Here, we've added another extension, this time belonging to category `views`. It 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 `type` property tells Open MCT Web 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.) * The `glyph` and `name` properties describe the icon and human-readable name @@ -395,43 +408,45 @@ for this view to display in the UI where needed (if multiple views are available for To-do Lists, the user will be able to choose one.) * Finally, the `templateUrl` points to the Angular template we wrote; this path is -relative to the bundle’s `res` folder. +relative to the bundle's `res` folder. -This template looks like it should display tasks, but we don’t have any way for +This template looks like it should display tasks, but we don't have any way for the user to create these yet. As a temporary workaround to test the view, we will specify an initial state for To-do List domain object models in the definition of that type. - { - "name": "To-do Plugin", - "description": "Allows creating and editing to-do lists.", - "extensions": { - "types": [ - { - "key": "example.todo", - "name": "To-Do List", - "glyph": "j", - "description": "A list of things that need to be done.", - "features": ["creation"], - ++ "model": { - ++ "tasks": [ - ++ { "description": "Add a type", "completed": true }, - ++ { "description": "Add a view" } - ++ ] - } +```diff +{ + "name": "To-do Plugin", + "description": "Allows creating and editing to-do lists.", + "extensions": { + "types": [ + { + "key": "example.todo", + "name": "To-Do List", + "glyph": "j", + "description": "A list of things that need to be done.", + "features": ["creation"], ++ "model": { ++ "tasks": [ ++ { "description": "Add a type", "completed": true }, ++ { "description": "Add a view" } ++ ] } - ], - "views": [ - { - "key": "example.todo", - "type": "example.todo", - "glyph": "j", - "name": "List", - "templateUrl": "templates/todo.html" - } - ] - } + } + ], + "views": [ + { + "key": "example.todo", + "type": "example.todo", + "glyph": "j", + "name": "List", + "templateUrl": "templates/todo.html" + } + ] } +} +``` __tutorials/todo/bundle.json__ Now, when To-do List objects are created in Open MCT Web, they will initially @@ -442,11 +457,11 @@ we should now see: ![To-Do List](images/todo-list.png) -This looks roughly like what we want. We’ll handle styling later, so let’s work +This looks roughly like what we want. We'll handle styling later, so let's work on adding functionality. Currently, the filter choices do nothing, and while the -checkboxes can be checked/unchecked, we’re not actually making the changes in +checkboxes can be checked/unchecked, we're not actually making the changes in the domain object - if we click over to My Items and come back to our -To-Do List, for instance, we’ll see that those check boxes have returned to +To-Do List, for instance, we'll see that those check boxes have returned to their initial state. ### Step 4-Add a Controller @@ -458,50 +473,51 @@ particular, we want to: * Change the completion state of tasks in the model. To do this, we will support this by adding an Angular controller. (See -[https://docs.angularjs.org/guide/controller]() for an overview of controllers.) -We will define that in an AMD module (see [http://requirejs.org/docs/whyamd.html]()) +https://docs.angularjs.org/guide/controller for an overview of controllers.) +We will define that in an AMD module (see http://requirejs.org/docs/whyamd.html) in the directory `tutorials/todo/src/controllers` (`src` is, by default, the directory where bundle-related source code is kept, and controllers is where Angular controllers are stored by convention.) +```diff +define(function () { + function TodoController($scope) { + var showAll = true, + showCompleted; - define(function () { - function TodoController($scope) { - var showAll = true, - showCompleted; - - // Persist changes made to a domain object's model - function persist() { - var persistence = - $scope.domainObject.getCapability('persistence'); - return persistence && persistence.persist(); - } - - // Change which tasks are visible - $scope.setVisibility = function (all, completed) { - showAll = all; - showCompleted = completed; - }; - - // Toggle the completion state of a task - $scope.toggleCompletion = function (taskIndex) { - $scope.domainObject.useCapability('mutation', function (model) { - var task = model.tasks[taskIndex]; - task.completed = !task.completed; - }); - persist(); - }; - - // Check whether a task should be visible - $scope.showTask = function (task) { - return showAll || (showCompleted === !!(task.completed)); - }; + // Persist changes made to a domain object's model + function persist() { + var persistence = + $scope.domainObject.getCapability('persistence'); + return persistence && persistence.persist(); } - - return TodoController; - }); + + // Change which tasks are visible + $scope.setVisibility = function (all, completed) { + showAll = all; + showCompleted = completed; + }; + + // Toggle the completion state of a task + $scope.toggleCompletion = function (taskIndex) { + $scope.domainObject.useCapability('mutation', function (model) { + var task = model.tasks[taskIndex]; + task.completed = !task.completed; + }); + persist(); + }; + + // Check whether a task should be visible + $scope.showTask = function (task) { + return showAll || (showCompleted === !!(task.completed)); + }; + } + + return TodoController; +}); +``` __tutorials/todo/src/controllers/TodoController.js__ -Here, we’ve defined three new functions and placed them in our `$scope`, which +Here, we've defined three new functions and placed them in our `$scope`, which will make them available from the template: * `setVisibility` changes which tasks are meant to be visible. The first argument @@ -510,13 +526,13 @@ argument is the completion state we want to show (which is only relevant if the first argument is falsy.) * `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 for more information on these capabilities. * `showTask` is meant to be used to help decide if a task should be shown, based on the current visibility settings. It is true when we have decided to show -everything, or when the completion state matches the state we’ve chosen. (Note +everything, or when the completion state matches the state we've chosen. (Note the use of the double-not !! to coerce the completed flag to a boolean, for equality testing.) @@ -527,23 +543,25 @@ prior to our template being utilized. On its own, this controller merely exposes these functions; the next step is to use them from our template: - ++
-
- ++ All - ++ Incomplete - ++ Complete -
- -
    -
  • - - {{task.description}} -
  • -
- ++
+```diff ++
+
++ All ++ Incomplete ++ Complete +
+ +
    +
  • + + {{task.description}} +
  • +
++
+``` __tutorials/todo/res/templates/todo.html__ Summary of changes here: @@ -557,47 +575,49 @@ filter settings. * Finally, when the checkbox for a task is clicked, we make the change in the model via `toggleCompletion`. -If we were to try to run at this point, we’d run into problems because the +If we were to try to run at this point, we'd run into problems because the `TodoController` has not been registered with Angular. We need to first declare it in our bundle definition, as an extension of category `controllers`: - { - "name": "To-do Plugin", - "description": "Allows creating and editing to-do lists.", - "extensions": { - "types": [ - { - "key": "example.todo", - "name": "To-Do List", - "glyph": "j", - "description": "A list of things that need to be done.", - "features": ["creation"], - "model": { - "tasks": [ - { "description": "Add a type", "completed": true }, - { "description": "Add a view" } - ] - } +```diff +{ + "name": "To-do Plugin", + "description": "Allows creating and editing to-do lists.", + "extensions": { + "types": [ + { + "key": "example.todo", + "name": "To-Do List", + "glyph": "j", + "description": "A list of things that need to be done.", + "features": ["creation"], + "model": { + "tasks": [ + { "description": "Add a type", "completed": true }, + { "description": "Add a view" } + ] } - ], - "views": [ - { - "key": "example.todo", - "type": "example.todo", - "glyph": "j", - "name": "List", - "templateUrl": "templates/todo.html" - } - ], - + "controllers": [ - + { - + "key": "TodoController", - + "implementation": "controllers/TodoController.js", - + "depends": [ "$scope" ] - + } - + ] - } + } + ], + "views": [ + { + "key": "example.todo", + "type": "example.todo", + "glyph": "j", + "name": "List", + "templateUrl": "templates/todo.html" + } + ], ++ "controllers": [ ++ { ++ "key": "TodoController", ++ "implementation": "controllers/TodoController.js", ++ "depends": [ "$scope" ] ++ } ++ ] } +} +``` __tutorials/todo/bundle.json__ In this extension definition we have: @@ -617,328 +637,334 @@ if we go to My Items and come back. ### Step 5-Support Editing -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! This is a good place to discuss the user interface style of Open MCT Web. 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 is reachable from the button with a pencil icon. This distinction helps users keep these tasks separate. -The distinction between “using” and “editing” may vary depending on what domain +The distinction between "using" and "editing" may vary depending on what domain objects or views are being used. While it may be convenient for a developer to -think of “editing” as “any changes made to a domain object,” in practice some of -these activities will be thought of as “using.” +think of "editing" as "any changes made to a domain object," in practice some of +these activities will be thought of as "using." -For this tutorial we’ll consider checking/unchecking tasks as “using” To-Do -Lists, and adding/removing tasks as “editing.” We’ve already implemented the -“using” part, in this case, so let’s focus on editing. +For this tutorial we'll consider checking/unchecking tasks as "using" To-Do +Lists, and adding/removing tasks as "editing." We've already implemented the +"using" part, in this case, so let's focus on editing. -There are two new pieces of functionality we’ll want out of this step: +There are two new pieces of functionality we'll want out of this step: * The ability to add new tasks. * The ability to remove existing tasks. An Editing user interface is typically handled in a tool bar associated with a -view. The contents of this tool bar are defined declaratively in a view’s +view. The contents of this tool bar are defined declaratively in a view's extension definition. - { - "name": "To-do Plugin", - "description": "Allows creating and editing to-do lists.", - "extensions": { - "types": [ - { - "key": "example.todo", - "name": "To-Do List", - "glyph": "j", - "description": "A list of things that need to be done.", - "features": ["creation"], - "model": { - "tasks": [ - { "description": "Add a type", "completed": true }, - { "description": "Add a view" } - ] - } +```diff +{ + "name": "To-do Plugin", + "description": "Allows creating and editing to-do lists.", + "extensions": { + "types": [ + { + "key": "example.todo", + "name": "To-Do List", + "glyph": "j", + "description": "A list of things that need to be done.", + "features": ["creation"], + "model": { + "tasks": [ + { "description": "Add a type", "completed": true }, + { "description": "Add a view" } + ] } - ], - "views": [ - { - "key": "example.todo", - "type": "example.todo", - "glyph": "j", - "name": "List", - "templateUrl": "templates/todo.html", - + "toolbar": { - + "sections": [ - + { - + "items": [ - + { - + "text": "Add Task", - + "glyph": "+", - + "method": "addTask", - + "control": "button" - + } - + ] - + }, - + { - + "items": [ - + { - + "glyph": "Z", - + "method": "removeTask", - + "control": "button" - + } - + ] - + } - + ] - + } - } - ], - "controllers": [ - { - "key": "TodoController", - "implementation": "controllers/TodoController.js", - "depends": [ "$scope" ] - } - ] - } + } + ], + "views": [ + { + "key": "example.todo", + "type": "example.todo", + "glyph": "j", + "name": "List", + "templateUrl": "templates/todo.html", ++ "toolbar": { ++ "sections": [ ++ { ++ "items": [ ++ { ++ "text": "Add Task", ++ "glyph": "+", ++ "method": "addTask", ++ "control": "button" ++ } ++ ] ++ }, ++ { ++ "items": [ ++ { ++ "glyph": "Z", ++ "method": "removeTask", ++ "control": "button" ++ } ++ ] ++ } ++ ] ++ } + } + ], + "controllers": [ + { + "key": "TodoController", + "implementation": "controllers/TodoController.js", + "depends": [ "$scope" ] + } + ] } +} +``` __tutorials/todo/bundle.json__ -What we’ve stated here is that the To-Do List’s view will have a toolbar which +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 -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 -as a trash can in Open MCT Web’s custom font set) which will invoke a `removeTask` +as a trash can in Open MCT Web's custom font set) which will invoke a `removeTask` method. For more information on forms and tool bars in Open MCT Web, see the Open MCT Web 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 Web, we won't see any tool bar when we switch over 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 all the applicable controls, which means no controls at all. To support selection, we will need to make some changes to our controller: - define(function () { - + // Form to display when adding new tasks - + var NEW_TASK_FORM = { - + name: "Add a Task", - + sections: [{ - + rows: [{ - + name: 'Description', - + key: 'description', - + control: 'textfield', - + required: true - + }] - + }] - + }; - - + function TodoController($scope, dialogService) { - var showAll = true, - showCompleted; - - // Persist changes made to a domain object's model - function persist() { - var persistence = - $scope.domainObject.getCapability('persistence'); - return persistence && persistence.persist(); - } - - + // Remove a task - + function removeTaskAtIndex(taskIndex) { - + $scope.domainObject.useCapability('mutation', function - + (model) { - + model.tasks.splice(taskIndex, 1); - + }); - + persist(); - + } - - + // Add a task - + function addNewTask(task) { - + $scope.domainObject.useCapability('mutation', function - + (model) { - + model.tasks.push(task); - + }); - + persist(); - + } - - // Change which tasks are visible - $scope.setVisibility = function (all, completed) { - showAll = all; - showCompleted = completed; - }; - - // Toggle the completion state of a task - $scope.toggleCompletion = function (taskIndex) { - $scope.domainObject.useCapability('mutation', function (model) { - var task = model.tasks[taskIndex]; - task.completed = !task.completed; - }); - persist(); - }; - - // Check whether a task should be visible - $scope.showTask = function (task) { - return showAll || (showCompleted === !!(task.completed)); - }; - - // Handle selection state in edit mode - + if ($scope.selection) { - + // Expose the ability to select tasks - + $scope.selectTask = function (taskIndex) { - + $scope.selection.select({ - + removeTask: function () { - + removeTaskAtIndex(taskIndex); - + $scope.selection.deselect(); - + } - + }); - + }; - - + // Expose a view-level selection proxy - + $scope.selection.proxy({ - + addTask: function () { - + dialogService.getUserInput(NEW_TASK_FORM, {}) - + .then(addNewTask); - + } - + }); - + } +```diff +define(function () { ++ // Form to display when adding new tasks ++ var NEW_TASK_FORM = { ++ name: "Add a Task", ++ sections: [{ ++ rows: [{ ++ name: 'Description', ++ key: 'description', ++ control: 'textfield', ++ required: true ++ }] ++ }] ++ }; + ++ function TodoController($scope, dialogService) { + var showAll = true, + showCompleted; + + // Persist changes made to a domain object's model + function persist() { + var persistence = + $scope.domainObject.getCapability('persistence'); + return persistence && persistence.persist(); } - - return TodoController; - }); + ++ // Remove a task ++ function removeTaskAtIndex(taskIndex) { ++ $scope.domainObject.useCapability('mutation', function ++ (model) { ++ model.tasks.splice(taskIndex, 1); ++ }); ++ persist(); ++ } + ++ // Add a task ++ function addNewTask(task) { ++ $scope.domainObject.useCapability('mutation', function ++ (model) { ++ model.tasks.push(task); ++ }); ++ persist(); ++ } + + // Change which tasks are visible + $scope.setVisibility = function (all, completed) { + showAll = all; + showCompleted = completed; + }; + + // Toggle the completion state of a task + $scope.toggleCompletion = function (taskIndex) { + $scope.domainObject.useCapability('mutation', function (model) { + var task = model.tasks[taskIndex]; + task.completed = !task.completed; + }); + persist(); + }; + + // Check whether a task should be visible + $scope.showTask = function (task) { + return showAll || (showCompleted === !!(task.completed)); + }; + + // Handle selection state in edit mode ++ if ($scope.selection) { ++ // Expose the ability to select tasks ++ $scope.selectTask = function (taskIndex) { ++ $scope.selection.select({ ++ removeTask: function () { ++ removeTaskAtIndex(taskIndex); ++ $scope.selection.deselect(); ++ } ++ }); ++ }; + ++ // Expose a view-level selection proxy ++ $scope.selection.proxy({ ++ addTask: function () { ++ dialogService.getUserInput(NEW_TASK_FORM, {}) ++ .then(addNewTask); ++ } ++ }); ++ } + } + + return TodoController; +}); +``` __tutorials/todo/src/controllers/TodoController.js__ -There are a few changes to pay attention to here. Let’s review them: +There are a few changes to pay attention to here. Let's review them: * At the top, we describe the form that should be shown to the user when they 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 -To-Do List’s model. -* We’ve added an argument to the `TodoController`: The `dialogService`, which is +To-Do List's model. +* We've added an argument to the `TodoController`: The `dialogService`, which is exposed by the Open MCT Web platform to handle showing dialogs. * 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. * Finally, we check for the presence of a `selection` object in our scope. This object is provided by Edit mode to manage current selections for editing. When it is present, we expose a `selectTask` function to our scope to allow selecting individual tasks; when this occurs, we expose an object to `selection` which has -a `removeTask` method, as expected by the tool bar we’ve defined. We additionally +a `removeTask` method, as expected by the tool bar we've defined. We additionally expose a view proxy, to handle view-level changes (e.g. not associated with any specific selected object); this has an `addTask` method, which again is expected -by the tool bar we’ve defined. +by the tool bar we've defined. Additionally, we need to make changes to our template to select specific tasks in response to some user gesture. Here, we will select tasks when a user clicks the description. - -
- - -
    -
  • - - + - {{task.description}} - + -
  • -
+```diff +
+ + +
    +
  • + ++ + {{task.description}} ++ +
  • +
+
+``` __tutorials/todo/res/templates/todo.html__ Finally, the `TodoController` uses the `dialogService` now, so we need to declare that dependency in its extension definition: - - { - "name": "To-do Plugin", - "description": "Allows creating and editing to-do lists.", - "extensions": { - "types": [ - { - "key": "example.todo", - "name": "To-Do List", - "glyph": "j", - "description": "A list of things that need to be done.", - "features": ["creation"], - "model": { - "tasks": [ - { "description": "Add a type", "completed": true }, - { "description": "Add a view" } - ] - } +```diff +{ + "name": "To-do Plugin", + "description": "Allows creating and editing to-do lists.", + "extensions": { + "types": [ + { + "key": "example.todo", + "name": "To-Do List", + "glyph": "j", + "description": "A list of things that need to be done.", + "features": ["creation"], + "model": { + "tasks": [ + { "description": "Add a type", "completed": true }, + { "description": "Add a view" } + ] } - ], - "views": [ - { - "key": "example.todo", - "type": "example.todo", - "glyph": "j", - "name": "List", - "templateUrl": "templates/todo.html", - "toolbar": { - "sections": [ - { - "items": [ - { - "text": "Add Task", - "glyph": "+", - "method": "addTask", - "control": "button" - } - ] - }, - { - "items": [ - { - "glyph": "Z", - "method": "removeTask", - "control": "button" - } - ] - } - ] - } + } + ], + "views": [ + { + "key": "example.todo", + "type": "example.todo", + "glyph": "j", + "name": "List", + "templateUrl": "templates/todo.html", + "toolbar": { + "sections": [ + { + "items": [ + { + "text": "Add Task", + "glyph": "+", + "method": "addTask", + "control": "button" + } + ] + }, + { + "items": [ + { + "glyph": "Z", + "method": "removeTask", + "control": "button" + } + ] + } + ] } - ], - "controllers": [ - { - "key": "TodoController", - "implementation": "controllers/TodoController.js", - + "depends": [ "$scope", "dialogService" ] - } - ] - } + } + ], + "controllers": [ + { + "key": "TodoController", + "implementation": "controllers/TodoController.js", ++ "depends": [ "$scope", "dialogService" ] + } + ] } +} +``` __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 Web, 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 -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: +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: ![Edit](images/todo-edit.png) -If we click on this, we’ll get a dialog allowing us to add a new task: +If we click on this, we'll get a dialog allowing us to add a new task: ![Add task](images/add-task.png) -Finally, if we click on the description of a specific task, we’ll see a new +Finally, if we click on the description of a specific task, we'll see a new button appear, which we can then click on to remove that task: ![Remove task](images/remove-task.png) As always in Edit mode, the user will be able to Save or Cancel any changes they have made. -In terms of functionality, our To-Do List can do all the things we want, but the appearance is still lacking. In particular, we can’t distinguish our current filter choice or our current selection state. +In terms of functionality, our To-Do List can do all the things we want, but the appearance is still lacking. In particular, we can't distinguish our current filter choice or our current selection state. ### Step 6-Customizing Look and Feel @@ -949,107 +975,108 @@ In this section, our goal is to: * Tweak the general aesthetics to our liking. * Get rid of those default tasks (we can create our own now.) -To support the first two, we’ll need to expose some methods for checking these +To support the first two, we'll need to expose some methods for checking these states in the controller: - - define(function () { - // Form to display when adding new tasks - var NEW_TASK_FORM = { - name: "Add a Task", - sections: [{ - rows: [{ - name: 'Description', - key: 'description', - control: 'textfield', - required: true - }] +```diff +define(function () { + // Form to display when adding new tasks + var NEW_TASK_FORM = { + name: "Add a Task", + sections: [{ + rows: [{ + name: 'Description', + key: 'description', + control: 'textfield', + required: true }] - }; - - function TodoController($scope, dialogService) { - var showAll = true, - showCompleted; - - // Persist changes made to a domain object's model - function persist() { - var persistence = - $scope.domainObject.getCapability('persistence'); - return persistence && persistence.persist(); - } - - // Remove a task - function removeTaskAtIndex(taskIndex) { - $scope.domainObject.useCapability('mutation', function (model) { - model.tasks.splice(taskIndex, 1); - }); - persist(); - } - - // Add a task - function addNewTask(task) { - $scope.domainObject.useCapability('mutation', function (model) { - model.tasks.push(task); - }); - persist(); - } - - // Change which tasks are visible - $scope.setVisibility = function (all, completed) { - showAll = all; - showCompleted = completed; - }; - - + // Check if current visibility settings match - + $scope.checkVisibility = function (all, completed) { - + return showAll ? all : (completed === showCompleted); - + }; - - // Toggle the completion state of a task - $scope.toggleCompletion = function (taskIndex) { - $scope.domainObject.useCapability('mutation', function (model) { - var task = model.tasks[taskIndex]; - task.completed = !task.completed; - }); - persist(); - }; - - // Check whether a task should be visible - $scope.showTask = function (task) { - return showAll || (showCompleted === !!(task.completed)); - }; - - // Handle selection state in edit mode - if ($scope.selection) { - // Expose the ability to select tasks - $scope.selectTask = function (taskIndex) { - $scope.selection.select({ - removeTask: function () { - removeTaskAtIndex(taskIndex); - $scope.selection.deselect(); - }, - + taskIndex: taskIndex - }); - }; - - + // Expose a check for current selection state - + $scope.isSelected = function (taskIndex) { - + return ($scope.selection.get() || {}).taskIndex === - + taskIndex; - + }; - - // Expose a view-level selection proxy - $scope.selection.proxy({ - addTask: function () { - dialogService.getUserInput(NEW_TASK_FORM, {}) - .then(addNewTask); - } - }); - } + }] + }; + + function TodoController($scope, dialogService) { + var showAll = true, + showCompleted; + + // Persist changes made to a domain object's model + function persist() { + var persistence = + $scope.domainObject.getCapability('persistence'); + return persistence && persistence.persist(); } - - return TodoController; - }); + + // Remove a task + function removeTaskAtIndex(taskIndex) { + $scope.domainObject.useCapability('mutation', function (model) { + model.tasks.splice(taskIndex, 1); + }); + persist(); + } + + // Add a task + function addNewTask(task) { + $scope.domainObject.useCapability('mutation', function (model) { + model.tasks.push(task); + }); + persist(); + } + + // Change which tasks are visible + $scope.setVisibility = function (all, completed) { + showAll = all; + showCompleted = completed; + }; + ++ // Check if current visibility settings match ++ $scope.checkVisibility = function (all, completed) { ++ return showAll ? all : (completed === showCompleted); ++ }; + + // Toggle the completion state of a task + $scope.toggleCompletion = function (taskIndex) { + $scope.domainObject.useCapability('mutation', function (model) { + var task = model.tasks[taskIndex]; + task.completed = !task.completed; + }); + persist(); + }; + + // Check whether a task should be visible + $scope.showTask = function (task) { + return showAll || (showCompleted === !!(task.completed)); + }; + + // Handle selection state in edit mode + if ($scope.selection) { + // Expose the ability to select tasks + $scope.selectTask = function (taskIndex) { + $scope.selection.select({ + removeTask: function () { + removeTaskAtIndex(taskIndex); + $scope.selection.deselect(); + }, ++ taskIndex: taskIndex + }); + }; + ++ // Expose a check for current selection state ++ $scope.isSelected = function (taskIndex) { ++ return ($scope.selection.get() || {}).taskIndex === ++ taskIndex; ++ }; + + // Expose a view-level selection proxy + $scope.selection.proxy({ + addTask: function () { + dialogService.getUserInput(NEW_TASK_FORM, {}) + .then(addNewTask); + } + }); + } + } + + return TodoController; +}); +``` __tutorials/todo/src/controllers/TodoController.js__ A summary of these changes: @@ -1068,35 +1095,38 @@ states visually, and to generally improve the appearance of our view. We add another file to the res directory of our bundle; this time, it is `css/todo.css` (with the `css` directory again being a convention.) - .example-todo div.example-button-group { - margin-top: 12px; - margin-bottom: 12px; - } - - .example-todo .example-button-group a { - padding: 3px; - margin: 3px; - } - - .example-todo .example-button-group a.selected { - border: 1px gray solid; - border-radius: 3px; - background: #444; - } - - .example-todo .example-task-completed .example-task-description { - text-decoration: line-through; - opacity: 0.75; - } - - .example-todo .example-task-description.selected { - background: #46A; - border-radius: 3px; - } - - .example-todo .example-message { - font-style: italic; - } +```diff +.example-todo div.example-button-group { + margin-top: 12px; + margin-bottom: 12px; +} + +.example-todo .example-button-group a { + padding: 3px; + margin: 3px; +} + +.example-todo .example-button-group a.selected { + border: 1px gray solid; + border-radius: 3px; + background: #444; +} + +.example-todo .example-task-completed .example-task-description { + text-decoration: line-through; + opacity: 0.75; +} + +.example-todo .example-task-description.selected { + background: #46A; + border-radius: 3px; +} + +.example-todo .example-message { + font-style: italic; +} +``` + __tutorials/todo/res/css/todo.css__ Here, we have defined classes and appearances for: @@ -1109,104 +1139,106 @@ Here, we have defined classes and appearances for: To include this CSS file in our running instance of Open MCT Web, we need to declare it in our bundle definition, this time as an extension of category `stylesheets`: - - { - "name": "To-do Plugin", - "description": "Allows creating and editing to-do lists.", - "extensions": { - "types": [ - { - "key": "example.todo", - "name": "To-Do List", - "glyph": "j", - "description": "A list of things that need to be done.", - "features": ["creation"], - "model": { - "tasks": [] - } +```diff +{ + "name": "To-do Plugin", + "description": "Allows creating and editing to-do lists.", + "extensions": { + "types": [ + { + "key": "example.todo", + "name": "To-Do List", + "glyph": "j", + "description": "A list of things that need to be done.", + "features": ["creation"], + "model": { + "tasks": [] } - ], - "views": [ - { - "key": "example.todo", - "type": "example.todo", - "glyph": "j", - "name": "List", - "templateUrl": "templates/todo.html", - "toolbar": { - "sections": [ - { - "items": [ - { - "text": "Add Task", - "glyph": "+", - "method": "addTask", - "control": "button" - } - ] - }, - { - "items": [ - { - "glyph": "Z", - "method": "removeTask", - "control": "button" - } - ] - } - ] - } + } + ], + "views": [ + { + "key": "example.todo", + "type": "example.todo", + "glyph": "j", + "name": "List", + "templateUrl": "templates/todo.html", + "toolbar": { + "sections": [ + { + "items": [ + { + "text": "Add Task", + "glyph": "+", + "method": "addTask", + "control": "button" + } + ] + }, + { + "items": [ + { + "glyph": "Z", + "method": "removeTask", + "control": "button" + } + ] + } + ] } - ], - "controllers": [ - { - "key": "TodoController", - "implementation": "controllers/TodoController.js", - "depends": [ "$scope", "dialogService" ] - } - ], - + "stylesheets": [ - + { - + "stylesheetUrl": "css/todo.css" - + } - + ] - } + } + ], + "controllers": [ + { + "key": "TodoController", + "implementation": "controllers/TodoController.js", + "depends": [ "$scope", "dialogService" ] + } + ], ++ "stylesheets": [ ++ { ++ "stylesheetUrl": "css/todo.css" ++ } ++ ] } +} +``` __tutorials/todo/bundle.json__ -Note that we’ve also removed our placeholder tasks from the `model` of the -To-Do List’s type above; now To-Do Lists will start off empty. +Note that we've also removed our placeholder tasks from the `model` of the +To-Do List's type above; now To-Do Lists will start off empty. -Finally, let’s utilize these changes from our view’s template: +Finally, let's utilize these changes from our view's template: +```diff ++
++
++ All ++ Incomplete ++ Complete +
- +
- +
- + All - + Incomplete - + Complete -
- -
    -
  • - - - {{task.description}} - -
  • -
- +
- + There are no tasks to show. - +
- +
+
    +
  • + + + {{task.description}} + +
  • +
++
++ There are no tasks to show. ++
++
+``` __tutorials/todo/res/templates/todo.html__ Now, if we reload our page and create a new To-Do List, we will initially see: @@ -1233,31 +1265,34 @@ there will be addressed in more brevity here. Since the goal is to introduce a new view and expose it from a plugin, we will want to create a new bundle which declares an extension of category `views`. -We’ll also be defining some custom styles, so we’ll include that extension as -well. We’ll be creating this plugin in `tutorials/bargraph`, so our initial +We'll also be defining some custom styles, so we'll include that extension as +well. We'll be creating this plugin in `tutorials/bargraph`, so our initial bundle definition looks like: - { - "name": "Bar Graph", - "description": "Provides the Bar Graph view of telemetry elements.", - "extensions": { - "views": [ - { - "name": "Bar Graph", - "key": "example.bargraph", - "glyph": "H", - "templateUrl": "templates/bargraph.html", - "needs": [ "telemetry" ], - "delegation": true - } - ], - "stylesheets": [ - { - "stylesheetUrl": "css/bargraph.css" - } - ] - } +```diff +{ + "name": "Bar Graph", + "description": "Provides the Bar Graph view of telemetry elements.", + "extensions": { + "views": [ + { + "name": "Bar Graph", + "key": "example.bargraph", + "glyph": "H", + "templateUrl": "templates/bargraph.html", + "needs": [ "telemetry" ], + "delegation": true + } + ], + "stylesheets": [ + { + "stylesheetUrl": "css/bargraph.css" + } + ] } +} +``` + __tutorials/bargraph/bundle.json__ The view definition should look familiar after the To-Do List tutorial, with @@ -1272,49 +1307,51 @@ via capability delegation; that is, by domain objects which delegate the used for Telemetry Panel objects as well as for individual telemetry-providing domain objects. -For this tutorial, we’ll assume that we’ve sketched out our template and CSS +For this tutorial, we'll assume that we've sketched out our template and CSS file ahead of time to describe the general look we want for the view. These look like: -
-
-
High
-
Middle
-
Low
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
+```diff +
+
+
High
+
Middle
+
Low
+
+ +
+
+
- -
-
- Label A +
+
-
- Label B -
-
- Label C +
+
+
+
+
+ +
+
+ Label A +
+
+ Label B +
+
+ Label C +
+
+
+``` __tutorials/bargraph/res/templates/bargraph.html__ Here, three regions are defined. The first will be for tick labels along the @@ -1324,80 +1361,81 @@ The third is for labels along the horizontal axis, which will indicate which bar corresponds to which telemetry point. Inline `style` attributes are used wherever dynamic positioning (handled by a script) is anticipated. The corresponding CSS file which styles and positions these elements: +```diff +.example-bargraph { + position: absolute; + top: 0; + bottom: 0; + right: 0; + left: 0; + mid-width: 160px; + min-height: 160px; +} - .example-bargraph { - position: absolute; - top: 0; - bottom: 0; - right: 0; - left: 0; - mid-width: 160px; - min-height: 160px; - } - - .example-bargraph .example-tick-labels { - position: absolute; - left: 0; - top: 24px; - bottom: 32px; - width: 72px; - font-size: 75%; - } - - .example-bargraph .example-tick-label { - position: absolute; - right: 0; - height: 1em; - margin-bottom: -0.5em; - padding-right: 6px; - text-align: right; - } - - .example-bargraph .example-graph-area { - position: absolute; - border: 1px gray solid; - left: 72px; - top: 24px; - bottom: 32px; - right: 0; - } - - .example-bargraph .example-bar-labels { - position: absolute; - left: 72px; - bottom: 0; - right: 0; - height: 32px; - } - - .example-bargraph .example-bar-holder { - position: absolute; - top: 0; - bottom: 0; - } - - .example-bargraph .example-graph-tick { - position: absolute; - width: 100%; - height: 1px; - border-bottom: 1px gray dashed; - } - - .example-bargraph .example-bar { - position: absolute; - background: darkcyan; - right: 4px; - left: 4px; - } - - .example-bargraph .example-label { - text-align: center; - font-size: 85%; - padding-top: 6px; - } +.example-bargraph .example-tick-labels { + position: absolute; + left: 0; + top: 24px; + bottom: 32px; + width: 72px; + font-size: 75%; +} + +.example-bargraph .example-tick-label { + position: absolute; + right: 0; + height: 1em; + margin-bottom: -0.5em; + padding-right: 6px; + text-align: right; +} + +.example-bargraph .example-graph-area { + position: absolute; + border: 1px gray solid; + left: 72px; + top: 24px; + bottom: 32px; + right: 0; +} + +.example-bargraph .example-bar-labels { + position: absolute; + left: 72px; + bottom: 0; + right: 0; + height: 32px; +} + +.example-bargraph .example-bar-holder { + position: absolute; + top: 0; + bottom: 0; +} + +.example-bargraph .example-graph-tick { + position: absolute; + width: 100%; + height: 1px; + border-bottom: 1px gray dashed; +} + +.example-bargraph .example-bar { + position: absolute; + background: darkcyan; + right: 4px; + left: 4px; +} + +.example-bargraph .example-label { + text-align: center; + font-size: 85%; + padding-top: 6px; +} +``` __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 for domain objects which provide telemetry (such as the example _Sine Wave Generator_) as well as for _Telemetry Panel_ objects: @@ -1409,10 +1447,10 @@ elements based on the actual contents of the domain object. ### Step 2-Add a Controller -Our next step will be to begin dynamically populating this template’s contents. +Our next step will be to begin dynamically populating this template's contents. Specifically, our goals for this step will be to: -* Show one bar per telemetry-providing domain object (for which we’ll be getting +* Show one bar per telemetry-providing domain object (for which we'll be getting actual telemetry data in subsequent steps.) * Show correct labels for these objects at the bottom. * Show numeric labels on the left-hand side. @@ -1420,40 +1458,41 @@ actual telemetry data in subsequent steps.) Notably, we will not try to show telemetry data after this step. To support this, we will add a new controller which supports our Bar Graph view: +```diff +define(function () { + function BarGraphController($scope, telemetryHandler) { + var handle; - define(function () { - function BarGraphController($scope, telemetryHandler) { - var handle; - - // Add min/max defaults - $scope.low = -1; - $scope.middle = 0; - $scope.high = 1; - - // Convert value to a percent between 0-100, keeping values in points - $scope.toPercent = function (value) { - var pct = 100 * (value - $scope.low) / ($scope.high - $scope.low); - return Math.min(100, Math.max(0, pct)); - }; - - // Use the telemetryHandler to get telemetry objects here - handle = telemetryHandler.handle($scope.domainObject, function () { - $scope.telemetryObjects = handle.getTelemetryObjects(); - $scope.barWidth = - 100 / Math.max(($scope.telemetryObjects).length, 1); - }); - - // Release subscriptions when scope is destroyed - $scope.$on('$destroy', handle.unsubscribe); - } - - return BarGraphController; - }); + // Add min/max defaults + $scope.low = -1; + $scope.middle = 0; + $scope.high = 1; + + // Convert value to a percent between 0-100, keeping values in points + $scope.toPercent = function (value) { + var pct = 100 * (value - $scope.low) / ($scope.high - $scope.low); + return Math.min(100, Math.max(0, pct)); + }; + + // Use the telemetryHandler to get telemetry objects here + handle = telemetryHandler.handle($scope.domainObject, function () { + $scope.telemetryObjects = handle.getTelemetryObjects(); + $scope.barWidth = + 100 / Math.max(($scope.telemetryObjects).length, 1); + }); + + // Release subscriptions when scope is destroyed + $scope.$on('$destroy', handle.unsubscribe); + } + + return BarGraphController; +}); +``` __tutorials/bargraph/src/controllers/BarGraphController.js__ -A summary of what we’ve done here: +A summary of what we've done here: -* We’re exposing some numeric values that will correspond to the _low_, _middle_, +* We're exposing some numeric values that will correspond to the _low_, _middle_, and _high_ end of the graph. (The `medium` attribute will be useful for positioning the middle line, which are graphs will ultimately descend down or push up from.) @@ -1470,39 +1509,40 @@ Whenever the telemetry handler invokes its callbacks, we update the set of telemetry objects in view, as well as the width for each bar. We will also utilize this from our template: - -
-
- +
- + {{value}} - +
-
- -
- +
-
-
- +
- +
-
-
- -
- +
- + - + - +
-
+```diff +
+
++
++ {{value}} ++
+ +
++
+
+
++
++
+
+
+ +
++
++ ++ ++
+
+
+``` __tutorials/bargraph/res/templates/bargraph.html__ Summarizing these changes: @@ -1522,34 +1562,36 @@ Finally, we expose our controller from our bundle definition. Note that the depends declaration includes both `$scope` as well as the `telemetryHandler` service we made use of. - { - "name": "Bar Graph", - "description": "Provides the Bar Graph view of telemetry elements.", - "extensions": { - "views": [ - { - "name": "Bar Graph", - "key": "example.bargraph", - "glyph": "H", - "templateUrl": "templates/bargraph.html", - "needs": [ "telemetry" ], - "delegation": true - } - ], - "stylesheets": [ - { - "stylesheetUrl": "css/bargraph.css" - } - ], - + "controllers": [ - + { - + "key": "BarGraphController", - + "implementation": "controllers/BarGraphController.js", - + "depends": [ "$scope", "telemetryHandler" ] - + } - + ] - } +```diff +{ + "name": "Bar Graph", + "description": "Provides the Bar Graph view of telemetry elements.", + "extensions": { + "views": [ + { + "name": "Bar Graph", + "key": "example.bargraph", + "glyph": "H", + "templateUrl": "templates/bargraph.html", + "needs": [ "telemetry" ], + "delegation": true + } + ], + "stylesheets": [ + { + "stylesheetUrl": "css/bargraph.css" + } + ], ++ "controllers": [ ++ { ++ "key": "BarGraphController", ++ "implementation": "controllers/BarGraphController.js", ++ "depends": [ "$scope", "telemetryHandler" ] ++ } ++ ] } +} +``` __tutorials/bargraph/bundle.json__ When we reload Open MCT Web, we are now able to see that our bar graph view @@ -1560,52 +1602,53 @@ this Telemetry Panel containing four Sine Wave Generators. ### Step 3-Using Telemetry Data -Now that our bar graph is labeled correctly, it’s time to start putting data +Now that our bar graph is labeled correctly, it's time to start putting data into the view. -First, let’s add expose some more functionality from our controller. To make it -simple, we’ll expose the top and bottom for a bar graph for a given +First, let's add expose some more functionality from our controller. To make it +simple, we'll expose the top and bottom for a bar graph for a given telemetry-providing domain object, as percentages. +```diff +define(function () { + function BarGraphController($scope, telemetryHandler) { + var handle; - define(function () { - function BarGraphController($scope, telemetryHandler) { - var handle; - - // Add min/max defaults - $scope.low = -1; - $scope.middle = 0; - $scope.high = 1; - - // Convert value to a percent between 0-100, keeping values in points - $scope.toPercent = function (value) { - var pct = 100 * (value - $scope.low) / ($scope.high - $scope.low); - return Math.min(100, Math.max(0, pct)); - }; - - // Get bottom and top (as percentages) for current value - + $scope.getBottom = function (telemetryObject) { - + var value = handle.getRangeValue(telemetryObject); - + return $scope.toPercent(Math.min($scope.middle, value)); - + } - + $scope.getTop = function (telemetryObject) { - + var value = handle.getRangeValue(telemetryObject); - + return 100 - $scope.toPercent(Math.max($scope.middle, value)); - + } - - // Use the telemetryHandler to get telemetry objects here - handle = telemetryHandler.handle($scope.domainObject, function () { - $scope.telemetryObjects = handle.getTelemetryObjects(); - $scope.barWidth = - 100 / Math.max(($scope.telemetryObjects).length, 1); - }); - - // Release subscriptions when scope is destroyed - $scope.$on('$destroy', handle.unsubscribe); - } - - return BarGraphController; - }); + // Add min/max defaults + $scope.low = -1; + $scope.middle = 0; + $scope.high = 1; + + // Convert value to a percent between 0-100, keeping values in points + $scope.toPercent = function (value) { + var pct = 100 * (value - $scope.low) / ($scope.high - $scope.low); + return Math.min(100, Math.max(0, pct)); + }; + + // Get bottom and top (as percentages) for current value ++ $scope.getBottom = function (telemetryObject) { ++ var value = handle.getRangeValue(telemetryObject); ++ return $scope.toPercent(Math.min($scope.middle, value)); ++ } ++ $scope.getTop = function (telemetryObject) { ++ var value = handle.getRangeValue(telemetryObject); ++ return 100 - $scope.toPercent(Math.max($scope.middle, value)); ++ } + + // Use the telemetryHandler to get telemetry objects here + handle = telemetryHandler.handle($scope.domainObject, function () { + $scope.telemetryObjects = handle.getTelemetryObjects(); + $scope.barWidth = + 100 / Math.max(($scope.telemetryObjects).length, 1); + }); + + // Release subscriptions when scope is destroyed + $scope.$on('$destroy', handle.unsubscribe); + } + + return BarGraphController; +}); +``` __tutorials/bargraph/src/controllers/BarGraphController.js__ The `telemetryHandler` exposes a method to provide us with our latest data value @@ -1618,41 +1661,44 @@ decide this. Next, we utilize this functionality from the template: -
-
-
- {{value}} -
-
- -
-
-
-
-
-
-
-
- -
-
- - -
+```diff +
+
+
+ {{value}}
+ +
+
+
+
+
+
+
+
+ +
+
+ + +
+
+
+''' + __tutorials/bargraph/res/templates/bargraph.html__ Here, we utilize the functions we just provided from the controller to position @@ -1664,156 +1710,160 @@ When we reload Open MCT Web, our bar graph view now looks like: ### Step 4-View Configuration -The default minimum and maximum values we’ve provided happen to make sense for +The default minimum and maximum values we've provided happen to make sense for sine waves, but what about other values? We want to provide the user with a means of configuring these boundaries. This is normally done via Edit mode. Since view configuration is a common problem, the Open MCT Web 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. -First, let’s add a tool bar for changing these three values in Edit mode: +First, let's add a tool bar for changing these three values in Edit mode: - { - "name": "Bar Graph", - "description": "Provides the Bar Graph view of telemetry elements.", - "extensions": { - "views": [ - { - "name": "Bar Graph", - "key": "example.bargraph", - "glyph": "H", - "templateUrl": "templates/bargraph.html", - "needs": [ "telemetry" ], - "delegation": true, - + "toolbar": { - + "sections": [ - + { - + "items": [ - + { - + "name": "Low", - + "property": "low", - + "required": true, - + "control": "textfield", - + "size": 4 - + }, - + { - + "name": "Middle", - + "property": "middle", - + "required": true, - + "control": "textfield", - + "size": 4 - + }, - + { - + "name": "High", - + "property": "high", - + "required": true, - + "control": "textfield", - + "size": 4 - + } - + ] - + } - ] - } +```diff +{ + "name": "Bar Graph", + "description": "Provides the Bar Graph view of telemetry elements.", + "extensions": { + "views": [ + { + "name": "Bar Graph", + "key": "example.bargraph", + "glyph": "H", + "templateUrl": "templates/bargraph.html", + "needs": [ "telemetry" ], + "delegation": true, ++ "toolbar": { ++ "sections": [ ++ { ++ "items": [ ++ { ++ "name": "Low", ++ "property": "low", ++ "required": true, ++ "control": "textfield", ++ "size": 4 ++ }, ++ { ++ "name": "Middle", ++ "property": "middle", ++ "required": true, ++ "control": "textfield", ++ "size": 4 ++ }, ++ { ++ "name": "High", ++ "property": "high", ++ "required": true, ++ "control": "textfield", ++ "size": 4 ++ } ++ ] ++ } + ] } - ], - "stylesheets": [ - { - "stylesheetUrl": "css/bargraph.css" - } - ], - "controllers": [ - { - "key": "BarGraphController", - "implementation": "controllers/BarGraphController.js", - "depends": [ "$scope", "telemetryHandler" ] - } - ] - } + } + ], + "stylesheets": [ + { + "stylesheetUrl": "css/bargraph.css" + } + ], + "controllers": [ + { + "key": "BarGraphController", + "implementation": "controllers/BarGraphController.js", + "depends": [ "$scope", "telemetryHandler" ] + } + ] } +} +``` __tutorials/bargraph/bundle.json__ As we saw in to To-Do List plugin, a tool bar needs either a selected object or a view proxy to work from. We will add this to our controller, and additionally -will start reading/writing those properties to the view’s `configuration` +will start reading/writing those properties to the view's `configuration` object. - define(function () { - function BarGraphController($scope, telemetryHandler) { - var handle; - - + // Expose configuration constants directly in scope - + function exposeConfiguration() { - + $scope.low = $scope.configuration.low; - + $scope.middle = $scope.configuration.middle; - + $scope.high = $scope.configuration.high; - + } - - + // Populate a default value in the configuration - + function setDefault(key, value) { - + if ($scope.configuration[key] === undefined) { - + $scope.configuration[key] = value; - + } - + } - - + // Getter-setter for configuration properties (for view proxy) - + function getterSetter(property) { - + return function (value) { - + value = parseFloat(value); - + if (!isNaN(value)) { - + $scope.configuration[property] = value; - + exposeConfiguration(); - + } - + return $scope.configuration[property]; - + }; - } - - + // Add min/max defaults - + setDefault('low', -1); - + setDefault('middle', 0); - + setDefault('high', 1); - + exposeConfiguration($scope.configuration); - - + // Expose view configuration options - + if ($scope.selection) { - + $scope.selection.proxy({ - + low: getterSetter('low'), - + middle: getterSetter('middle'), - + high: getterSetter('high') - + }); - + } - - // Convert value to a percent between 0-100 - $scope.toPercent = function (value) { - var pct = 100 * (value - $scope.low) / - ($scope.high - $scope.low); - return Math.min(100, Math.max(0, pct)); - }; - - // Get bottom and top (as percentages) for current value - $scope.getBottom = function (telemetryObject) { - var value = handle.getRangeValue(telemetryObject); - return $scope.toPercent(Math.min($scope.middle, value)); - } - $scope.getTop = function (telemetryObject) { - var value = handle.getRangeValue(telemetryObject); - return 100 - $scope.toPercent(Math.max($scope.middle, value)); - } - - // Use the telemetryHandler to get telemetry objects here - handle = telemetryHandler.handle($scope.domainObject, function () { - $scope.telemetryObjects = handle.getTelemetryObjects(); - $scope.barWidth = - 100 / Math.max(($scope.telemetryObjects).length, 1); - }); - - // Release subscriptions when scope is destroyed - $scope.$on('$destroy', handle.unsubscribe); +```diff +define(function () { + function BarGraphController($scope, telemetryHandler) { + var handle; + ++ // Expose configuration constants directly in scope ++ function exposeConfiguration() { ++ $scope.low = $scope.configuration.low; ++ $scope.middle = $scope.configuration.middle; ++ $scope.high = $scope.configuration.high; ++ } + ++ // Populate a default value in the configuration ++ function setDefault(key, value) { ++ if ($scope.configuration[key] === undefined) { ++ $scope.configuration[key] = value; ++ } ++ } + ++ // Getter-setter for configuration properties (for view proxy) ++ function getterSetter(property) { ++ return function (value) { ++ value = parseFloat(value); ++ if (!isNaN(value)) { ++ $scope.configuration[property] = value; ++ exposeConfiguration(); ++ } ++ return $scope.configuration[property]; ++ }; } - - return BarGraphController; - }); + ++ // Add min/max defaults ++ setDefault('low', -1); ++ setDefault('middle', 0); ++ setDefault('high', 1); ++ exposeConfiguration($scope.configuration); + ++ // Expose view configuration options ++ if ($scope.selection) { ++ $scope.selection.proxy({ ++ low: getterSetter('low'), ++ middle: getterSetter('middle'), ++ high: getterSetter('high') ++ }); ++ } + + // Convert value to a percent between 0-100 + $scope.toPercent = function (value) { + var pct = 100 * (value - $scope.low) / + ($scope.high - $scope.low); + return Math.min(100, Math.max(0, pct)); + }; + + // Get bottom and top (as percentages) for current value + $scope.getBottom = function (telemetryObject) { + var value = handle.getRangeValue(telemetryObject); + return $scope.toPercent(Math.min($scope.middle, value)); + } + $scope.getTop = function (telemetryObject) { + var value = handle.getRangeValue(telemetryObject); + return 100 - $scope.toPercent(Math.max($scope.middle, value)); + } + + // Use the telemetryHandler to get telemetry objects here + handle = telemetryHandler.handle($scope.domainObject, function () { + $scope.telemetryObjects = handle.getTelemetryObjects(); + $scope.barWidth = + 100 / Math.max(($scope.telemetryObjects).length, 1); + }); + + // Release subscriptions when scope is destroyed + $scope.$on('$destroy', handle.unsubscribe); + } + + return BarGraphController; +}); +``` __tutorials/bargraph/src/controllers/BarGraphController.js__ A summary of these changes: @@ -1823,11 +1873,11 @@ initializing them to explicit values. This is placed into its own function, since it will be called a lot. * The function `setDefault` is included; it will be used to set the default values for `low`, `middle`, and `high` in the view configuration, but only if -they aren’t present. +they aren't present. * The tool bar will treat properties in a view proxy as getter-setters if they are functions; that is, they will be called with an argument to be used as a setter, and with no argument to use as a getter. We provide ourselves a -function for making these getter-setters (since we’ll need three) that +function for making these getter-setters (since we'll need three) that additionally handles some checking to ensure that these are actually numbers. * After that, we actually initialize both the view `configuration` object with defaults (if needed), and expose its state into the scope. @@ -1860,134 +1910,135 @@ For purposes of this tutorial, a simple node server is provided to stand in place of this existing telemetry system. It generates real-time data and exposes it over a WebSocket connection. +```diff +/*global require,process,console*/ - /*global require,process,console*/ - - var CONFIG = { - port: 8081, - dictionary: "dictionary.json", - interval: 1000 - }; - - (function () { - "use strict"; - - var WebSocketServer = require('ws').Server, - fs = require('fs'), - wss = new WebSocketServer({ port: CONFIG.port }), - dictionary = JSON.parse(fs.readFileSync(CONFIG.dictionary, "utf8")), - spacecraft = { - "prop.fuel": 77, - "prop.thrusters": "OFF", - "comms.recd": 0, - "comms.sent": 0, - "pwr.temp": 245, - "pwr.c": 8.15, - "pwr.v": 30 - }, - histories = {}, - listeners = []; - - function updateSpacecraft() { - spacecraft["prop.fuel"] = Math.max( - 0, - spacecraft["prop.fuel"] - - (spacecraft["prop.thrusters"] === "ON" ? 0.5 : 0) - ); - spacecraft["pwr.temp"] = spacecraft["pwr.temp"] * 0.985 - + Math.random() * 0.25 + Math.sin(Date.now()); - spacecraft["pwr.c"] = spacecraft["pwr.c"] * 0.985; - spacecraft["pwr.v"] = 30 + Math.pow(Math.random(), 3); - } - - function generateTelemetry() { - var timestamp = Date.now(), sent = 0; - Object.keys(spacecraft).forEach(function (id) { - var state = { timestamp: timestamp, value: spacecraft[id] }; - histories[id] = histories[id] || []; // Initialize - histories[id].push(state); - spacecraft["comms.sent"] += JSON.stringify(state).length; - }); - listeners.forEach(function (listener) { - listener(); - }); - } - - function update() { - updateSpacecraft(); - generateTelemetry(); - } - - function handleConnection(ws) { - var subscriptions = {}, // Active subscriptions for this connection - handlers = { // Handlers for specific requests - dictionary: function () { - ws.send(JSON.stringify({ - type: "dictionary", - value: dictionary - })); - }, - subscribe: function (id) { - subscriptions[id] = true; - }, - unsubscribe: function (id) { - delete subscriptions[id]; - }, - history: function (id) { - ws.send(JSON.stringify({ - type: "history", - id: id, - value: histories[id] - })); - } - }; - - function notifySubscribers() { - Object.keys(subscriptions).forEach(function (id) { - var history = histories[id]; - if (history) { - ws.send(JSON.stringify({ - type: "data", - id: id, - value: history[history.length - 1] - })); - } - }); - } - - // Listen for requests - ws.on('message', function (message) { - var parts = message.split(' '), - handler = handlers[parts[0]]; - if (handler) { - handler.apply(handlers, parts.slice(1)); +var CONFIG = { + port: 8081, + dictionary: "dictionary.json", + interval: 1000 +}; + +(function () { + "use strict"; + + var WebSocketServer = require('ws').Server, + fs = require('fs'), + wss = new WebSocketServer({ port: CONFIG.port }), + dictionary = JSON.parse(fs.readFileSync(CONFIG.dictionary, "utf8")), + spacecraft = { + "prop.fuel": 77, + "prop.thrusters": "OFF", + "comms.recd": 0, + "comms.sent": 0, + "pwr.temp": 245, + "pwr.c": 8.15, + "pwr.v": 30 + }, + histories = {}, + listeners = []; + + function updateSpacecraft() { + spacecraft["prop.fuel"] = Math.max( + 0, + spacecraft["prop.fuel"] - + (spacecraft["prop.thrusters"] === "ON" ? 0.5 : 0) + ); + spacecraft["pwr.temp"] = spacecraft["pwr.temp"] * 0.985 + + Math.random() * 0.25 + Math.sin(Date.now()); + spacecraft["pwr.c"] = spacecraft["pwr.c"] * 0.985; + spacecraft["pwr.v"] = 30 + Math.pow(Math.random(), 3); + } + + function generateTelemetry() { + var timestamp = Date.now(), sent = 0; + Object.keys(spacecraft).forEach(function (id) { + var state = { timestamp: timestamp, value: spacecraft[id] }; + histories[id] = histories[id] || []; // Initialize + histories[id].push(state); + spacecraft["comms.sent"] += JSON.stringify(state).length; + }); + listeners.forEach(function (listener) { + listener(); + }); + } + + function update() { + updateSpacecraft(); + generateTelemetry(); + } + + function handleConnection(ws) { + var subscriptions = {}, // Active subscriptions for this connection + handlers = { // Handlers for specific requests + dictionary: function () { + ws.send(JSON.stringify({ + type: "dictionary", + value: dictionary + })); + }, + subscribe: function (id) { + subscriptions[id] = true; + }, + unsubscribe: function (id) { + delete subscriptions[id]; + }, + history: function (id) { + ws.send(JSON.stringify({ + type: "history", + id: id, + value: histories[id] + })); + } + }; + + function notifySubscribers() { + Object.keys(subscriptions).forEach(function (id) { + var history = histories[id]; + if (history) { + ws.send(JSON.stringify({ + type: "data", + id: id, + value: history[history.length - 1] + })); } }); - - // Stop sending telemetry updates for this connection when closed - ws.on('close', function () { - listeners = listeners.filter(function (listener) { - return listener !== notifySubscribers; - }); - }); - - // Notify subscribers when telemetry is updated - listeners.push(notifySubscribers); } - - update(); - setInterval(update, CONFIG.interval); - - wss.on('connection', handleConnection); - - console.log("Example spacecraft running on port "); - console.log("Press Enter to toggle thruster state."); - process.stdin.on('data', function (data) { - spacecraft['prop.thrusters'] = - (spacecraft['prop.thrusters'] === "OFF") ? "ON" : "OFF"; - console.log("Thrusters " + spacecraft["prop.thrusters"]); + + // Listen for requests + ws.on('message', function (message) { + var parts = message.split(' '), + handler = handlers[parts[0]]; + if (handler) { + handler.apply(handlers, parts.slice(1)); + } }); - }()); + + // Stop sending telemetry updates for this connection when closed + ws.on('close', function () { + listeners = listeners.filter(function (listener) { + return listener !== notifySubscribers; + }); + }); + + // Notify subscribers when telemetry is updated + listeners.push(notifySubscribers); + } + + update(); + setInterval(update, CONFIG.interval); + + wss.on('connection', handleConnection); + + console.log("Example spacecraft running on port "); + console.log("Press Enter to toggle thruster state."); + process.stdin.on('data', function (data) { + spacecraft['prop.thrusters'] = + (spacecraft['prop.thrusters'] === "OFF") ? "ON" : "OFF"; + console.log("Thrusters " + spacecraft["prop.thrusters"]); + }); +}()); +``` __tutorial-server/app.js__ For purposes of this tutorial, how this server has been implemented is @@ -1995,7 +2046,7 @@ not important; it has just enough functionality to resemble a WebSocket interface to a real telemetry system, and niceties such as error-handling have been omitted. (For more information on using WebSockets, both in the client and on the server, -[https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API]() is an +https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API is an excellent starting point.) What does matter for this tutorial is the interfaces that are exposed. Once a @@ -2005,108 +2056,110 @@ messages in the following formats, and issues JSON-formatted responses. The requests it handles are: * `dictionary`: Responds with a JSON response with the following fields: - * `type`: “dictionary” + * `type`: "dictionary" * `value`: … the telemetry dictionary (see below) … * `subscribe `: Subscribe to new telemetry data for the measurement with the provided identifier. The server will begin sending messages of the following form: - * `type`: “data” + * `type`: "data" * `id`: The identifier for the measurement. * `value`: An object containing the actual measurement, in two fields: - * `timestamp`: A UNIX timestamp (in milliseconds) for the “measurement” + * `timestamp`: A UNIX timestamp (in milliseconds) for the "measurement" * `value`: The data value for the measurement (either a number, or a string) * `unsubscribe `: Stop receiving new data for the identified measurement. * `history `: Request a history of all telemetry data for the identified measurement. - * `type`: “history” + * `type`: "history" * `id`: The identifier for the measurement. * `value`: An array of objects containing the actual measurement, each of which having two fields: - * `timestamp`: A UNIX timestamp (in milliseconds) for the “measurement” + * `timestamp`: A UNIX timestamp (in milliseconds) for the "measurement" * `value`: The data value for the measurement (either a number, or a string) -(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, mnemonics, telemetry points, or other names. No preference is made here; Open MCT Web is easily adapted to use the terminology appropriate to your system.) 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" on and off, having observable changes in telemetry. The telemetry dictionary referenced previously is contained in a separate file, used by the server. It uses a custom format and, for purposes of example, -contains three “subsystems” containing a mix of numeric and string-based +contains three "subsystems" containing a mix of numeric and string-based telemetry. - { - "name": "Example Spacecraft", - "identifier": "sc", - "subsystems": [ - { - "name": "Propulsion", - "identifier": "prop", - "measurements": [ - { - "name": "Fuel", - "identifier": "prop.fuel", - "units": "kilograms", - "type": "float" - }, - { - "name": "Thrusters", - "identifier": "prop.thrusters", - "units": "None", - "type": "string" - } - ] - }, - { - "name": "Communications", - "identifier": "comms", - "measurements": [ - { - "name": "Received", - "identifier": "comms.recd", - "units": "bytes", - "type": "integer" - }, - { - "name": "Sent", - "identifier": "comms.sent", - "units": "bytes", - "type": "integer" - } - ] - }, - { - "name": "Power", - "identifier": "pwr", - "measurements": [ - { - "name": "Generator Temperature", - "identifier": "pwr.temp", - "units": "\u0080C", - "type": "float" - }, - { - "name": "Generator Current", - "identifier": "pwr.c", - "units": "A", - "type": "float" - }, - { - "name": "Generator Voltage", - "identifier": "pwr.v", - "units": "V", - "type": "float" - } - ] - } - ] - } +```diff +{ + "name": "Example Spacecraft", + "identifier": "sc", + "subsystems": [ + { + "name": "Propulsion", + "identifier": "prop", + "measurements": [ + { + "name": "Fuel", + "identifier": "prop.fuel", + "units": "kilograms", + "type": "float" + }, + { + "name": "Thrusters", + "identifier": "prop.thrusters", + "units": "None", + "type": "string" + } + ] + }, + { + "name": "Communications", + "identifier": "comms", + "measurements": [ + { + "name": "Received", + "identifier": "comms.recd", + "units": "bytes", + "type": "integer" + }, + { + "name": "Sent", + "identifier": "comms.sent", + "units": "bytes", + "type": "integer" + } + ] + }, + { + "name": "Power", + "identifier": "pwr", + "measurements": [ + { + "name": "Generator Temperature", + "identifier": "pwr.temp", + "units": "\u0080C", + "type": "float" + }, + { + "name": "Generator Current", + "identifier": "pwr.c", + "units": "A", + "type": "float" + }, + { + "name": "Generator Voltage", + "identifier": "pwr.v", + "units": "V", + "type": "float" + } + ] + } + ] +} +``` __tutorial-server/dictionary.json__ It should be noted that neither the interface for the example server nor the @@ -2121,20 +2174,20 @@ We can run this example server by: node app.js To verify that this is running and try out its interface, we can use a tool -like [https://www.npmjs.com/package/wscat](): +like https://www.npmjs.com/package/wscat : wscat -c ws://localhost:8081 connected (press CTRL+C to quit) > dictionary < {"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. ### Step 1-Add a Top-level Object -Since Open MCT Web 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 +Since Open MCT Web 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 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 will populate this with the contents of the telemetry dictionary (which we @@ -2165,60 +2218,63 @@ will retrieve from the server.) } __tutorials/telemetry/bundle.json__ -Here, we’ve created our initial telemetry plugin. This exposes a new domain -object type (the “Spacecraft”, which will be represented by the contents of the +Here, we've created our initial telemetry plugin. This exposes a new domain +object type (the "Spacecraft", which will be represented by the contents of the telemetry dictionary) and also adds one instance of it as a root-level object (by declaring an extension of category roots.) We have also set priority to preferred so that this shows up near the top, instead of below My Items. If we include this in our set of active bundles: - [ - "platform/framework", - "platform/core", - "platform/representation", - "platform/commonUI/about", - "platform/commonUI/browse", - "platform/commonUI/edit", - "platform/commonUI/dialog", - "platform/commonUI/general", - "platform/containment", - "platform/telemetry", - "platform/features/layout", - "platform/features/pages", - "platform/features/plot", - "platform/features/scrolling", - "platform/forms", - "platform/persistence/queue", - "platform/policy", - - "example/persistence", - "example/generator" - ] - [ - "platform/framework", - "platform/core", - "platform/representation", - "platform/commonUI/about", - "platform/commonUI/browse", - "platform/commonUI/edit", - "platform/commonUI/dialog", - "platform/commonUI/general", - "platform/containment", - "platform/telemetry", - "platform/features/layout", - "platform/features/pages", - "platform/features/plot", - "platform/features/scrolling", - "platform/forms", - "platform/persistence/queue", - "platform/policy", - - "example/persistence", - "example/generator", - - + "tutorials/telemetry" - ] +```diff +[ + "platform/framework", + "platform/core", + "platform/representation", + "platform/commonUI/about", + "platform/commonUI/browse", + "platform/commonUI/edit", + "platform/commonUI/dialog", + "platform/commonUI/general", + "platform/containment", + "platform/telemetry", + "platform/features/layout", + "platform/features/pages", + "platform/features/plot", + "platform/features/scrolling", + "platform/forms", + "platform/persistence/queue", + "platform/policy", + + "example/persistence", + "example/generator" +] +[ + "platform/framework", + "platform/core", + "platform/representation", + "platform/commonUI/about", + "platform/commonUI/browse", + "platform/commonUI/edit", + "platform/commonUI/dialog", + "platform/commonUI/general", + "platform/containment", + "platform/telemetry", + "platform/features/layout", + "platform/features/pages", + "platform/features/plot", + "platform/features/scrolling", + "platform/forms", + "platform/persistence/queue", + "platform/policy", + + "example/persistence", + "example/generator", + ++ "tutorials/telemetry" +] +``` + __bundles.json__ ...we will be able to reload Open MCT Web and see that it is present: @@ -2277,8 +2333,8 @@ __tutorials/telemetry/src/ExampleTelemetryServerAdapter.js__ When created, this service initiates a connection to the server, and begins loading the dictionary. This will occur asynchronously, so the `dictionary()` method it exposes returns a `Promise` for the loaded dictionary -(`dictionary.json` from above), using Angular’s `$q` -(see [https://docs.angularjs.org/api/ng/service/$q]().) Note that error- and +(`dictionary.json` from above), using Angular's `$q` +(see https://docs.angularjs.org/api/ng/service/$q .) Note that error- and close-handling for this WebSocket connection have been omitted for brevity. Once the dictionary has been loaded, we will want to represent its contents @@ -2377,10 +2433,10 @@ object models from the persistence store.) Here, we read the dictionary using the server adapter from above; since this will be loaded asynchronously, we use promise-chaining (see -[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then#Chaining]()) +https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then#Chaining ) to take that result and build up an object mapping identifiers to new domain object models. This is returned from our `modelService`, but only when the -request actually calls for identifiers that look like they’re from the +request actually calls for identifiers that look like they're from the dictionary. This means that loading other models is not blocked by loading the dictionary. (Note that the `modelService` contract allows us to return either a sub- or superset of the requested models, so it is fine to always return the @@ -2412,11 +2468,11 @@ ordering properties of the data, but this will be the same for all measurements, so we will define that later at the type level.) * This field (whose contents will be merged atop the telemetry property we define at the type-level) will serve as a template for later `telemetry` -requests to the `telemetryService`, so we’ll see the properties we define here +requests to the `telemetryService`, so we'll see the properties we define here again later in Steps 3 and 4. This allows our telemetry dictionary to be expressed as domain object models -(and, in turn, as domain objects), but these objects still aren’t reachable. To +(and, in turn, as domain objects), but these objects still aren't reachable. To fix this, we will need another script which will add these subsystems to the root-level object we added in Step 1. @@ -2473,91 +2529,93 @@ __tutorials/telemetry/src/ExampleTelemetryInitializer.js__ At the conclusion of Step 1, the top-level My Spacecraft object was empty. This script will wait for the dictionary to be loaded, then load My Spacecraft (by -its identifier), and “mutate” it. The `mutation` capability allows changes to be -made to a domain object’s model. Here, we take this top-level object, update its +its identifier), and "mutate" it. The `mutation` capability allows changes to be +made to a domain object's model. Here, we take this top-level object, update its name to match what was in the dictionary, and set its `composition` to an array of domain object identifiers for all subsystems contained in the dictionary (using the same identifier prefix as before.) -Finally, we wire in these changes by modifying our plugin’s `bundle.json` to +Finally, we wire in these changes by modifying our plugin's `bundle.json` to provide metadata about how these pieces interact (both with each other, and with the platform): - { - "name": "Example Telemetry Adapter", - "extensions": { - "types": [ - { - "name": "Spacecraft", - "key": "example.spacecraft", - "glyph": "o" - }, - { - + "name": "Subsystem", - + "key": "example.subsystem", - + "glyph": "o", - + "model": { "composition": [] } - + }, - + { - + "name": "Measurement", - + "key": "example.measurement", - + "glyph": "T", - + "model": { "telemetry": {} }, - + "telemetry": { - + "source": "example.source", - + "domains": [ - + { - + "name": "Time", - + "key": "timestamp" - + } - + ] - + } - + } - ], - "roots": [ - { - "id": "example:sc", - "priority": "preferred", - "model": { - "type": "example.spacecraft", - "name": "My Spacecraft", - "composition": [] - } +```diff +{ + "name": "Example Telemetry Adapter", + "extensions": { + "types": [ + { + "name": "Spacecraft", + "key": "example.spacecraft", + "glyph": "o" + }, + { ++ "name": "Subsystem", ++ "key": "example.subsystem", ++ "glyph": "o", ++ "model": { "composition": [] } ++ }, ++ { ++ "name": "Measurement", ++ "key": "example.measurement", ++ "glyph": "T", ++ "model": { "telemetry": {} }, ++ "telemetry": { ++ "source": "example.source", ++ "domains": [ ++ { ++ "name": "Time", ++ "key": "timestamp" ++ } ++ ] ++ } ++ } + ], + "roots": [ + { + "id": "example:sc", + "priority": "preferred", + "model": { + "type": "example.spacecraft", + "name": "My Spacecraft", + "composition": [] } - ], - + "services": [ - + { - + "key": "example.adapter", - + "implementation": "ExampleTelemetryServerAdapter.js", - + "depends": [ "$q", "EXAMPLE_WS_URL" ] - + } - + ], - + "constants": [ - + { - + "key": "EXAMPLE_WS_URL", - + "priority": "fallback", - + "value": "ws://localhost:8081" - + } - + ], - + "runs": [ - + { - + "implementation": "ExampleTelemetryInitializer.js", - + "depends": [ "example.adapter", "objectService" ] - + } - + ], - + "components": [ - + { - + "provides": "modelService", - + "type": "provider", - + "implementation": "ExampleTelemetryModelProvider.js", - + "depends": [ "example.adapter", "$q" ] - + } - + ] - } + } + ], ++ "services": [ ++ { ++ "key": "example.adapter", ++ "implementation": "ExampleTelemetryServerAdapter.js", ++ "depends": [ "$q", "EXAMPLE_WS_URL" ] ++ } ++ ], ++ "constants": [ ++ { ++ "key": "EXAMPLE_WS_URL", ++ "priority": "fallback", ++ "value": "ws://localhost:8081" ++ } ++ ], ++ "runs": [ ++ { ++ "implementation": "ExampleTelemetryInitializer.js", ++ "depends": [ "example.adapter", "objectService" ] ++ } ++ ], ++ "components": [ ++ { ++ "provides": "modelService", ++ "type": "provider", ++ "implementation": "ExampleTelemetryModelProvider.js", ++ "depends": [ "example.adapter", "$q" ] ++ } ++ ] } +} +``` __tutorials/telemetry/bundle.json__ -A summary of what we’ve added here: +A summary of what we've added here: * New type definitions have been added to represent Subsystems and Measurements, respectively. @@ -2565,7 +2623,7 @@ respectively. field added in the model, but contains properties that will be common among all Measurements. In particular, the `source` field will be used later as a symbolic identifier for the telemetry data source. - * We have also added some “initial models” for these two types using the + * We have also added some "initial models" for these two types using the `model` field. While domain objects of these types cannot be created via the Create menu, some policies will look at initial models to predict what capabilities domain objects of certain types would have, so we want to @@ -2582,13 +2640,13 @@ different URLs for the WebSocket connection. to ensure that this executes (and populates the contents of the top-level My Spacecraft object) once Open MCT Web is started. * 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. * Finally, the `modelService` provider which presents dictionary elements as domain object models is exposed. Since `modelService` is a composite service, this is registered under the extension category `components`. * As with the initializer, this depends upon the `example.adapter` service - 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. Now if we run Open MCT Web (assuming our example telemetry server is also @@ -2598,67 +2656,68 @@ dictionary: ![Telemetry 2](images/telemetry-2.png) -Note that “My Spacecraft” has changed its name to “Example Spacecraft”, which +Note that "My Spacecraft" has changed its name to "Example Spacecraft", which is the name it had in the dictionary. ### Step 3-Historical Telemetry After Step 2, we are able to see our dictionary in the user interface and click -around our different measurements, but we don’t see any data. We need to give +around our different measurements, but we don't see any data. We need to give ourselves the ability to retrieve this data from the server. In this step, we -will do so for the server’s historical telemetry. +will do so for the server's historical telemetry. Our first step will be to add a method to our server adapter which allows us to send history requests to the server: - /*global define,WebSocket*/ - - define( - [], - function () { - "use strict"; - - function ExampleTelemetryServerAdapter($q, wsUrl) { - var ws = new WebSocket(wsUrl), - + histories = {}, - dictionary = $q.defer(); - - // Handle an incoming message from the server - ws.onmessage = function (event) { - var message = JSON.parse(event.data); - - switch (message.type) { - case "dictionary": - dictionary.resolve(message.value); - break; - + case "history": - + histories[message.id].resolve(message); - + delete histories[message.id]; - + break; - } - }; - - // Request dictionary once connection is established - ws.onopen = function () { - ws.send("dictionary"); - }; - - return { - dictionary: function () { - return dictionary.promise; - }, - + history: function (id) { - + histories[id] = histories[id] || $q.defer(); - + ws.send("history " + id); - + return histories[id].promise; - + } - }; - } - - return ExampleTelemetryServerAdapter; - } - ); +```diff +/*global define,WebSocket*/ +define( + [], + function () { + "use strict"; + + function ExampleTelemetryServerAdapter($q, wsUrl) { + var ws = new WebSocket(wsUrl), ++ histories = {}, + dictionary = $q.defer(); + + // Handle an incoming message from the server + ws.onmessage = function (event) { + var message = JSON.parse(event.data); + + switch (message.type) { + case "dictionary": + dictionary.resolve(message.value); + break; ++ case "history": ++ histories[message.id].resolve(message); ++ delete histories[message.id]; ++ break; + } + }; + + // Request dictionary once connection is established + ws.onopen = function () { + ws.send("dictionary"); + }; + + return { + dictionary: function () { + return dictionary.promise; + }, ++ history: function (id) { ++ histories[id] = histories[id] || $q.defer(); ++ ws.send("history " + id); ++ return histories[id].promise; ++ } + }; + } + + return ExampleTelemetryServerAdapter; + } +); +``` __tutorials/telemetry/src/ExampleTelemetryServerAdapter.js__ When the `history` method is called, a new request is issued to the server for @@ -2668,53 +2727,54 @@ identifier, the pending promise is resolved. This `history` method will be used by a `telemetryService` provider which we will implement: +```diff +/*global define*/ - /*global define*/ - - define( - ['./ExampleTelemetrySeries'], - function (ExampleTelemetrySeries) { - "use strict"; - - var SOURCE = "example.source"; - - function ExampleTelemetryProvider(adapter, $q) { - // Used to filter out requests for telemetry - // from some other source - function matchesSource(request) { - return (request.source === SOURCE); - } - - return { - requestTelemetry: function (requests) { - var packaged = {}, - relevantReqs = requests.filter(matchesSource); - - // Package historical telemetry that has been received - function addToPackage(history) { - packaged[SOURCE][history.id] = - new ExampleTelemetrySeries(history.value); - } - - // Retrieve telemetry for a specific measurement - function handleRequest(request) { - var key = request.key; - return adapter.history(key).then(addToPackage); - } - - packaged[SOURCE] = {}; - return $q.all(relevantReqs.map(handleRequest)) - .then(function () { return packaged; }); - }, - subscribe: function (callback, requests) { - return function () {}; - } - }; +define( + ['./ExampleTelemetrySeries'], + function (ExampleTelemetrySeries) { + "use strict"; + + var SOURCE = "example.source"; + + function ExampleTelemetryProvider(adapter, $q) { + // Used to filter out requests for telemetry + // from some other source + function matchesSource(request) { + return (request.source === SOURCE); } - - return ExampleTelemetryProvider; + + return { + requestTelemetry: function (requests) { + var packaged = {}, + relevantReqs = requests.filter(matchesSource); + + // Package historical telemetry that has been received + function addToPackage(history) { + packaged[SOURCE][history.id] = + new ExampleTelemetrySeries(history.value); + } + + // Retrieve telemetry for a specific measurement + function handleRequest(request) { + var key = request.key; + return adapter.history(key).then(addToPackage); + } + + packaged[SOURCE] = {}; + return $q.all(relevantReqs.map(handleRequest)) + .then(function () { return packaged; }); + }, + subscribe: function (callback, requests) { + return function () {}; + } + }; } - ); + + return ExampleTelemetryProvider; + } +); +``` __tutorials/telemetry/src/ExampleTelemetryProvider.js__ The `requestTelemetry` method of a `telemetryService` is expected to take an @@ -2737,9 +2797,9 @@ 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 domain object, it does so by merging together three JavaScript objects: -* First, the `telemetry` property from that domain object’s type definition. -* Second, the `telemetry` property from that domain object’s model. -* Finally, the `request` object that was passed in via that domain object’s +* First, the `telemetry` property from that domain object's type definition. +* Second, the `telemetry` property from that domain object's model. +* Finally, the `request` object that was passed in via that domain object's `telemetry` capability. As such, the `source` and `key` properties we observe here will come from the @@ -2752,115 +2812,117 @@ Finally, note that we also have a `subscribe` method, to satisfy the interface o `telemetryService`, but this `subscribe` method currently does nothing. This script uses an `ExampleTelemetrySeries` class, which looks like: +```diff +/*global define*/ - /*global define*/ - - define( - function () { - "use strict"; - - function ExampleTelemetrySeries(data) { - return { - getPointCount: function () { - return data.length; - }, - getDomainValue: function (index) { - return (data[index] || {}).timestamp; - }, - getRangeValue: function (index) { - return (data[index] || {}).value; - } - }; - } - - return ExampleTelemetrySeries; +define( + function () { + "use strict"; + + function ExampleTelemetrySeries(data) { + return { + getPointCount: function () { + return data.length; + }, + getDomainValue: function (index) { + return (data[index] || {}).timestamp; + }, + getRangeValue: function (index) { + return (data[index] || {}).value; + } + }; } - ); + + return ExampleTelemetrySeries; + } +); +``` __tutorials/telemetry/src/ExampleTelemetrySeries.js__ This takes the array of telemetry values (as returned by the server) and wraps it with the interface expected by the platform (the methods shown.) Finally, we expose this `telemetryService` provider declaratively: - - { - "name": "Example Telemetry Adapter", - "extensions": { - "types": [ - { - "name": "Spacecraft", - "key": "example.spacecraft", - "glyph": "o" - }, - { - "name": "Subsystem", - "key": "example.subsystem", - "glyph": "o", - "model": { "composition": [] } - }, - { - "name": "Measurement", - "key": "example.measurement", - "glyph": "T", - "model": { "telemetry": {} }, - "telemetry": { - "source": "example.source", - "domains": [ - { - "name": "Time", - "key": "timestamp" - } - ] - } +```diff +{ + "name": "Example Telemetry Adapter", + "extensions": { + "types": [ + { + "name": "Spacecraft", + "key": "example.spacecraft", + "glyph": "o" + }, + { + "name": "Subsystem", + "key": "example.subsystem", + "glyph": "o", + "model": { "composition": [] } + }, + { + "name": "Measurement", + "key": "example.measurement", + "glyph": "T", + "model": { "telemetry": {} }, + "telemetry": { + "source": "example.source", + "domains": [ + { + "name": "Time", + "key": "timestamp" + } + ] } - ], - "roots": [ - { - "id": "example:sc", - "priority": "preferred", - "model": { - "type": "example.spacecraft", - "name": "My Spacecraft", - "composition": [] - } + } + ], + "roots": [ + { + "id": "example:sc", + "priority": "preferred", + "model": { + "type": "example.spacecraft", + "name": "My Spacecraft", + "composition": [] } - ], - "services": [ - { - "key": "example.adapter", - "implementation": "ExampleTelemetryServerAdapter.js", - "depends": [ "$q", "EXAMPLE_WS_URL" ] - } - ], - "constants": [ - { - "key": "EXAMPLE_WS_URL", - "priority": "fallback", - "value": "ws://localhost:8081" - } - ], - "runs": [ - { - "implementation": "ExampleTelemetryInitializer.js", - "depends": [ "example.adapter", "objectService" ] - } - ], - "components": [ - { - "provides": "modelService", - "type": "provider", - "implementation": "ExampleTelemetryModelProvider.js", - "depends": [ "example.adapter", "$q" ] - }, - + { - + "provides": "telemetryService", - + "type": "provider", - + "implementation": "ExampleTelemetryProvider.js", - + "depends": [ "example.adapter", "$q" ] - + } - ] - } + } + ], + "services": [ + { + "key": "example.adapter", + "implementation": "ExampleTelemetryServerAdapter.js", + "depends": [ "$q", "EXAMPLE_WS_URL" ] + } + ], + "constants": [ + { + "key": "EXAMPLE_WS_URL", + "priority": "fallback", + "value": "ws://localhost:8081" + } + ], + "runs": [ + { + "implementation": "ExampleTelemetryInitializer.js", + "depends": [ "example.adapter", "objectService" ] + } + ], + "components": [ + { + "provides": "modelService", + "type": "provider", + "implementation": "ExampleTelemetryModelProvider.js", + "depends": [ "example.adapter", "$q" ] + }, ++ { ++ "provides": "telemetryService", ++ "type": "provider", ++ "implementation": "ExampleTelemetryProvider.js", ++ "depends": [ "example.adapter", "$q" ] ++ } + ] } +} +``` __tutorials/telemetry/bundle.json__ Now, if we navigate to one of our numeric measurements, we should see a plot of @@ -2868,78 +2930,80 @@ its historical telemetry: ![Telemetry](images/telemetry-3.png) -We can now visualize our data, but it doesn’t update over time - we know the +We can now visualize our data, but it doesn't update over time - we know the server is continually producing new data, but we have to click away and come back to see it. We can fix this by adding support for telemetry subscriptions. ### 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 this from our server adapter: - /*global define,WebSocket*/ - - define( - [], - function () { - "use strict"; - - function ExampleTelemetryServerAdapter($q, wsUrl) { - var ws = new WebSocket(wsUrl), - histories = {}, - + listeners = [], - dictionary = $q.defer(); - - // Handle an incoming message from the server - ws.onmessage = function (event) { - var message = JSON.parse(event.data); - - switch (message.type) { - case "dictionary": - dictionary.resolve(message.value); - break; - case "history": - histories[message.id].resolve(message); - delete histories[message.id]; - break; - + case "data": - + listeners.forEach(function (listener) { - + listener(message); - + }); - + break; - } - }; - - // Request dictionary once connection is established - ws.onopen = function () { - ws.send("dictionary"); - }; - - return { - dictionary: function () { - return dictionary.promise; - }, - history: function (id) { - histories[id] = histories[id] || $q.defer(); - ws.send("history " + id); - return histories[id].promise; - }, - + subscribe: function (id) { - + ws.send("subscribe " + id); - + }, - + unsubscribe: function (id) { - + ws.send("unsubscribe " + id); - + }, - + listen: function (callback) { - + listeners.push(callback); - + } - }; - } - - return ExampleTelemetryServerAdapter; +```diff +/*global define,WebSocket*/ + +define( + [], + function () { + "use strict"; + + function ExampleTelemetryServerAdapter($q, wsUrl) { + var ws = new WebSocket(wsUrl), + histories = {}, ++ listeners = [], + dictionary = $q.defer(); + + // Handle an incoming message from the server + ws.onmessage = function (event) { + var message = JSON.parse(event.data); + + switch (message.type) { + case "dictionary": + dictionary.resolve(message.value); + break; + case "history": + histories[message.id].resolve(message); + delete histories[message.id]; + break; ++ case "data": ++ listeners.forEach(function (listener) { ++ listener(message); ++ }); ++ break; + } + }; + + // Request dictionary once connection is established + ws.onopen = function () { + ws.send("dictionary"); + }; + + return { + dictionary: function () { + return dictionary.promise; + }, + history: function (id) { + histories[id] = histories[id] || $q.defer(); + ws.send("history " + id); + return histories[id].promise; + }, ++ subscribe: function (id) { ++ ws.send("subscribe " + id); ++ }, ++ unsubscribe: function (id) { ++ ws.send("unsubscribe " + id); ++ }, ++ listen: function (callback) { ++ listeners.push(callback); ++ } + }; } - ); + + return ExampleTelemetryServerAdapter; + } +); +``` __tutorials/telemetry/src/ExampleTelemetryServerAdapter.js__ Here, we have added `subscribe` and `unsubscribe` methods which issue the @@ -2949,88 +3013,91 @@ with these subscriptions. We then need only to utilize these methods from our `telemetryService`: - /*global define*/ - - define( - ['./ExampleTelemetrySeries'], - function (ExampleTelemetrySeries) { - "use strict"; - - var SOURCE = "example.source"; - - function ExampleTelemetryProvider(adapter, $q) { - + var subscribers = {}; - - // Used to filter out requests for telemetry - // from some other source - function matchesSource(request) { - return (request.source === SOURCE); - } - - + // Listen for data, notify subscribers - + adapter.listen(function (message) { - + var packaged = {}; - + packaged[SOURCE] = {}; - + packaged[SOURCE][message.id] = - + new ExampleTelemetrySeries([message.value]); - + (subscribers[message.id] || []).forEach(function (cb) { - + cb(packaged); - + }); - + }); - - return { - requestTelemetry: function (requests) { - var packaged = {}, - relevantReqs = requests.filter(matchesSource); - - // Package historical telemetry that has been received - function addToPackage(history) { - packaged[SOURCE][history.id] = - new ExampleTelemetrySeries(history.value); - } - - // Retrieve telemetry for a specific measurement - function handleRequest(request) { - var key = request.key; - return adapter.history(key).then(addToPackage); - } - - packaged[SOURCE] = {}; - return $q.all(relevantReqs.map(handleRequest)) - .then(function () { return packaged; }); - }, - subscribe: function (callback, requests) { - + var keys = requests.filter(matchesSource) - + .map(function (req) { return req.key; }); - + - + function notCallback(cb) { - + return cb !== callback; - + } - + - + function unsubscribe(key) { - + subscribers[key] = - + (subscribers[key] || []).filter(notCallback); - + if (subscribers[key].length < 1) { - + adapter.unsubscribe(key); - + } - + } - + - + keys.forEach(function (key) { - + subscribers[key] = subscribers[key] || []; - + adapter.subscribe(key); - + subscribers[key].push(callback); - + }); - + - + return function () { - + keys.forEach(unsubscribe); - + }; - } - }; +```diff +/*global define*/ + +define( + ['./ExampleTelemetrySeries'], + function (ExampleTelemetrySeries) { + "use strict"; + + var SOURCE = "example.source"; + + function ExampleTelemetryProvider(adapter, $q) { ++ var subscribers = {}; + + // Used to filter out requests for telemetry + // from some other source + function matchesSource(request) { + return (request.source === SOURCE); } - - return ExampleTelemetryProvider; + ++ // Listen for data, notify subscribers ++ adapter.listen(function (message) { ++ var packaged = {}; ++ packaged[SOURCE] = {}; ++ packaged[SOURCE][message.id] = ++ new ExampleTelemetrySeries([message.value]); ++ (subscribers[message.id] || []).forEach(function (cb) { ++ cb(packaged); ++ }); ++ }); + + return { + requestTelemetry: function (requests) { + var packaged = {}, + relevantReqs = requests.filter(matchesSource); + + // Package historical telemetry that has been received + function addToPackage(history) { + packaged[SOURCE][history.id] = + new ExampleTelemetrySeries(history.value); + } + + // Retrieve telemetry for a specific measurement + function handleRequest(request) { + var key = request.key; + return adapter.history(key).then(addToPackage); + } + + packaged[SOURCE] = {}; + return $q.all(relevantReqs.map(handleRequest)) + .then(function () { return packaged; }); + }, + subscribe: function (callback, requests) { ++ var keys = requests.filter(matchesSource) ++ .map(function (req) { return req.key; }); ++ ++ function notCallback(cb) { ++ return cb !== callback; ++ } ++ ++ function unsubscribe(key) { ++ subscribers[key] = ++ (subscribers[key] || []).filter(notCallback); ++ if (subscribers[key].length < 1) { ++ adapter.unsubscribe(key); ++ } ++ } ++ ++ keys.forEach(function (key) { ++ subscribers[key] = subscribers[key] || []; ++ adapter.subscribe(key); ++ subscribers[key].push(callback); ++ }); ++ ++ return function () { ++ keys.forEach(unsubscribe); ++ }; + } + }; } - ); + + return ExampleTelemetryProvider; + } +); +``` + __tutorials/telemetry/src/ExampleTelemetryProvider.js__ A quick summary of these changes: @@ -3046,7 +3113,7 @@ providing single-element series objects.) subscribers. This method is expected to return a function which terminates the subscription when called, so we do some work to remove subscribers in this situations. When our subscriber count for a given measurement drops to zero, -we issue an unsubscribe request. (We don’t take any care to avoid issuing +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 server can handle this.)