From 73e959f95a8257ca6eb4569f4b5b3311543d5269 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Thu, 24 Sep 2015 09:44:24 -0700 Subject: [PATCH 01/24] Incremental commit of developer's guide --- docs/src/guide/index.md | 2288 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 2286 insertions(+), 2 deletions(-) diff --git a/docs/src/guide/index.md b/docs/src/guide/index.md index c575439d48..5c30ddb8e9 100644 --- a/docs/src/guide/index.md +++ b/docs/src/guide/index.md @@ -1,3 +1,2287 @@ -# Developer Guide +# Open MCT Web Developer Guide +Victor Woeltjen + +[victor.woeltjen@nasa.gov](mailto:victor.woeltjen@nasa.gov) + +September 23, 2015 +Document Version 1.1 + +Date | Version | Summary of Changes | Author +------------------- | --------- | ----------------------- | --------------- +April 29, 2015 | 0 | Initial Draft | Victor Woeltjen +May 12, 2015 | 0.1 | | Victor Woeltjen +June 4, 2015 | 1.0 | Name Changes | Victor Woeltjen +September 23, 2015 | 1.1 | Conversion to MarkDown | Andrew Henry + +# Contents +1. [Introduction](#Introduction) + 1. [What is Open MCT Web?](#What-is-Open-MCT-Web-) + 2. [Client-Server Relationship](#Client-Server-Relationship) + +# Introduction +The purpose of this guide is to familiarize software developers with the Open +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](h​ttp://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 +vehicles, so some of its features are specific to that task; however, it is +flexible enough to be adapted to a variety of other application domains where a +display tool oriented toward browsing, composing, and visualizing would be +useful. + +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. +* A plugin framework and an extensible API for introducing new application +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. + +## Client-Server Relationship +Open MCT Web is client software - it runs entirely in the user’s web browser. As +such, it is largely “server agnostic”; any web server capable of serving files +from paths is capable of providing Open MCT Web. + +While Open MCT Web can be configured to run as a standalone client, this is +rarely very useful. Instead, it is intended to be used as a display and +interaction layer for information obtained from a variety of back­end services. +Doing so requires authoring or utilizing adapter plugins which allow Open MCT +Web to interact with these services. + +Typically, the pattern here is to provide a known interface that Open MCT Web +can utilize, and implement it such that it interacts with whatever back­end +provides the relevant information. Examples of back­ends that can be utilized in +this fashion include databases for the persistence of user­created objects, or +sources of telemetry data. + +See the [Architecture Guide](../architecture/index.md#Overview) for more details +on the client-server relationship. + +## Developing with Open MCT Web +Building applications with Open MCT Web typically means authoring and utilizing +a set of plugins which provide application­specific details about how Open MCT +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. +Open MCT Web is built using [AngularJS](h​ttp://www.angularjs.org) ​from Google. A +good understanding of Angular is recommended for developers working with Open +MCT Web. + +### Forking +Open MCT Web does not currently have a single stand­alone artifact that can be +used as a library. Instead, the recommended approach for creating a new +application is to start by forking/branching Open MCT Web, and then adding new +features from there. Put another way, Open MCT Web’s source structure is built +to serve as a template for specific applications. + +Forking in this manner should not require that you edit Open MCT Web’s sources. +The preferred approach is to create a new directory (peer to ​`index.html`)​for +the new application, then add new bundles (as described in the Framework +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: + + cd + git checkout open­master + git checkout ­b + +As a convention used internally, applications built using Open MCT Web have +master branch names with an identifying prefix. For instance, if building an +application called “Foo”, the last statement above would look like: + + git checkout ­b foo­master + +This convention is not enforced or understood by Open MCT Web in any way; it is +mentioned here as a more general recommendation. + +# Overview + +Open MCT Web is implemented as a framework component which manages a set of +other components. These components, called “bundles”, act as containers to group +sets of related functionality; individual units of functionality are expressed +within these bundles as "extensions." + +Extensions declare dependencies on other extensions (either individually or +categorically), and the framework provides actual extension instances at +run­time to satisfy these declared dependency. This dependency injection +approach allows software components which have been authored separately (e.g. as +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] +(h​ttp://wiki.osgi.org/wiki/Declarative_Services)​. In particular, this is where +the term "bundle" comes from. + +## Framework Overview + +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. + +ADD LINK TO DIAGRAM HERE + +### Tiers +While all bundles in a running Open MCT Web instance are effectively peers, it +is useful to think of them as a tiered architecture, where each tier adds more +specificity to the application. + +ADD LINK TO DIAGRAM HERE + +* __Framework__ : This tier is responsible for wiring together the set of +configured components (called bundles) together to instantiate the running +application. It is responsible for mediating between AngularJS (in particular, +its dependency injection mechanism) and RequireJS (to load scripts at run­time.) +It additionally interprets bundle definitions (see explanation below, as well as +further detail in the Framework chapter.) At this tier, we are at our most +general: We know only that we are a plugin­based application.
 +* __Platform__: Components in the Platform tier describe both the general user +interface and corresponding developer­facing interfaces of Open MCT Web. This +tier provides the general infrastructure for applications. It is less general +than the framework tier, insofar as this tier introduces a specific user +interface paradigm, but it is still non-specific as to what useful features +will be provided. Although they can be removed or replaced easily, bundles +provided by the Platform tier generally should not be thought of as optional.
 +* __Application__: The application tier consists of components which utilize the +infrastructure provided by the Platform to provide functionality which will (or +could) be useful to specific applications built using Open MCT Web. These +include adapters to specific persistence back­ends (such as ElasticSearch or +CouchDB) as well as bundles which describe more user­facing features (such as +Plot views for visualizing time series data, or Layout objects for +display­building.) Bundles from this tier can be added or removed without +compromising basic application functionality, with the caveat that at least one +persistence adapter needs to be present. +* __Plugins__: Conceptually, this tier is not so different from the application +tier; it consists of bundles describing new features, back­end adapters, that +are specific to the application being built on Open MCT Web. It is described as +a separate tier here because it has one important distinction from the +application tier: It consists of bundles that are not included with the platform +(either authored anew for the specific application, or obtained from elsewhere.) + +Note that bundles in any tier can go off and consult back­end services. In +practice, this responsibility is handled at the Application and/or Plugin tiers; +Open MCT Web is built to be server­agnostic, so any back­end is considered an +application­specific detail. + +## Platform Overview + +The "tiered" architecture described in the preceding text describes a way of +thinking of and categorizing software components of a Open MCT Web application, +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. + +### Logical Architecture +INSERT DIAGRAM HERE + +* __Templates__​: HTML templates written in Angular’s template syntax; see  +the [Angular documentation on templates](https://docs.angularjs.org/guide/templates)​.  +These describe the page as actually seen by the user. Conceptually, stylesheets  +(controlling the look­and­feel of the rendered templates) belong in this  +grouping as well.  +* __Presentation__: ​Responsible for providing information to be displayed in  +templates, and managing interactions with the information model. Provides the  +logic and behavior of the user interface itself.  +* __Information model__: ​Provides a common (within Open MCT Web) set of interfaces  +for dealing with “things” ­ domain objects ­ within the system. User­facing  +concerns in a Open MCT Web application are expressed as domain objects; examples  +include folders (used to organize other domain objects), layouts (used to build  +displays), or telemetry points (used as handles for streams of remote  +measurements.) These domain objects expose a common set of interfaces to allow  +reusable user interfaces to be built in the presentation and template tiers; the  +specifics of these behaviors are then mapped to interactions with underlying  +services.  +* __Services__: ​A set of interfaces for dealing with back­end services. +* __Back­end__​: External to the Open MCT Web client; the underlying persistence  +stores, telemetry streams, and so forth which the Open MCT Web client is being  +used to interact with. + +### 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  +`XMLHttpRequest`​, typically wrapped by Angular’s '`$http​`.) + +INSERT DIAGRAM HERE + +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.  +The application is composed of bundles. Plug­ins are bundles. +* __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  +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  +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.  +* __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.  + +# 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 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 MCT  +Web.) +* __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  +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.  + +UP TO HERE + +## 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.  + +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 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 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.  +  +   +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.  + 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.  +  + + + 14  +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 at  + 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:  +  +{  +    "name": "Example Policy",  +    "description": "Provides an example of using policies.",  +    "sources": "src",  +    "extensions": {  +        "policies": [  +            {  +                "implementation": "ExamplePolicy.js",  +                "category": "action"  +            }  +        ]  +    }  +}  +  + + + 15  +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 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:  +   +  +  + + + 16  +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.  +  +General Extensions +  + 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.  + 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 +  + The properties used in extension definitions are typically unique to each category of  +extension; a few properties have standard interpretations by the platform.  +  + ● 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.  +  + 17  + 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.  + +Partial Construction +  + 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.  + 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.  + 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 +  + 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  + 18  +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​.  +  +Directives +  + New directives (see ​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.  +  +Controllers +  + New controllers (see ​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.  +   +  +Services +  + New services (see ​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.  +  +  + +Constants +  + Constant values may be registered as extensions of the ​constants​ category; see  +https://docs.angularjs.org/api/ng/type/angular.Module#constant​. These extensions have no  + 19  +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.  +  +  +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; see  +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.)  +  +  +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:  +  + ● 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.)  +  +  + + + 20  +Composite Services +  + A special category of extensions recognized by the framework are ​components​; these  +are parts of services intended to be fit together in a common pattern.  +  +   +  + Components all implement the same interface, which is the interface expected for  +services of the type that they create. Components fall into three types:  +  + ● provider​: Provides an actual implementation of the service in question.  + ● aggregator​: Makes many implementations of the service in question appear as one.  + ● decorator​: Modifies the inputs or outputs of another implementation of the service.  +  + When the framework layer encounters components, it assembles them into single  +service instances that can be referred to elsewhere as single dependencies. All providers are  +instantiated, and passed to the first available aggregator; decorators are then layered on in  +priority order to create the final form of the service.  + A component should include the following properties in its extension definition:  +  + ● 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)  +  + 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.  + + 21  +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.  + The core bundle (​platform/core​) introduces a few additional object types meant to  +be passed along by other services.  +  +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.  +  + 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 by this domain object, this  + will return ​undefined​.  + 22  + ● 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 exposed.  +  +Actions +  + 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 exposed it to be  + removed from its container.  + ● 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.  + ○ 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 Contexts +  + 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 dragged object  + in a drag­and­drop operation.)  + + + + 23  +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 Requests +  + 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.  +  + Additional properties may be included in telemetry requests which have specific  +interpretations for specific sources.  +  +Telemetry Responses +  + When returned from the ​telemetryService​ (see 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.)  +  + + + 24  +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 range, and may have more than one.  + 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.  +  +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:  +  + ● 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.  +  +  +Types +  + A domain object’s type is represented as a ​Type​ object, which has the following  +interface:  + 25  +  + ● 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 +  + 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 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.)  +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 in subsequent sections.  + 26  +  +Actions +  + 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 Contexts, under  + Core API.)  + ● 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 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 inherently inapplicable.  +  + An action’s bundle definition (and/or ​getMetadata()​ return value) may include:  + ● category​: A string or dearray 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.  +  +  + + + 27  +Capabilities +  + Capabilities are exposed by domain objects (e.g. via the g​ etCapability​ 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 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 support for ​invoke​ 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 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.  +  +Controls +  + Controls provide options for the ​mct­control​ directive.  +  + Four standard control types are included in the 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.  +  + 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 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.  + 28  +  +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.  + ● 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.  +  +Gestures +  + 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 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 of unremoved listeners.  +  +Indicators +  + An indicator is an element that should appear in the status area at the bottom of a  +running Open MCT Web client instance.  +  + + + 29  +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.  +  + 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 +  + 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 +  + 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:  +  + ● name​: Human­readable name of the licensed component. (e.g. “AngularJS”.)  + ● version​: Human­readable version of the licensed component. (e.g. “1.2.26”.)  + ● 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.  +  + 30  +  +Policies +  + 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.  +  + 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.  +  +Representations +  + 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.  + 31  + ● 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.)  +  + 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 definition.  +  +Representers +  + 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 represent​ations​, which describe specific ways to represent domain objects,  +represent​ers ​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  + 32  +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.  +  +Roots +  + 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 domain object being exposed.  + ● model​: The model, as a JSON object, for the domain object being exposed.  +  +Stylesheets +  + 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​)  +  + To control the order of CSS files, use ​priority​ (see the section on Extension  +Definitions above.)  +   +  + + + 33  +Templates +  + 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 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.  +  + 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.  +  +Types +  + 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 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 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.  + ● properties​: An array describing individual properties of this domain object (as should  + appear in the Create or the Edit Properties dialog.) Each property is described by an  + object containing the following properties:  + 34  + ○ control​: The key of the control (see mct­control and the controls extension  + 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.  +  +Versions +  + 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 the list of  + version information in the About dialog.  + ● 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 above.)  +   + This extension category does not have implementations.  +  +Views +  + 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 font  + set.  + ● type​: Optional; if present, this representation is only applicable for domain object’s of  + this type.  + 35  + ● 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 the ​delegation​ capability, in the Capabilities section.)  + ● 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 +  + 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  + configuration​ as ready to persist.  + ● selection​: An object representing the current 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.  + 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.)  +  +   +    + 36  + 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 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.  +  + + + 37  +Directives +  + Open MCT Web defines several Angular directives that are intended for use both  +internally within the platform, and by plugins.  +  +Before Unload +  + The ​mct­before­unload​ directive is used to listen for (and prompt for user  +confirmation) of navigation changes in the browser. This includes reloading, following links out  +of Open MCT 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.  +  +Chart +  + The ​mct­chart​ directive is used to support drawing of simple charts. It is present to  +support the Plot view, and its functionality is limited to the functionality that is relevant for that  +view.  + This directive is used at the element level and takes one attribute, ​draw​, which is an  +Angular expression which will should evaluate to a drawing object. This drawing object should  +contain the following properties:  + ● dimensions​: The size, in logical coordinates, of the chart area. A two­element  + array or numbers.  + ● origin​: The position, in logical coordinates, of the lower­left corner of the chart  + area. A two­element array or numbers.  + ● lines​: An array of lines (e.g. as a plot line) to draw, where each line is  + expressed as an object containing:  + ○ buffer​: A Float32Array containing points in the line, in logical  + coordinates, in sequential x,y pairs.  + ○ color​: The color of the line, as a four­element RGBA array, where each  + element is a number in the range of 0.0­1.0.  + ○ points​: The number of points in the line.  + ● boxes​: An array of rectangles to draw in the chart area. Each is an object  + containing:  + ○ start​: The first corner of the rectangle, as a two­element array of  + numbers, in logical coordinates.  + 38  + ○ end​: The opposite corner of the rectangle, as a two­element array of  + numbers, in logical coordinates.  + ○ color​: The color of the line, as a four­element RGBA array, where each  + element is a number in the range of 0.0­1.0.  +  + While ​mct­chart​ is intended to support plots specifically, it does perform some useful  +management of canvas objects (e.g. choosing between WebGL and Canvas 2D APIs for  +drawing based on browser support) so its usage is recommended when its supported drawing  +primitives are sufficient for other charting tasks.  +  +Container +  + 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,  +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.  + 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 Angular  +expressions:  +  + ● 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.  +  +Drag +  + 39  + 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:  +  + ● 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.  +   +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:  +  + ● 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.  +  + + + 40  +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​.  + A form’s structure is represented by a JavaScript object in the following form:  +{  +    "name": ... title to display for the form, as a string ...,  +    "sections": [  +        {  +            "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 ...  +    ]  +}  +  +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.  +  + + + 41  +Form Controls +  + 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.  +  +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 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.  +  + + + 42  +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 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 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 the size is checked.  +  +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 assignable Angular expressions which two­way bind  +to the scroll bar state.  +  + + + 43  +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:  +  + ● 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.  +  + Toolbars support the same ​control​ options as forms.   +  +Toolbar Structure +  + 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) ...  + 44  +                },  +                ... and other rows ...  +            ]  +        },  +        ... and other sections ...  +    ]  +}  +  +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:  +  + ● 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 +  + 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 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 +  + 45  + The ​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.    +  +  +Capability Service +  + The ​capabilityService​ provides constructors for capabilities which will be exposed  +for a given domain object.  + 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.  +  +  +Dialog Service +  + 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.  + 46  +  +Dialog Structure +  + 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 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.  + ○ description​: Description to show in tooltip on hover.  +  +  +Domain Object Service +  + The ​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.  +   +  +Gesture Service +  + 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.  +  +  + 47  +Model Service +  + The ​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.  +   +  +Persistence Service +  + The ​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:  +  + ● 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.  +  +  + + + 48  +Policy Service +  + The ​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 Policies 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 +  + The ​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.  + 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 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 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.  +  + 49  +Type Service +  + The ​typeService​ exposes domain object types. It has the following interface:  +  + ● 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.  +  +View Service +  + The ​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​.  +  +Other Services +  +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​ 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.  +  + + + 50  +Navigation +  + The ​navigationService​ 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 to  + addListener​.  +  +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.  +  +Telemetry Formatter +  + The ​telemetryFormatter​ is a utility for formatting domain and range values read  +from a telemetry series.  + The ​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  + display; returns a string.  +  + + + 51  +Telemetry Handler +  + The ​telemetryHandler​ 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:  +  + ● 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 +  + 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 a  + previous ​request(...)​ call) available for this domain object.  +  + 52  +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.  +  +General Metadata +  + 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 +  + Other properties of domain object models have specific meaning imposed by other  +extensions within the Open MCT Web platform.  +  +Capability-specific Properties +  + 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  + 53  + 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 +  + 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.  +  + + + 54  +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.  +  +Action +   + 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:  + 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 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.  +  + 55  +Composition +  + 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:  +  + ● invoke()​: Returns a ​Promise​ for an array of ​DomainObject​ instances.  + +Delegation +   + 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:  +  + ● 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.   +  +  + + + 56  +Editor +  + 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 +  + 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.  + ● 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 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:  +  + ● 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.  + 57  + ● 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 +  + 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 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 completed.  + ● getSpace()​: Return the string which identifies the persistence space which stores this  + domain object.  +  +Relationship +  + 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. 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 (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 relationship types, and values are  +arrays of domain object identifiers.  +  + 58  +Telemetry +  + 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 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 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 object, and will also be returned by  +getMetadata() ​above.  +  +Type +   + The ​type​ capability exposes information about the domain object’s type. It has the  +same interface as ​Type​; see Core API.  +   +View +  + 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 applicable for  + this domain object.  + 59  +Actions +  + 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 ​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 context.  +  + ● cancel​: Cancel the current editing action (invoked from Edit mode.)  + ● 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 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 way of its ​composition  + capability.)  + ● save​: Save changes (invoked from Edit mode.)  + ● window​: Open this object in a new window.  +  + + + 60  +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 (describing, generally, the context in which the decision is  +occurring.)   + 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 ​category  +parameter of an policy’s extension definition.)  +  + ● 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 contained.  + ● view​: Determines whether or not a view is applicable for a domain object. The  + candidate argument is the view’s extension definition; the context argument is the  + DomainObject​ to be viewed.  +  +  +  + + + 61  +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 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/​).  +   + Invoking ​mvn clean install​ will:  +  + ● Check code style using JSLint. The build will fail if JSLint raises any warnings.  + ● Run the test suite (see below.) The build will fail if any tests fail.  + ● 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.   + 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 ​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 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 does not: This directory must be named  + src​ for the test runner to find it.)  + 62  + ● 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. with  +contents:  + [ "controllers/FooController", "directives/FooDirective" ]  +  + 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*/  +  +define(  +    ["../../src/controllers/FooController"],  +    function (FooController) {  +        "use strict";  +  +        describe("The foo controller", function () {  +            it("does something", function () {  +                var controller = new FooController();  +                expect(controller.foo()).toEqual("foo");  +            });  +        });  +    }  +);  +  +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 bottom of the screen. Currently,  +only statement coverage is displayed.  + + 63  +Deployment +  + 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 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, 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 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 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 disallowed. There are two workarounds if this occurs:  + ● 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 configuration does not create  + a security vulnerability.  +  + 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 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 Open MCT Web as flat files from a different path.  + 64  + ● 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, 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 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 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 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 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 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,  + 65  + 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 default paths  + to reach external services are all correct.  +  +Configuration Constants +  + The following configuration constants are recognized by Open MCT Web bundles:  +  + ● CouchDB adapter, ​platform/persistence/coucb  + ○ 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  + 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 ​/​.  + 66  + + -This is a placeholder for the developer guide. From 2f4cf44229c4d345c3edb77c658de6512b39abf9 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Thu, 24 Sep 2015 13:17:46 -0700 Subject: [PATCH 02/24] Added additional pages to developer's guide MD version --- docs/src/guide/index.md | 1468 ++++++++++++++++++++------------------- 1 file changed, 742 insertions(+), 726 deletions(-) diff --git a/docs/src/guide/index.md b/docs/src/guide/index.md index 5c30ddb8e9..25b18575b8 100644 --- a/docs/src/guide/index.md +++ b/docs/src/guide/index.md @@ -260,24 +260,24 @@ distinguish types of extensions from specific extension instances.  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  +* __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.  +* __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  +* __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 @@ -292,555 +292,571 @@ 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 MCT  -Web.) -* __Extension:__ ​An extension is an individual unit of functionality. Extensions are  -collected together in bundles, and may interact with other extensions.  +* __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.  -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  +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.  +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.  +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.  -UP TO HERE - ## 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 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 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.  -  -   -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.  - 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.  -  +A bundle is a directory which contains: - - 14  -Bundle Definition +* 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  +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  +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.  +   +### Configuring Active Bundles   - 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.  +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.  + +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   - This definition is expressed as a single JSON object with the following properties (all of  -which are optional, falling back to reasonable defaults):  +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.    - ● 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 at  - 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.   +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.   + +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"  + } + ]  + }  + } + +### Bundle Directory Structure   - For example, the bundle definition for ​example/policy​ looks like:  +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  +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.    -{  -    "name": "Example Policy",  -    "description": "Provides an example of using policies.",  -    "sources": "src",  -    "extensions": {  -        "policies": [  -            {  -                "implementation": "ExamplePolicy.js",  -                "category": "action"  -            }  -        ]  -    }  -}  -  - - - 15  -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 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  +For example, the directory structure for bundle ​`platform/commonUI/about` ​looks  like:  -   -  -  - - 16  -Extensions +INSERT DIAGRAM HERE + +## 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.    - 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.  -  -General Extensions -  - Extensions are intended as a general­purpose mechanism for adding new types of  +### General Extensions + +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.  - 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 -  - The properties used in extension definitions are typically unique to each category of  -extension; a few properties have standard interpretations by the platform.  -  - ● 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.  -  - 17  - 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.  -Partial Construction +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.    - 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.  - 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.  - 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.  +### Extension Definitions + +The properties used in extension definitions are typically unique to each  +category of extension; a few properties have standard interpretations by the  +platform.    -Priority +* `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.  + +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.  + +#### Partial Construction + +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.  + +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.  + +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.    - 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:  +#### 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.  +* `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  - 18  -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  +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 +### 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​_.    - 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.    -Directives -  - New directives (see ​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.  -  -Controllers -  - New controllers (see ​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 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.      -Services -  - New services (see ​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 Services -Constants -  - Constant values may be registered as extensions of the ​constants​ category; see  -https://docs.angularjs.org/api/ng/type/angular.Module#constant​. These extensions have no  - 19  -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.  -  -  -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; see  -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.)  -  -  -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:  -  - ● 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.)  -  -  +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.  - - 20  -Composite Services +#### 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  +property ​value​, which is the constant value that will be registered. + +#### 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  +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:    - A special category of extensions recognized by the framework are ​components​; these  -are parts of services intended to be fit together in a common pattern.  -  -   -  - Components all implement the same interface, which is the interface expected for  -services of the type that they create. Components fall into three types:  -  - ● provider​: Provides an actual implementation of the service in question.  - ● aggregator​: Makes many implementations of the service in question appear as one.  - ● decorator​: Modifies the inputs or outputs of another implementation of the service.  -  - When the framework layer encounters components, it assembles them into single  -service instances that can be referred to elsewhere as single dependencies. All providers are  -instantiated, and passed to the first available aggregator; decorators are then layered on in  -priority order to create the final form of the service.  - A component should include the following properties in its extension definition:  -  - ● 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)  -  - 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  +* `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 + +A special category of extensions recognized by the framework are ​`components`​;  +these are parts of services intended to be fit together in a common pattern.  + +INSERT DIAGRAM HERE + +Components all implement the same interface, which is the interface expected for  +services of the type that they create. Components fall into three types: + +* `provider​`: Provides an actual implementation of the service in question.  +* `aggregator`​: Makes many implementations of the service in question appear as  +one.  +* `decorator​`: Modifies the inputs or outputs of another implementation of the  +service.  + +When the framework layer encounters components, it assembles them into single  +service instances that can be referred to elsewhere as single dependencies. All  +providers are instantiated, and passed to the first available aggregator;  +decorators are then layered on in priority order to create the final form of the  +service. + +A component should include the following properties in its extension definition: + +* `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)  + +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.  - - 21  -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.  - The core bundle (​platform/core​) introduces a few additional object types meant to  -be passed along by other services.  -  -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.  -  - 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 by this domain object, this  - will return ​undefined​.  - 22  - ● 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 exposed.  -  -Actions -  - 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 exposed it to be  - removed from its container.  - ● 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.  - ○ 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 Contexts -  - 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 dragged object  - in a drag­and­drop operation.)  +# Core API - - 23  -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 Requests -  - 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.  -  - Additional properties may be included in telemetry requests which have specific  -interpretations for specific sources.  -  -Telemetry Responses -  - When returned from the ​telemetryService​ (see 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.)  -  +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.  - - 24  -Telemetry Series +The core bundle (`​platform/core`​) introduces a few additional object types meant  +to be passed along by other services.  + +## 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.  + +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  +by this domain object, this will return ​undefined​. +* `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  +exposed. + +## Actions + +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  +exposed it to be removed from its container. +* `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.  + * `context`​: The context in which this action is being performed (see below) + +Action instances are typically obtained via a domain object’s `​action​`  +capability.    - 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:  +### Action Contexts + +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  +dragged object in a drag­and­drop operation.) + +## 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.    - ● 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 Requests + +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._  + +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.)  + +### 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  +range, and may have more than one. + +Telemetry series data in Open MCT Web is expressed via the following  +`TelemetrySeries​` interface:    -Telemetry Metadata +* `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.    - 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:  +### 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:    - ● 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.  -  -  -Types -  - A domain object’s type is represented as a ​Type​ object, which has the following  +* `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.  + +## Types +A domain object’s type is represented as a ​Type​ object, which has the following  interface:  - 25  -  - ● 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 -  - 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 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.)  -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 in subsequent sections.  - 26  -  -Actions -  - 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 Contexts, under  - Core API.)  - ● 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 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 inherently inapplicable.  -  - An action’s bundle definition (and/or ​getMetadata()​ return value) may include:  - ● category​: A string or dearray 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.  -  +* `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 +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 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.)  + +#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  +in subsequent sections. +  +## Actions + +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  +Contexts, under Core API.)  +* 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  +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  +inherently inapplicable. + +An action’s bundle definition (and/or `​getMetadata()`​ return value) may include:  +* `category​`: A string or dearray 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.  27  Capabilities @@ -869,41 +885,41 @@ Controls   Four standard control types are included in the 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  + * 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.    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 in rows of the  + * 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  + * templateUrl​: The URL to the control's Angular template, relative to the resources  directory of the bundle which exposes the extension.  28    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  + * 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  + * 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.  + * field​: Name of the field in ​ngModel​ which will hold the value for this control.    Gestures   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 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.  + * 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.)  @@ -928,15 +944,15 @@ 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  + * 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  + * 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,  + * 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  @@ -957,12 +973,12 @@ Licenses information” page, reachable from Open MCT Web’s About dialog.  Licenses may have the following properties, all of which are strings:    - ● name​: Human­readable name of the licensed component. (e.g. “AngularJS”.)  - ● version​: Human­readable version of the licensed component. (e.g. “1.2.26”.)  - ● 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.  + * name​: Human­readable name of the licensed component. (e.g. “AngularJS”.)  + * version​: Human­readable version of the licensed component. (e.g. “1.2.26”.)  + * 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.    30    @@ -974,10 +990,10 @@ whether or not a domain object of one type can contain a domain obj 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  + * 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  + * 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,  @@ -996,17 +1012,17 @@ 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  + * 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  + * 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.  31  - ● gestures​: An array of keys identifying gestures (see the ​gestures​ extension  + * 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  @@ -1023,17 +1039,17 @@ https://docs.angularjs.org/guide/controller​ for more information on cont   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  + * 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  + * 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  + * 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.  + * Any capabilities requested by the ​uses​ property of the representation definition.    Representers   @@ -1062,15 +1078,15 @@ Root­level domain objects appear at the top­level of the tree hierar 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 domain object being exposed.  - ● model​: The model, as a JSON object, for the domain object being exposed.  + * id​: The machine­readable identifier for the domain object being exposed.  + * model​: The model, as a JSON object, for the domain object being exposed.    Stylesheets   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.  + * stylesheetUrl​: Path and filename, including extension, for the stylesheet to include.  This path is relative to the bundle’s resources folder (by default, ​res​)    To control the order of CSS files, use ​priority​ (see the section on Extension  @@ -1088,9 +1104,9 @@ behaves similarly to ​ng­include​, except that it uses these symbo paths.  A template’s extension definition should include the following properties:    - ● key​: The machine­readable name which identifies this template, matched against the  + * 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  + * 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  @@ -1105,19 +1121,19 @@ Types 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 stored to and  + * 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  + * 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 objects of  + * 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.  + * 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  + * properties​: An array describing individual properties of this domain object (as should  appear in the Create or the Edit Properties dialog.) Each property is described by an  object containing the following properties:  34  @@ -1139,9 +1155,9 @@ Versions 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 the list 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 dialog.  + * 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 above.)  @@ -1155,19 +1171,19 @@ available views of domain objects of specific types. A view’s extens 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 font  + * 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 font  set.  - ● type​: Optional; if present, this representation is only applicable for domain object’s of  + * type​: Optional; if present, this representation is only applicable for domain object’s of  this type.  35  - ● needs​: Optional array of strings; if present, this representation is only applicable for  + * 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  + * 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 the ​delegation​ capability, in the Capabilities section.)  - ● toolbar​: Optional; a definition for the toolbar which may appear in a toolbar when  + * 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  @@ -1186,9 +1202,9 @@ 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   @@ -1204,15 +1220,15 @@ within the view. (Future versions of Open MCT Web may support multipl 36  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 currently  + * 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.  + * 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  + * all()​: Get an array of all objects in the selection state. Will include either or both of the  view proxy and selected object.    @@ -1242,18 +1258,18 @@ view.  This directive is used at the element level and takes one attribute, ​draw​, which is an  Angular expression which will should evaluate to a drawing object. This drawing object should  contain the following properties:  - ● dimensions​: The size, in logical coordinates, of the chart area. A two­element  + * 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  + * 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  + * 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  + * 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.  @@ -1289,11 +1305,11 @@ delegate to other ​mct­control​ instances, and also facilitates usa This directive supports the following additional attributes, all specified as Angular  expressions:    - ● 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  + * 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  + * field​: The field in the ​ngModel​ under which to read/store the property associated with  this control.    Drag @@ -1304,9 +1320,9 @@ Note that this is not “drag” in the “drag­and­drop” sense, b down, mouse move, mouse up” sense.  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.  + * 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  @@ -1317,12 +1333,12 @@ 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:    - ● ng­model​: The object which should contain the full form input. Individual fields in this  + * 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.  + * 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.  + * 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.    @@ -1371,12 +1387,12 @@ Form Controls   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  + * 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 @@ -1387,11 +1403,11 @@ will have an isolated scope.  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  + * 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  + * 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​.  + * parameters​: Optional; will be passed into the template’s scope as ​parameters​.  Intended usage is for template­specific display parameters.    @@ -1404,12 +1420,12 @@ 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  + * 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  + * 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​.  + * parameters​: Optional; will be passed into the template’s scope as ​parameters​.  Intended usage is for template­specific display parameters.    Resize @@ -1440,12 +1456,12 @@ Toolbar 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  + * 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.  + * 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.  + * 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.    @@ -1484,18 +1500,18 @@ items​.          },          ... and other sections ...      ]  -}  -  -Services -  - The Open MCT Web platform provides a variety of services which can be retrieved and  +} + +# 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:    - ● Composite Services are defined by a set of ​components​ extensions; plugins may  + * 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  + * 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 @@ -1524,7 +1540,7 @@ Action Service 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  + * getActions(context)​: Returns an array of ​Action​ objects which are applicable in  the specified action context.        @@ -1534,7 +1550,7 @@ Capability Service for a given domain object.  The ​capabilityService​ has the following interface:    - ● getCapabilities(model)​: Returns a an object containing key­value pairs,  + * 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:  @@ -1552,12 +1568,12 @@ Dialog Service 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.  + * 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  + * 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.  @@ -1568,13 +1584,13 @@ Dialog Structure 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  + * 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  + * 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  @@ -1586,7 +1602,7 @@ Domain Object Service   The ​objectService​ provides domain object instances. It has the following interface:    - ● getObjects(ids)​: For the provided array of domain object identifiers, returns a  + * 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.  @@ -1597,7 +1613,7 @@ Gesture Service 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  + * 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.  @@ -1608,7 +1624,7 @@ Model Service   The ​modelService​ provides domain object models. It has the following interface:    - ● getModels(ids)​: For the provided array of domain object identifiers, returns a  + * 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.  @@ -1620,21 +1636,21 @@ Persistence Service (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  + * 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  + * 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  + * 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  + * 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  + * 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  + * 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.    @@ -1647,7 +1663,7 @@ Policy Service The ​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  + * 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 Policies for categories supported by the platform; plugins may define  @@ -1676,9 +1692,9 @@ subscribing to and requesting telemetry data associated with domain obj domain objects. See the Other Services section for more information.  The ​telemetryService​ has the following interface:    - ● requestTelemetry(requests)​: Issue a request for telemetry, matching the  + * 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,  + * 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.  @@ -1688,9 +1704,9 @@ Type Service   The ​typeService​ exposes domain object types. It has the following interface:    - ● listTypes()​: Returns all domain object types supported in the application, as an  + * 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  + * getType(key)​: Returns the ​Type​ instance identified by the provided key, or  undefined​ if no such type exists.    View Service @@ -1698,7 +1714,7 @@ View Service The ​viewService​ exposes definitions for views of domain objects. It has the following  interface:    - ● getViews(domainObject):​ Get an array of extension definitions of category ​views  + * getViews(domainObject):​ Get an array of extension definitions of category ​views  which are valid and applicable to the specified ​domainObject​.    Other Services @@ -1712,11 +1728,11 @@ as by permitting inspection during drag (which is normally prohibited  reasons.)  The ​dndService​ has the following methods:    - ● setData(key, value)​: Set drag data associated with a given type, specified by the  + * 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  + * 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  + * removeData(key)​: Clear drag data associated with a given type, specified by the ​key  argument.    @@ -1730,13 +1746,13 @@ state and notifies listeners; it does not take immediate action when  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  + * 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  + * 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  + * removeListener(callback)​: Stop listening for changes in navigation state. The  provided ​callback​ should be a ​Function​ which has previously been passed to  addListener​.    @@ -1752,9 +1768,9 @@ Telemetry Formatter from a telemetry series.  The ​telemetryFormatter​ has the following methods:    - ● formatDomainValue(value)​: Format the provided domain value (which will be  + * 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  + * formatRangeValue(value)​: Format the provided range value (a number) for  display; returns a string.    @@ -1767,7 +1783,7 @@ domain objects; it is particularly useful for dealing with cases where is delegated to contained objects (as occurs in Telemetry Panels.)  The ​telemetryHandler​ has the following methods:    - ● handle(domainObject, callback, [lossless])​: Subscribe to and issue  + * 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.)  @@ -1776,27 +1792,27 @@ Telemetry Handle   A ​TelemetryHandle​ has the following methods:    - ● getTelemetryObjects()​: Get the domain objects (as a ​DomainObject[]​) that  + * 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  + * 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  + * unsubscribe()​: Unsubscribe to streaming telemetry updates associated with this  handle.  - ● getDomainValue(domainObject)​: Get the most recent domain value received via a  + * 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  + * 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  + * 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.  + * 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  + * getSeries(domainObject)​: Get the latest ​TelemetrySeries​ (as resulted from a  previous ​request(...)​ call) available for this domain object.    52  @@ -1812,7 +1828,7 @@ General Metadata 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.  + * name​: The human­readable name of the domain object.    Extension-specific Properties   @@ -1824,25 +1840,25 @@ Capability-specific Properties 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  + * 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  + * 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  + * 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  + * 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  + * 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  53  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.  + * type​: A string identifying the type of this domain object. Used by the ​type​ capability.    View Configurations   @@ -1888,11 +1904,11 @@ defined.    This capability has the following interface:    - ● getActions(context)​: Get the actions that are applicable in the specified action  + * 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  + * 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  @@ -1908,7 +1924,7 @@ corresponding ​DomainObject​ instances in the same order. The absenc model will result in the absence of this capability in the domain object.  This capability has the following interface:    - ● invoke()​: Returns a ​Promise​ for an array of ​DomainObject​ instances.  + * invoke()​: Returns a ​Promise​ for an array of ​DomainObject​ instances.  Delegation   @@ -1917,10 +1933,10 @@ delegate responsibilities, which would normally handled by other capabil objects in its composition.  This capability has the following interface:    - ● getDelegates(key)​: Returns a ​Promise​ for an array of ​DomainObject​ instances,  + * 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  + * 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  @@ -1946,11 +1962,11 @@ Mutation 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  + * 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​.  + * 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  @@ -1961,13 +1977,13 @@ 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:    - ● A ​Promise​, in which case the resolved value of the promise will be used to determine  + * 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  + * 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.  57  - ● No value (or, equivalently, ​undefined​), in which case the cloned copy (including any  + * 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.    @@ -1976,12 +1992,12 @@ Persistence 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  + * 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  + * 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 stores this  + * getSpace()​: Return the string which identifies the persistence space which stores this  domain object.    Relationship @@ -1989,9 +2005,9 @@ Relationship 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. Returns  + * 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  + * 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.    @@ -2006,17 +2022,17 @@ Telemetry 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,  + * 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  + * 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.  + * 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  @@ -2033,7 +2049,7 @@ View 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 applicable for  + * invoke()​: Returns an array of extension definitions for views which are applicable for  this domain object.  59  Actions @@ -2045,8 +2061,8 @@ Action Categories 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  + * contextual​: Appears in context menus.  + * view­control​: Appears in top­right area of view (as buttons) in Browse mode    Platform Actions   @@ -2054,19 +2070,19 @@ Platform Actions 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 added  + * cancel​: Cancel the current editing action (invoked from Edit mode.)  + * 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 the tree,  + * 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 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  + * 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 way of its ​composition  capability.)  - ● save​: Save changes (invoked from Edit mode.)  - ● window​: Open this object in a new window.  + * save​: Save changes (invoked from Edit mode.)  + * window​: Open this object in a new window.    @@ -2085,12 +2101,12 @@ Policy Categories 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  + * 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  + * 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.    @@ -2112,10 +2128,10 @@ Command-line Build   Invoking ​mvn clean install​ will:    - ● Check code style using JSLint. The build will fail if JSLint raises any warnings.  - ● Run the test suite (see below.) The build will fail if any tests fail.  - ● Populate version info (e.g. commit hash, build time.)  - ● Produce a web archive (​.war​) artifact in the ​target​ directory.  + * Check code style using JSLint. The build will fail if JSLint raises any warnings.  + * Run the test suite (see below.) The build will fail if any tests fail.  + * 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.   @@ -2129,25 +2145,25 @@ test.html​, included at the top level of the source repository, can 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 which scripts  + * Include a directory named ​test​ within that bundle.  + * 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  + * 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.)  62  - ● For each script to be tested, a corresponding test script should be located in the bundle’s  + * 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  + * 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. with  contents:  @@ -2199,9 +2215,9 @@ manner in which they are exposed) that determine how to deploy Open  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 actually  + * 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  + * 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.  @@ -2209,28 +2225,28 @@ of the browser) then access may be disallowed. There are two workarou 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  + * 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  + * 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.  64  - ● If a single server component is being developed to handle all server­side needs of an  + * 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  + * 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  + * 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  @@ -2258,25 +2274,25 @@ 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  + * 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  + * Add a separate configuration bundle which overrides the values of these constants. This  is particularly appropriate when multiple configurations (e.g. development, test,  65  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 default paths  + * 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 configuration constants are recognized by Open MCT Web bundles:    - ● CouchDB adapter, ​platform/persistence/coucb  + * CouchDB adapter, ​platform/persistence/coucb  ○ 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  + * ElasticSearch adapter, ​platform/persistence/elastic  ○ 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  From b7a612127d25cd2018cd6a09c490cd8f0d538e00 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Fri, 25 Sep 2015 09:09:34 -0700 Subject: [PATCH 03/24] Added additional sections, up to Templates --- docs/src/guide/index.md | 491 +++++++++++++++++++++------------------- 1 file changed, 255 insertions(+), 236 deletions(-) diff --git a/docs/src/guide/index.md b/docs/src/guide/index.md index 25b18575b8..7774adffb1 100644 --- a/docs/src/guide/index.md +++ b/docs/src/guide/index.md @@ -835,9 +835,9 @@ initiated by the user.  An action’s implementation: * Should take a single `​context​` argument in its constructor. (See Action  -Contexts, under Core API.)  +Contexts, under Core API.) * Should provide a method ​`perform​`, which causes the behavior associated with  -the action to occur.  +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  metadata from the action’s extension definition. @@ -846,259 +846,278 @@ available as a property of the implementation’s constructor itself),  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:  +An action’s bundle definition (and/or `​getMetadata()`​ return value) may include: +  * `category​`: A string or dearray 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.  + * `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.  - - 27  -Capabilities -  - Capabilities are exposed by domain objects (e.g. via the g​ etCapability​ 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 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 support for ​invoke​ 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 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.  -  -Controls -  - Controls provide options for the ​mct­control​ directive.  -  - Four standard control types are included in the 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.  -  - 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 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.  - 28  -  -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.  - * 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.  -  -Gestures -  - 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 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 of unremoved listeners.  -  -Indicators -  - An indicator is an element that should appear in the status area at the bottom of a  -running Open MCT Web client instance.  -  +* `glyph`​: A single character which will be rendered in Open MCT Web’s custom  +font set as an icon for this action. - - 29  -Standard Indicators +## Capabilities + +Capabilities are exposed by domain objects (e.g. via the `g​etCapability​` 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  +argument to a domain object’s `​getCapability(key)` call.   - Indicators which wish to appear in the common form of an icon­text pair should provide  -implementations with the following methods:  +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 ​invoke​ 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  +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.    - * 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.  +## Controls + +Controls provide options for the ​mct­control​ directive.    - Note that all methods are optional, and are called directly from an Angular template, so  -they should be appropriate to run during digest cycles.  +Six standard control types are included in the 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.  + +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  +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.  + +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.  +* `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.    -Custom Indicators +## Gestures + +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  +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.    - 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.  +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 + +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   -Licenses +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.    - The extension category ​licenses​ can be used to add entries into the “Licensing  +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 + +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 + +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:  -  - * name​: Human­readable name of the licensed component. (e.g. “AngularJS”.)  - * version​: Human­readable version of the licensed component. (e.g. “1.2.26”.)  - * 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.  -  - 30  -  -Policies -  - 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.  -  - 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.  -  -Representations -  - 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.  - 31  - * 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.)  -  - 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 definition.  -  -Representers -  - 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 represent​ations​, which describe specific ways to represent domain objects,  -represent​ers ​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  - 32  -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.  -  -Roots -  - 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 domain object being exposed.  - * model​: The model, as a JSON object, for the domain object being exposed.  -  -Stylesheets -  - 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​)  -  - To control the order of CSS files, use ​priority​ (see the section on Extension  -Definitions above.)  -   -  - - 33  -Templates +Licenses may have the following properties, all of which are strings: + +* `name​`: Human­readable name of the licensed component. (e.g. “AngularJS”.) +* `version`​: Human­readable version of the licensed component. (e.g. “1.2.26”.) +* `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.  + +## Policies + +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.    - The ​templates​ extension category is used to expose Angular templates under  +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.  +  +## Representations + +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.)  + +### 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.)  +  +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  +definition. +  +## Representers + +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 _represent​ations​_, which describe specific ways to represent domain  +objects, represent​ers ​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 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.  + +## Roots + +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.  +  +## Stylesheets + +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​`)  +  +To control the order of CSS files, use ​priority​ (see the section on Extension  +Definitions above.)  + +## Templates + +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.  From b3fb06ba3faa69619c3d84c155be5324c125b348 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Fri, 25 Sep 2015 14:28:52 -0700 Subject: [PATCH 04/24] Up to page 52 --- docs/src/guide/index.md | 1279 +++++++++++++++++++-------------------- 1 file changed, 633 insertions(+), 646 deletions(-) diff --git a/docs/src/guide/index.md b/docs/src/guide/index.md index 7774adffb1..0f83d1dd0b 100644 --- a/docs/src/guide/index.md +++ b/docs/src/guide/index.md @@ -600,25 +600,8 @@ path relative to the bundle’s resource directory (​`res​` by defa ### Composite Services -A special category of extensions recognized by the framework are ​`components`​;  -these are parts of services intended to be fit together in a common pattern.  - -INSERT DIAGRAM HERE - -Components all implement the same interface, which is the interface expected for  -services of the type that they create. Components fall into three types: - -* `provider​`: Provides an actual implementation of the service in question.  -* `aggregator`​: Makes many implementations of the service in question appear as  -one.  -* `decorator​`: Modifies the inputs or outputs of another implementation of the  -service.  - -When the framework layer encounters components, it assembles them into single  -service instances that can be referred to elsewhere as single dependencies. All  -providers are instantiated, and passed to the first available aggregator;  -decorators are then layered on in priority order to create the final form of the  -service. +Composite services are described in the [relevant section](../architecture/Framework.md#Composite-Services) +of the framework guide. A component should include the following properties in its extension definition: @@ -626,7 +609,7 @@ A component should include the following properties in its extension d fully­composed service will be registered with Angular under this name. * `type​`: One of `​provider`​, ​`aggregator​`, or `​decorator​` (as above)  -In addition to any declared dependencies, aggregators and decorators both  +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);  @@ -1074,9 +1057,9 @@ The ​`representers​` extension category is used to add additional b `mct­representation​` directive. This extension category is intended primarily  for use internal to the platform.  -Unlike _represent​ations​_, which describe specific ways to represent domain  +Unlike _represent​ations​_, which describe specific ways to represent domain  objects, represent​ers ​are used to modify or augment the process of representing  -domain objects in general. For example, support for the ​_gestures​_ extension  +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`  @@ -1117,647 +1100,651 @@ Definitions above.)  ## Templates -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 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.  -  - 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.  -  -Types -  - 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 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 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.  - * properties​: An array describing individual properties of this domain object (as should  - appear in the Create or the Edit Properties dialog.) Each property is described by an  - object containing the following properties:  - 34  - ○ control​: The key of the control (see mct­control and the controls extension  - 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.  -  -Versions -  - 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 the list of  - version information in the About dialog.  - * 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 above.)  -   - This extension category does not have implementations.  -  -Views -  - 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 font  - set.  - * type​: Optional; if present, this representation is only applicable for domain object’s of  - this type.  - 35  - * 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 the ​delegation​ capability, in the Capabilities section.)  - * 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 -  - 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  - configuration​ as ready to persist.  - * selection​: An object representing the current 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.  - 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.)  -  -   -    - 36  - 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 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.  -  +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. - - 37  -Directives +A template’s extension definition should include the following properties: +* `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.  + +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.  + +## Types + +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  +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  +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.  +* `properties`​: An array describing individual properties of this domain object +(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) 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.    - Open MCT Web defines several Angular directives that are intended for use both  +## Versions +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  +the list of version information in the About dialog. +* `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 above.)  + +This extension category does not have implementations.  +  +## Views + +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  +font set. +* `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 the ​delegation​ capability, in the Capabilities  +section.) +* `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 + +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  + configuration​ as ready to persist. +* `selection​`: An object representing the current 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.  + +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:  + +* `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.  + +# Directives + +Open MCT Web defines several Angular directives that are intended for use both  internally within the platform, and by plugins.  -  -Before Unload -  - The ​mct­before­unload​ directive is used to listen for (and prompt for user  -confirmation) of navigation changes in the browser. This includes reloading, following links out  -of Open MCT 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.  -  -Chart -  - The ​mct­chart​ directive is used to support drawing of simple charts. It is present to  -support the Plot view, and its functionality is limited to the functionality that is relevant for that  -view.  - This directive is used at the element level and takes one attribute, ​draw​, which is an  -Angular expression which will should evaluate to a drawing object. This drawing object should  -contain the following properties:  - * dimensions​: The size, in logical coordinates, of the chart area. A two­element  - array or numbers.  - * origin​: The position, in logical coordinates, of the lower­left corner of the chart  - area. A two­element array or numbers.  - * lines​: An array of lines (e.g. as a plot line) to draw, where each line is  - expressed as an object containing:  - ○ buffer​: A Float32Array containing points in the line, in logical  - coordinates, in sequential x,y pairs.  - ○ color​: The color of the line, as a four­element RGBA array, where each  - element is a number in the range of 0.0­1.0.  - ○ points​: The number of points in the line.  - * boxes​: An array of rectangles to draw in the chart area. Each is an object  - containing:  - ○ start​: The first corner of the rectangle, as a two­element array of  - numbers, in logical coordinates.  - 38  - ○ end​: The opposite corner of the rectangle, as a two­element array of  - numbers, in logical coordinates.  - ○ color​: The color of the line, as a four­element RGBA array, where each  - element is a number in the range of 0.0­1.0.  -  - While ​mct­chart​ is intended to support plots specifically, it does perform some useful  -management of canvas objects (e.g. choosing between WebGL and Canvas 2D APIs for  -drawing based on browser support) so its usage is recommended when its supported drawing  -primitives are sufficient for other charting tasks.  -  -Container -  - 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,  -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.  - 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 Angular  -expressions:  -  - * 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.  -  -Drag -  - 39  - 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:  -  - * 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.  -   -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:  -  - * 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.  -  - - 40  -Form Structure +## 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  +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.    - 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 ...,  -    "sections": [  -        {  -            "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 ...  -    ]  -}  -  -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.  +## Chart + +The `​mct­chart​` directive is used to support drawing of simple charts. It is  +present to support the Plot view, and its functionality is limited to the  +functionality that is relevant for that view. + +This directive is used at the element level and takes one attribute, `​draw​`,  +which is an Angular expression which will should evaluate to a drawing object.  +This drawing object should contain the following properties: + +* `dimensions​`: The size, in logical coordinates, of the chart area. A  +two­element array or numbers.  +* `origin​`: The position, in logical coordinates, of the lower­left corner of  +the chart area. A two­element array or numbers.  +* `lines​`: An array of lines (e.g. as a plot line) to draw, where each line is  +expressed as an object containing:  + * `buffer`​: A Float32Array containing points in the line, in logical  + coordinates, in sequential x,y pairs.  + * `color​`: The color of the line, as a four­element RGBA array, where  + each element is a number in the range of 0.0­1.0.  + * `points​`: The number of points in the line.  +* `boxes`​: An array of rectangles to draw in the chart area. Each is an object  +containing:  + * `start​`: The first corner of the rectangle, as a two­element array of + numbers, in logical coordinates.  + * `end​`: The opposite corner of the rectangle, as a two­element array of  + numbers, in logical coordinates. color​: The color of the line, as a  + four­element RGBA array, where each element is a number in the range of  + 0.0­1.0.  + +While ​`mct­chart​` is intended to support plots specifically, it does perform  +some useful management of canvas objects (e.g. choosing between WebGL and Canvas  +2D APIs for drawing based on browser support) so its usage is recommended when  +its supported drawing primitives are sufficient for other charting tasks.    +## Container + +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,  +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.  + +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  +Angular expressions: + +* `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.  + +## 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.  + +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.  + +## 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:  + +* `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.  + +### 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​`.  + +A form’s structure is represented by a JavaScript object in the following form: - - 41  -Form Controls -  - 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.  -  -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 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.  -  + {  + "name": ... title to display for the form, as a string ...,  + "sections": [ + {  + "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 ...  + ]  + }  - - 42  -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 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 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 the size is checked.  -  -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 assignable Angular expressions which two­way bind  -to the scroll bar state.  -  +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.  - - 43  -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:  -  - * 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.  -  - Toolbars support the same ​control​ options as forms.   -  -Toolbar Structure -  - 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) ...  - 44  -                },  -                ... and other rows ...  -            ]  -        },  -        ... and other sections ...  -    ]  -} +### Form Controls + +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.  + +##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 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 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  +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  +the size is checked. + +## 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  +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:  + +* `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.  + +Toolbars support the same ​control​ options as forms.   + +### Toolbar Structure + +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 ...  +     ]  + } # 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:  -  - * 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 -  - 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 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 -  - 45  - The ​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.    -  -  -Capability Service -  - The ​capabilityService​ provides constructors for capabilities which will be exposed  -for a given domain object.  - 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.  -  -  -Dialog Service -  - 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.  - 46  -  -Dialog Structure -  - 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 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.  - ○ description​: Description to show in tooltip on hover.  -  -  -Domain Object Service -  - The ​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.  -   -  -Gesture Service -  - 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.  -  -  - 47  -Model Service -  - The ​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.  -   -  -Persistence Service -  - The ​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:  -  - * 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.  -  -  +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:  - - 48  -Policy Service +* _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 + +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 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.    - The ​policyService​ may be used to determine whether or not certain behaviors are  +### Action Service + +The ​`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.    + +### Capability Service + +The ​capabilityService​ provides constructors for capabilities which will be  +exposed for a given domain object.  + +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.  + +### Dialog Service + +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.  + +### Dialog Structure + +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  +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.  + * `description​`: Description to show in tooltip on hover.  + +## Domain Object Service + +The ​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.  + +## Gesture Service + +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.  + +## Model Service + +The ​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.  + +## Persistence Service + +The ​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:  +  +* `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 + +The ​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 Policies 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 -  - The ​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.  - 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 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 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.  -  - 49  -Type Service -  - The ​typeService​ exposes domain object types. It has the following interface:  -  - * 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.  -  -View Service -  - The ​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​.  -  -Other Services -  -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​ 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.  -  +* `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 Policies 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.  - - 50  -Navigation +## Telemetry Service + +The ​`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.  + +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  +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.  + +## Type Service + +The `​typeService​` exposes domain object types. It has the following interface: + +* `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.  + +## View Service + +The `​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​`.  +  +## Other Services + +### 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​` 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.  + +# Navigation   The ​navigationService​ provides information about the current navigation state of  the application; that is, which object is the user currently viewing? This service merely tracks this  From 37dede568c883cd70e24562cbb7d12874116b4ec Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Tue, 29 Sep 2015 15:30:12 -0700 Subject: [PATCH 05/24] Added Capabilities --- docs/src/guide/index.md | 470 ++++++++++++++++++++-------------------- 1 file changed, 237 insertions(+), 233 deletions(-) diff --git a/docs/src/guide/index.md b/docs/src/guide/index.md index 0f83d1dd0b..019afb75a7 100644 --- a/docs/src/guide/index.md +++ b/docs/src/guide/index.md @@ -1744,256 +1744,260 @@ by the `key​` argument.  * `removeData(key)`​: Clear drag data associated with a given type, specified by  the ​`key` argument.  -# Navigation -  - The ​navigationService​ 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 to  - addListener​.  -  -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.  -  -Telemetry Formatter -  - The ​telemetryFormatter​ is a utility for formatting domain and range values read  -from a telemetry series.  - The ​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  - display; returns a string.  +### Navigation   +The ​`navigationService​` 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.  - - 51  -Telemetry Handler -  - The ​telemetryHandler​ 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:  -  - * 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 -  - 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 a  - previous ​request(...)​ call) available for this domain object.  -  - 52  -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.  -  -General Metadata -  - Some properties of domain object models have a ubiquitous meaning through Open  +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  +to addListener​. + +### 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.  + +### Telemetry Formatter + +The `​telemetryFormatter​` is a utility for formatting domain and range values  +read from a telemetry series.  + +The ​`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  +display; returns a string. + +### Telemetry Handler + +The ​telemetryHandler​ 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:  + +* `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 + +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  +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.  + +## General Metadata + +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 -  - Other properties of domain object models have specific meaning imposed by other  + +* `name`​: The human­readable name of the domain object. + +## Extension-specific Properties + +Other properties of domain object models have specific meaning imposed by other  extensions within the Open MCT Web platform.  -  -Capability-specific Properties -  - 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  - 53  - 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 -  - 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.  -  - - 54  -Capabilities +### Capability-specific Properties + +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​  +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.  - 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.  +### 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.    -Action +## 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.  + +# 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.  +  +## Action + +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:  + `domainObject.getCapability("action").perform("navigate"); ` + ...will initiate a navigate action upon the domain object, if an action with  + key "navigate" is defined.    - 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:  - 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 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.  +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.  + +## Composition   - 55  -Composition -  - 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  +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:  -  - * invoke()​: Returns a ​Promise​ for an array of ​DomainObject​ instances.  -Delegation -   - 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:  -  - * 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.   -  +This capability has the following interface:    +* `invoke()`​: Returns a `​Promise​` for an array of `​DomainObject​` instances. - - 56  -Editor +## Delegation + +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:    - 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.  -   +* `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.   + +## Editor + +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 +## Mutation + +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.  +* `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  +modified by other means.   - 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.  - * 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 modified by other means.  -  -Mutator Function -  - The ​mutator​ argument above is a function which will receive a cloned copy of the  +### 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:  -  - * 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.  - 57  - * 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 + +* 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.  + +## Persistence   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:  From d1be2566916bf687bf3b9b3fc2af07a4a72f4f6a Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Thu, 1 Oct 2015 16:50:36 -0700 Subject: [PATCH 06/24] Reverted gendocs changes --- docs/src/architecture/Platform.md | 22 +- docs/src/guide/index.md | 729 +++++++++++++++--------------- docs/src/tutorials/index.md | 0 3 files changed, 383 insertions(+), 368 deletions(-) create mode 100644 docs/src/tutorials/index.md diff --git a/docs/src/architecture/Platform.md b/docs/src/architecture/Platform.md index 80f9e487f5..a59a6ebf9c 100644 --- a/docs/src/architecture/Platform.md +++ b/docs/src/architecture/Platform.md @@ -35,16 +35,26 @@ in __any of these tiers__. * _DOM_: The rendered HTML document, composed from HTML templates which have been processed by AngularJS and will be updated by AngularJS to reflect changes from the presentation layer. User interactions - are initiated from here and invoke behavior in the presentation layer. + are initiated from here and invoke behavior in the presentation layer. HTML  + templates are written in Angular’s template syntax; see the [Angular documentation on templates](https://docs.angularjs.org/guide/templates)​.  + These describe the page as actually seen by the user. Conceptually,  + stylesheets (controlling the look­and­feel of the rendered templates) belong  + in this grouping as well.  * [_Presentation layer_](#presentation-layer): The presentation layer is responsible for updating (and providing information to update) the displayed state of the application. The presentation layer consists primarily of _controllers_ and _directives_. The presentation layer is concerned with inspecting the information model and preparing it for display. -* [_Information model_](#information-model): The information model - describes the state and behavior of the objects with which the user - interacts. +* [_Information model_](#information-model): ​Provides a common (within Open MCT  + Web) set of interfaces for dealing with “things” ­ domain objects ­ within the  + system. User­facing concerns in a Open MCT Web application are expressed as  + domain objects; examples include folders (used to organize other domain  + objects), layouts (used to build displays), or telemetry points (used as  + handles for streams of remote measurements.) These domain objects expose a  + common set of interfaces to allow reusable user interfaces to be built in the  + presentation and template tiers; the specifics of these behaviors are then  + mapped to interactions with underlying services.  * [_Service infrastructure_](#service-infrastructure): The service infrastructure is responsible for providing the underlying general functionality needed to support the information model. This includes @@ -52,7 +62,9 @@ in __any of these tiers__. back-end. * _Back-end_: The back-end is out of the scope of Open MCT Web, except for the interfaces which are utilized by adapters participating in the - service infrastructure. + service infrastructure. Includes the underlying persistence stores, telemetry  + streams, and so forth which the Open MCT Web client is being used to interact  + with. ## Application Start-up diff --git a/docs/src/guide/index.md b/docs/src/guide/index.md index 019afb75a7..7765c8296f 100644 --- a/docs/src/guide/index.md +++ b/docs/src/guide/index.md @@ -13,10 +13,8 @@ May 12, 2015 | 0.1 | | Victor Woeltjen June 4, 2015 | 1.0 | Name Changes | Victor Woeltjen September 23, 2015 | 1.1 | Conversion to MarkDown | Andrew Henry -# Contents -1. [Introduction](#Introduction) - 1. [What is Open MCT Web?](#What-is-Open-MCT-Web-) - 2. [Client-Server Relationship](#Client-Server-Relationship) +# Table of Contents +```generated_toc``` # Introduction The purpose of this guide is to familiarize software developers with the Open @@ -135,14 +133,19 @@ 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. -ADD LINK TO DIAGRAM HERE +The framework is described in more detail in the [Framework Overview](../architecture/Framework.md#Overview) of the +architecture guide. ### Tiers While all bundles in a running Open MCT Web instance are effectively peers, it is useful to think of them as a tiered architecture, where each tier adds more specificity to the application. - -ADD LINK TO DIAGRAM HERE +```nomnoml +#direction: down +[Plugins (Features external to OpenMCTWeb) *Bundle]->[OpenMCTWeb | +[Application (Plots, layouts, ElasticSearch wrapper) *Bundle]->[Platform (Core API, common UI, infrastructure) *Bundle] +[Platform (Core API, common UI, infrastructure) *Bundle]->[Framework (RequireJS, AngularJS, bundle loader)]] +``` * __Framework__ : This tier is responsible for wiring together the set of configured components (called bundles) together to instantiate the running @@ -187,30 +190,8 @@ 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. -### Logical Architecture -INSERT DIAGRAM HERE - -* __Templates__​: HTML templates written in Angular’s template syntax; see  -the [Angular documentation on templates](https://docs.angularjs.org/guide/templates)​.  -These describe the page as actually seen by the user. Conceptually, stylesheets  -(controlling the look­and­feel of the rendered templates) belong in this  -grouping as well.  -* __Presentation__: ​Responsible for providing information to be displayed in  -templates, and managing interactions with the information model. Provides the  -logic and behavior of the user interface itself.  -* __Information model__: ​Provides a common (within Open MCT Web) set of interfaces  -for dealing with “things” ­ domain objects ­ within the system. User­facing  -concerns in a Open MCT Web application are expressed as domain objects; examples  -include folders (used to organize other domain objects), layouts (used to build  -displays), or telemetry points (used as handles for streams of remote  -measurements.) These domain objects expose a common set of interfaces to allow  -reusable user interfaces to be built in the presentation and template tiers; the  -specifics of these behaviors are then mapped to interactions with underlying  -services.  -* __Services__: ​A set of interfaces for dealing with back­end services. -* __Back­end__​: External to the Open MCT Web client; the underlying persistence  -stores, telemetry streams, and so forth which the Open MCT Web client is being  -used to interact with. +An overview of the logical architecture of the platform is given in the [Platform Architecture](../architecture/Platform.md#PlatformArchitecture) +section of the Platform guide ### Web Services @@ -221,7 +202,25 @@ telemetry data or store user­created objects. This interaction is hand individual bundles using APIs which are supported in browser (such as  `XMLHttpRequest`​, typically wrapped by Angular’s '`$http​`.) -INSERT DIAGRAM HERE +```nomnoml +#direction: right +[Web Service #1] <- [Web Browser] +[Web Service #2] <- [Web Browser] +[Web Service #3] <- [Web Browser] +[ Web Browser | + [ Open MCT Web | + [Plugin Bundle #1]-->[Core API] + [Core API]<--[Plugin Bundle #2] + [Platform Bundle #1]-->[Core API] + [Platform Bundle #2]-->[Core API] + [Platform Bundle #3]-->[Core API] + [Core API]<--[Platform Bundle #4] + [Core API]<--[Platform Bundle #5] + [Core API]<--[Plugin Bundle #3] + ] + [Open MCT Web] ->[Browser APIs] +] +``` This architectural approach ensures a loose coupling between applications built  using Open MCT Web and the backends which support them.  @@ -465,7 +464,7 @@ 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.  -  + * `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  @@ -512,7 +511,7 @@ extension's priority may be specified as a ​`priority`​ property in 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.  @@ -526,7 +525,7 @@ overridden.  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  @@ -590,7 +589,7 @@ opposed to a constructor function.) 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  @@ -734,7 +733,7 @@ range, and may have more than one. 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  @@ -749,7 +748,7 @@ Domain objects which have associated telemetry also expose metadata abo 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.  @@ -765,7 +764,8 @@ made using this capability.  ## Types A domain object’s type is represented as a ​Type​ object, which has the following  -interface:  +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.  @@ -828,9 +828,9 @@ metadata from the action’s extension definition. 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 dearray 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:  @@ -900,7 +900,7 @@ modified.  of an individual row definition.  * `field​`: Name of the field in ​`ngModel​` which will hold the value for this  control.  -  + ## Gestures A gesture is a user action which can be taken upon a representation of a domain  @@ -951,7 +951,7 @@ 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.  -  + ### Custom Indicators Indicators which wish to have an arbitrary appearance (instead of following the  @@ -1036,6 +1036,7 @@ which are referenced from templates. See [https://docs.angularjs.org/guide/ 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  @@ -1082,9 +1083,10 @@ models. Root­level domain objects appear at the top­level of the tre 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.  -  + ## Stylesheets The ​stylesheets​ extension category is used to add CSS files to style the  @@ -1106,6 +1108,7 @@ directive, which behaves similarly to `​ng­include​`, except that i 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  against the value given to the key attribute of the mct­include directive. * `templateUrl​`: The path to the relevant Angular template. This path is  @@ -1199,14 +1202,15 @@ that toolbar:  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.)  -  + property, necessarily.) + ### View Scope 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  configuration​ as ready to persist. * `selection​`: An object representing the current selection state.  @@ -1550,7 +1554,7 @@ extension mechanism is insufficient to achieve a desired result.  The ​`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.    @@ -1577,7 +1581,7 @@ model. Keys in this object are the capability keys (as used in a  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.  @@ -1604,7 +1608,7 @@ 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.  - * `description​`: Description to show in tooltip on hover.  + * `description​`: Description to show in tooltip on hover. ## Domain Object Service @@ -1630,7 +1634,7 @@ gestures.  ## Model Service The ​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  @@ -1641,7 +1645,7 @@ result may contain a superset or subset of the models requested.  The ​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:  -  + * `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  @@ -1925,7 +1929,7 @@ the result of the action that was performed, or `undefined` if no ma was found.  ## Composition -  + 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 ​ @@ -1934,7 +1938,7 @@ object’s model describes these contents (by their identifiers), the  model will result in the absence of this capability in the domain object.  This capability has the following interface:  -  + * `invoke()`​: Returns a `​Promise​` for an array of `​DomainObject​` instances. ## Delegation @@ -1944,7 +1948,7 @@ to delegate responsibilities, which would normally handled by other  capabilities, to the domain objects in its composition.  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​.  @@ -1997,317 +2001,316 @@ for this domain object. (including any changes made in place by the mutator function) will be used as  the new domain object model.  -## Persistence -  - 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 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 completed.  - * getSpace()​: Return the string which identifies the persistence space which stores this  - domain object.  -  -Relationship -  - 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. 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 (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 relationship types, and values are  -arrays of domain object identifiers.  -  - 58  -Telemetry -  - 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 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 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 object, and will also be returned by  -getMetadata() ​above.  -  -Type -   - The ​type​ capability exposes information about the domain object’s type. It has the  -same interface as ​Type​; see Core API.  -   -View -  - 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 applicable for  - this domain object.  - 59  -Actions -  - 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 ​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 context.  -  - * cancel​: Cancel the current editing action (invoked from Edit mode.)  - * 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 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 way of its ​composition  - capability.)  - * save​: Save changes (invoked from Edit mode.)  - * window​: Open this object in a new window.  -  +## Persistence - - 60  -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 (describing, generally, the context in which the decision is  -occurring.)   - 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 ​category  -parameter of an policy’s extension definition.)  -  - * 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 contained.  - * view​: Determines whether or not a view is applicable for a domain object. The  - candidate argument is the view’s extension definition; the context argument is the  - DomainObject​ to be viewed.  -  -  -  +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: - - 61  -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 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/​).  -   - Invoking ​mvn clean install​ will:  -  - * Check code style using JSLint. The build will fail if JSLint raises any warnings.  - * Run the test suite (see below.) The build will fail if any tests fail.  - * 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.   - 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 ​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 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 does not: This directory must be named  - src​ for the test runner to find it.)  - 62  - * 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. with  -contents:  - [ "controllers/FooController", "directives/FooDirective" ]  -  - 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*/  -  -define(  -    ["../../src/controllers/FooController"],  -    function (FooController) {  -        "use strict";  -  -        describe("The foo controller", function () {  -            it("does something", function () {  -                var controller = new FooController();  -                expect(controller.foo()).toEqual("foo");  -            });  -        });  -    }  -);  -  -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 bottom of the screen. Currently,  -only statement coverage is displayed.  - - 63  -Deployment -  - 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 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, 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 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 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 disallowed. There are two workarounds if this occurs:  - * 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 configuration does not create  - a security vulnerability.  -  - 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 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 Open MCT Web as flat files from a different path.  - 64  - * 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, 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 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 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 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 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 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,  - 65  - 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 default paths  - to reach external services are all correct.  -  -Configuration Constants -  - The following configuration constants are recognized by Open MCT Web bundles:  -  - * CouchDB adapter, ​platform/persistence/coucb  - ○ 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  - 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 ​/​.  - 66  +* `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 +completed. +* `getSpace()`: Return the string which identifies the persistence space which +stores this domain object. + +## Relationship + +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. +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 +(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 +relationship types, and values are arrays of domain object identifiers. + +##Telemetry + +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 +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 +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 +object, and will also be returned by `getMetadata()` above. + +## Type +The `type` capability exposes information about the domain object’s type. It has +the same interface as `Type`; see Core API. + +## View + +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 +applicable for this domain object. + +# Actions + +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 +`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 +context. + +* `cancel`: Cancel the current editing action (invoked from Edit mode.) +* `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 +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 +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 +(describing, generally, the context in which the decision is occurring.) + +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 +`category` parameter of an policy’s extension definition.) + +* `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 +contained. +* `view`: Determines whether or not a view is applicable for a domain object. +The candidate argument is the view’s extension definition; the context argument +is the `DomainObject` to be viewed. + +# 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 +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/](). + +Invoking mvn clean install will: + +* Check code style using JSLint. The build will fail if JSLint raises any warnings. +* Run the test suite (see below.) The build will fail if any tests fail. +* 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. + +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 +`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 +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 +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 +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. +with contents:`[ "controllers/FooController", "directives/FooDirective" ]` + +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*/ + + define( + ["../../src/controllers/FooController"], + function (FooController) { + "use strict"; + + + describe("The foo controller", function () { + it("does something", function () { + var controller = new FooController(); + expect(controller.foo()).toEqual("foo"); + }); + }); + } + ); +## 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 +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 +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 +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, +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 +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 +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 +disallowed. There are two workarounds if this occurs: + +* 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 +configuration does not create a security vulnerability. + +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 +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 +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 +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, +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 +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 +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 +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 +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 +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 +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 +default paths to reach external services are all correct. + +### Configuration Constants + +The following configuration constants are recognized by Open MCT Web bundles: +* CouchDB adapter, `platform/persistence/couch` + * `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 + 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 `/`. \ No newline at end of file diff --git a/docs/src/tutorials/index.md b/docs/src/tutorials/index.md new file mode 100644 index 0000000000..e69de29bb2 From e52f53b7ff2526438d7d1cd22cd9b9f6671bf9a7 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Sun, 4 Oct 2015 15:26:55 -0700 Subject: [PATCH 07/24] Fixed markdown --- docs/src/guide/index.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/src/guide/index.md b/docs/src/guide/index.md index 7765c8296f..9381152431 100644 --- a/docs/src/guide/index.md +++ b/docs/src/guide/index.md @@ -13,9 +13,6 @@ May 12, 2015 | 0.1 | | Victor Woeltjen June 4, 2015 | 1.0 | Name Changes | Victor Woeltjen September 23, 2015 | 1.1 | Conversion to MarkDown | Andrew Henry -# Table of Contents -```generated_toc``` - # Introduction The purpose of this guide is to familiarize software developers with the Open MCT Web platform. From 1922e1e241074a6b0811fc7250bbccecd3a9851a Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Sun, 4 Oct 2015 18:55:10 -0700 Subject: [PATCH 08/24] Finished developer guide Fixed date of modification --- docs/src/guide/index.md | 2859 ++++++++++++++++++++------------------- 1 file changed, 1436 insertions(+), 1423 deletions(-) diff --git a/docs/src/guide/index.md b/docs/src/guide/index.md index 9381152431..d06e962b44 100644 --- a/docs/src/guide/index.md +++ b/docs/src/guide/index.md @@ -11,7 +11,7 @@ Date | Version | Summary of Changes | Author April 29, 2015 | 0 | Initial Draft | Victor Woeltjen May 12, 2015 | 0.1 | | Victor Woeltjen June 4, 2015 | 1.0 | Name Changes | Victor Woeltjen -September 23, 2015 | 1.1 | Conversion to MarkDown | Andrew Henry +October 4, 2015 | 1.1 | Conversion to MarkDown | Andrew Henry # Introduction The purpose of this guide is to familiarize software developers with the Open @@ -21,8 +21,8 @@ MCT Web platform. 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](h​ttp://www.angularjs.org) as a framework. Its intended use is to -create single­page web applications which integrate data and behavior from a +[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 @@ -35,72 +35,72 @@ 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. +tree-on-the-left, view-on-the-right browsing environment which you customize by +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. * A set of general-purpose object types and visualizations, as well as some visualizations and infrastructure specific to telemetry display. ## Client-Server Relationship -Open MCT Web is client software - it runs entirely in the user’s web browser. As -such, it is largely “server agnostic”; any web server capable of serving files +Open MCT Web is client software - it runs entirely in the user's web browser. As +such, it is largely 'server agnostic'; any web server capable of serving files from paths is capable of providing Open MCT Web. While Open MCT Web can be configured to run as a standalone client, this is rarely very useful. Instead, it is intended to be used as a display and -interaction layer for information obtained from a variety of back­end services. +interaction layer for information obtained from a variety of back-end services. Doing so requires authoring or utilizing adapter plugins which allow Open MCT Web to interact with these services. Typically, the pattern here is to provide a known interface that Open MCT Web -can utilize, and implement it such that it interacts with whatever back­end -provides the relevant information. Examples of back­ends that can be utilized in -this fashion include databases for the persistence of user­created objects, or +can utilize, and implement it such that it interacts with whatever back-end +provides the relevant information. Examples of back-ends that can be utilized in +this fashion include databases for the persistence of user-created objects, or sources of telemetry data. -See the [Architecture Guide](../architecture/index.md#Overview) for more details +See the [Architecture Guide](../architecture/index.md#Overview) for information on the client-server relationship. ## Developing with Open MCT Web Building applications with Open MCT Web typically means authoring and utilizing -a set of plugins which provide application­specific details about how Open MCT +a set of plugins which provide application-specific details about how Open MCT Web should behave. ### 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. -Open MCT Web is built using [AngularJS](h​ttp://www.angularjs.org) ​from Google. A +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. ### Forking -Open MCT Web does not currently have a single stand­alone artifact that can be +Open MCT Web does not currently have a single stand-alone artifact that can be used as a library. Instead, the recommended approach for creating a new application is to start by forking/branching Open MCT Web, and then adding new -features from there. Put another way, Open MCT Web’s source structure is built +features from there. Put another way, Open MCT Web's source structure is built to serve as a template for specific applications. -Forking in this manner should not require that you edit Open MCT Web’s sources. -The preferred approach is to create a new directory (peer to ​`index.html`)​for +Forking in this manner should not require that you edit Open MCT Web's sources. +The preferred approach is to create a new directory (peer to `index.html`) for the new application, then add new bundles (as described in the Framework chapter) within that directory. To initially clone the Open MCT Web repository: -`git clone ­b open­master` +`git clone -b open-master` To create a fork to begin working on a new application using Open MCT Web: cd - git checkout open­master - git checkout ­b + git checkout open-master + git checkout -b As a convention used internally, applications built using Open MCT Web have master branch names with an identifying prefix. For instance, if building an -application called “Foo”, the last statement above would look like: +application called 'Foo', the last statement above would look like: - git checkout ­b foo­master + git checkout -b foo-master This convention is not enforced or understood by Open MCT Web in any way; it is mentioned here as a more general recommendation. @@ -108,26 +108,25 @@ mentioned here as a more general recommendation. # Overview Open MCT Web is implemented as a framework component which manages a set of -other components. These components, called “bundles”, act as containers to group +other components. These components, called _bundles_, act as containers to group sets of related functionality; individual units of functionality are expressed -within these bundles as "extensions." +within these bundles as _extensions_. Extensions declare dependencies on other extensions (either individually or categorically), and the framework provides actual extension instances at -run­time to satisfy these declared dependency. This dependency injection +run-time to satisfy these declared dependency. This dependency injection approach allows software components which have been authored separately (e.g. as -plugins) but to collaborate at run­time. +plugins) but to collaborate at run-time. -Open MCT Web’s framework layer is implemented on top of AngularJS’s [dependency -injection mechanism](https://docs.angularjs.org/guide/di)​ and is modelled after -[OSGi](hhttp://www.osgi.org/)​ and its [Declarative Services component model] -(h​ttp://wiki.osgi.org/wiki/Declarative_Services)​. In particular, this is where -the term "bundle" comes from. +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). +In particular, this is where the term _bundle_ comes from. ## Framework Overview -The framework’s role in the application is to manage connections between -bundles. All application­specific behavior is provided by individual bundles, or +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 @@ -145,59 +144,59 @@ specificity to the application. ``` * __Framework__ : This tier is responsible for wiring together the set of -configured components (called bundles) together to instantiate the running +configured components (called _bundles_) together to instantiate the running application. It is responsible for mediating between AngularJS (in particular, -its dependency injection mechanism) and RequireJS (to load scripts at run­time.) +its dependency injection mechanism) and RequireJS (to load scripts at run-time.) It additionally interprets bundle definitions (see explanation below, as well as further detail in the Framework chapter.) At this tier, we are at our most -general: We know only that we are a plugin­based application.
 +general: We know only that we are a plugin-based application. * __Platform__: Components in the Platform tier describe both the general user -interface and corresponding developer­facing interfaces of Open MCT Web. This +interface and corresponding developer-facing interfaces of Open MCT Web. This tier provides the general infrastructure for applications. It is less general than the framework tier, insofar as this tier introduces a specific user interface paradigm, but it is still non-specific as to what useful features will be provided. Although they can be removed or replaced easily, bundles -provided by the Platform tier generally should not be thought of as optional.
 +provided by the Platform tier generally should not be thought of as optional. * __Application__: The application tier consists of components which utilize the infrastructure provided by the Platform to provide functionality which will (or could) be useful to specific applications built using Open MCT Web. These -include adapters to specific persistence back­ends (such as ElasticSearch or -CouchDB) as well as bundles which describe more user­facing features (such as -Plot views for visualizing time series data, or Layout objects for -display­building.) Bundles from this tier can be added or removed without +include adapters to specific persistence back-ends (such as ElasticSearch or +CouchDB) as well as bundles which describe more user-facing features (such as +_Plot_ views for visualizing time series data, or _Layout_ objects for +display-building.) Bundles from this tier can be added or removed without compromising basic application functionality, with the caveat that at least one persistence adapter needs to be present. * __Plugins__: Conceptually, this tier is not so different from the application -tier; it consists of bundles describing new features, back­end adapters, that +tier; it consists of bundles describing new features, back-end adapters, that are specific to the application being built on Open MCT Web. It is described as a separate tier here because it has one important distinction from the application tier: It consists of bundles that are not included with the platform (either authored anew for the specific application, or obtained from elsewhere.) -Note that bundles in any tier can go off and consult back­end services. In +Note that bundles in any tier can go off and consult back-end services. In practice, this responsibility is handled at the Application and/or Plugin tiers; -Open MCT Web is built to be server­agnostic, so any back­end is considered an -application­specific detail. +Open MCT Web is built to be server-agnostic, so any back-end is considered an +application-specific detail. ## Platform Overview The "tiered" architecture described in the preceding text describes a way of thinking of and categorizing software components of a Open MCT Web application, -as well as the framework layer’s role in mediating between these components. +as well as the framework layer's role in mediating between these components. Once the framework layer has wired these software components together, however, -the application’s logical architecture emerges. +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) 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  -`XMLHttpRequest`​, typically wrapped by Angular’s '`$http​`.) +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 #direction: right @@ -219,1795 +218,1809 @@ 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.  -  +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. + +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.  -The application is composed of bundles. Plug­ins are bundles. -* __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  -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  -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.  -* __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.  +* __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 +non-persistent state associated with a domain object. +* __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 +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. +* __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. # 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  -MCT Web.) -* __Extension:__ ​An extension is an individual unit of functionality. Extensions  -are collected together in bundles, and may interact with other extensions.  +* __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. -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: +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 -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 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  -used by Open MCT Web in any way, but it’s a helpful convention to follow.) +* 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 +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.  + +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. -For instance, if `bundles.json` contained:  +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.   + [ + "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  -properties (all of which are optional, falling back to reasonable defaults): + +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  -used for new bundles. + +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  -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:  +* `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: -INSERT DIAGRAM HERE + Platform + | + |-commonUI + | + +-about + | + |-res + | + |-src + | + |-test + | + |-bundle.json + | + +-README.md ## 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 -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.  -  +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 -The properties used in extension definitions are typically unique to each  -category of extension; a few properties have standard interpretations by the  -platform.  +The properties used in extension definitions are typically unique to each +category of extension; a few properties have standard interpretations by the +platform. -* `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.  +* `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. -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.  +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. #### Partial Construction -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.  +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. -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.  +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. -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.  -  +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 -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:  +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.  +* `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.  -   +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​_.  -  +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.  +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.  -  +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.  +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.  -   -  +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 -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. +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 -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.) +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. -A component should include the following properties in its extension definition: +A component should include the following properties in its extension definition: -* `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)  +* `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) -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 -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 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: +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: +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  -by this domain object, this will return ​undefined​. -* `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  +* `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 +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 exposed. ## Actions -An ​`Action​` is behavior that can be performed upon/using a `​DomainObject​`. An  -Action has the following interface: +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  -exposed it to be removed from its container. -* `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.  - * `context`​: The context in which this action is being performed (see below) +* `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: + * `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. + * `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 -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  -dragged object in a drag­and­drop operation.) +* `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 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 -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  -interpretations for specific sources. +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 [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.) ### 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  -range, and may have more than 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 -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  +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 -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.  -  +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 dialogs  -to be generated by the platform. A ​TypeProperty​ has the following interface: +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  -in subsequent sections. -  +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 -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: +An action's implementation: -* 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  -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  -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  -inherently inapplicable. +* 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 +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 +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 +inherently inapplicable. -An action’s bundle definition (and/or `​getMetadata()`​ return value) may include: +An action's bundle definition (and/or `getMetadata()` return value) may include: -* `category​`: A string or dearray 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. +* `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 -Capabilities are exposed by domain objects (e.g. via the `g​etCapability​` method)  -but most commonly originate as extensions of this category. +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  -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  -support for ​invoke​ via the ​useCapability​ shorthand. +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 +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 -Controls provide options for the ​mct­control​ directive.  -  -Six standard control types are included in the forms bundle: +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. -* `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. -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  -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.  +* `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. -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.  -* `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.  +* `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. ## Gestures -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: +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 of  -unremoved listeners. +* `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 -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  -provide implementations with the following methods: + +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.  -  -Note that all methods are optional, and are called directly from an Angular  -template, so they should be appropriate to run during digest cycles.  +* `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. ### 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 -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: +Licenses may have the following properties, all of which are strings: -* `name​`: Human­readable name of the licensed component. (e.g. “AngularJS”.) -* `version`​: Human­readable version of the licensed component. (e.g. “1.2.26”.) -* `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.  +* `name`: Human-readable name of the licensed component. (e.g. 'AngularJS'.) +* `version`: Human-readable version of the licensed component. (e.g. '1.2.26'.) +* `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. ## Policies -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. +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: +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.  -  -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.  +* `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.  -  +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 -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: +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.)  -  -A representation’s scope will contain: +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  +* `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 definition. -  + ## Representers -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 _represent​ations​_, which describe specific ways to represent domain  -objects, represent​ers ​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. +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 event  -listeners to the element, etc. +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 -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: +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.  +* `id`: The machine-readable identifier for the domaiwn object being exposed. +* `model`: The model, as a JSON object, for the domain object being exposed. ## Stylesheets -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​`)  -  -To control the order of CSS files, use ​priority​ (see the section on Extension  -Definitions above.)  +* `stylesheetUrl`: Path and filename, including extension, for the stylesheet to +include. This path is relative to the bundle's resources folder (by default, +`res`) + +To control the order of CSS files, use priority (see the section on Extension +Definitions above.) ## Templates -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. +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: +A template's extension definition should include the following properties: -* `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.  +* `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. -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.) +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 -The ​types​ extension category describes types of domain objects which may appear  -within Open MCT Web. +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: +A type's extension definition should have the following properties: -* `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  -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.  -* `properties`​: An array describing individual properties of this domain object -(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) 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. +* `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 +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. +* `properties`: An array describing individual properties of this domain object +(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 + properties. -Types do not have implementations.  -  +Types do not have implementations. + ## Versions -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  -the list of version information in the About dialog. -* `value​`: The value which should appear to the right of the name in the About  +* `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 dialog. -To control the ordering of line items within the About dialog, use `​priority​`.  -(See section on Extension Definitions above.)  +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.  -  +This extension category does not have implementations. + ## Views -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: +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  -font set. -* `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 the ​delegation​ capability, in the Capabilities  -section.) -* `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.) +* `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 +font set. +* `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 + property, necessarily.) ### 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: +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  - configuration​ as ready to persist. -* `selection​`: An object representing the current selection state.  +* `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 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  -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.  +* `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. # 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 -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 -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. +The `mct-chart` directive is used to support drawing of simple charts. It is +present to support the Plot view, and its functionality is limited to the +functionality that is relevant for that view. -This directive is used at the element level and takes one attribute, `​draw​`,  -which is an Angular expression which will should evaluate to a drawing object.  -This drawing object should contain the following properties: +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 -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. +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,  -instead of as an Angular expression. +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  -Angular expressions: +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.  +* `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. ## 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. +* `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 -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  -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.  +* `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. ### 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: +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 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 ...  - ]  - }  + { + "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 ... + ] + } -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 -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  -attributes, all of which are specified as Angular expressions:  +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 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 `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: +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.  +* `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  -properties, `​width​` and `​height​`, describing the size in pixels of the element. +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  -the size is checked. +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 -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. +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  -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.  +* `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. -Toolbars support the same ​control​ options as forms.   +Toolbars support the same control options as forms. ### 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 -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 Services -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.   +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 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​`.)   +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.  -  +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 ​`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 `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 -The ​capabilityService​ provides constructors for capabilities which will be  -exposed for a given domain object.  +The capabilityServic e provides constructors for capabilities which will be +exposed for a given domain object. -The ​capabilityService​ has the following interface:  +Th e 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 -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 -The object passed as the ​dialogStructure​ to ​getUserChoice​ should have the  -following properties: +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  -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.  - * `description​`: Description to show in tooltip on hover. +* `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. + * `description`: Description to show in tooltip on hover. ## Domain Object Service -The ​objectService​ provides domain object instances. It has the following  +The 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 -The `​gestureService​` is used to attach gestures (see extension category  -​gestures​) to representations. It has the following interface: +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 -The ​modelService​ provides domain object models. It has the following interface:  +The 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 -The ​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:  +The 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: -* `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 -The ​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 Policies 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.  +The 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](#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 -The ​`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.  +The `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. -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: +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 -The `​typeService​` exposes domain object types. It has the following interface: +The `typeService` exposes domain object types. It has the following interface: -* `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.  +* `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. ## View Service -The `​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​`.  -  +The `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`. + ## Other Services ### 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 ​`navigationService​` 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 _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: +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  -to addListener​. +* `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 -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 -The `​telemetryFormatter​` 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. -The ​`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  -display; returns a string. +* `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 -The ​telemetryHandler​ 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 -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  -a previous ​`request(...)`​ call) available for this domain object. +* `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 -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. +* `name`: The human-readable name of the domain object. ## 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 -Some properties either trigger the presence/absence of certain capabilities, or  -are managed by specific capabilities: +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  +* `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.  -  +* `type`: A string identifying the type of this domain object. Used by the `type` +capability. + ### 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.  -  +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.  +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. # 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.  +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​.   +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.  -  +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 -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:  - `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  -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.  +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 +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. ## Composition -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. +* `invoke()`: Returns a `Promise` for an array of `DomainObject` instances. ## Delegation -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 -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. -  +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 -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: +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.  -* `invoke(...)​`: Alias of ​`mutate​`. +* `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  -modified by other means. -  +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  -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.  +* 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. ## Persistence The persistence capability provides a mean for interacting with the underlying -persistence service which stores this domain object’s model. It has the +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 will be true when the object was successfully persisted. -* `refresh()`: Replace this domain object’s model with the most recent version +* `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 @@ -2030,7 +2043,7 @@ 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 +## Telemetry The telemetry capability provides a means for accessing telemetry data associated with a domain object. It has the following interface: @@ -2046,7 +2059,7 @@ 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. +* `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; @@ -2054,7 +2067,7 @@ this object will serve as a template for telemetry requests made using this object, and will also be returned by `getMetadata()` above. ## Type -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 @@ -2073,26 +2086,26 @@ typically upon domain objects. ## Action Categories The platform understands the following action categories (specifiable as the -`category` parameter of an action’s extension definition.) +`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 +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 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 +* `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 way of its `composition` capability.) * `save`: Save changes (invoked from Edit mode.) @@ -2106,22 +2119,22 @@ 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 -`category` parameter of an policy’s extension definition.) +`category` parameter of an policy's extension definition.) * `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 +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 +The candidate argument is the view's extension definition; the context argument is the `DomainObject` to be viewed. # Build, Test, Deploy @@ -2140,7 +2153,7 @@ 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 +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 @@ -2149,7 +2162,7 @@ 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 +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`. @@ -2161,11 +2174,11 @@ 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 +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 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. @@ -2174,7 +2187,7 @@ 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 @@ -2223,7 +2236,7 @@ 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 +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: @@ -2242,7 +2255,7 @@ most sense) include: 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 +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, @@ -2254,7 +2267,7 @@ Open MCT Web as flat files from a different path. 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 +* 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 @@ -2285,7 +2298,7 @@ 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 +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: @@ -2303,10 +2316,10 @@ default paths to reach external services are all correct. ### Configuration Constants The following configuration constants are recognized by Open MCT Web bundles: -* CouchDB adapter, `platform/persistence/couch` +* CouchDB adapter - `platform/persistence/couch` * `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 +* ElasticSearch adapter - `platform/persistence/elastic` * `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 From d787e84fd4ecd0e7d6e676672c59916b1c06e438 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Thu, 8 Oct 2015 17:17:30 -0700 Subject: [PATCH 09/24] Updated tutorials --- docs/src/tutorials/images/chrome.png | Bin 0 -> 143095 bytes docs/src/tutorials/images/todo-list.png | Bin 0 -> 26034 bytes docs/src/tutorials/images/todo.png | Bin 0 -> 44219 bytes docs/src/tutorials/index.md | 2768 +++++++++++++++++++++++ 4 files changed, 2768 insertions(+) create mode 100644 docs/src/tutorials/images/chrome.png create mode 100644 docs/src/tutorials/images/todo-list.png create mode 100644 docs/src/tutorials/images/todo.png diff --git a/docs/src/tutorials/images/chrome.png b/docs/src/tutorials/images/chrome.png new file mode 100644 index 0000000000000000000000000000000000000000..1b9b7b80d242017ef189d36e40c51c3c1a348e76 GIT binary patch literal 143095 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 b;=#xD5musP>ydtfm1TsZF-OHm5`O*%Sp`nk literal 0 HcmV?d00001 diff --git a/docs/src/tutorials/images/todo-list.png b/docs/src/tutorials/images/todo-list.png new file mode 100644 index 0000000000000000000000000000000000000000..48c84c63e4cb448e30e1125ccbc8931385784a22 GIT binary patch literal 26034 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 literal 0 HcmV?d00001 diff --git a/docs/src/tutorials/index.md b/docs/src/tutorials/index.md index e69de29bb2..2920b27f05 100644 --- a/docs/src/tutorials/index.md +++ b/docs/src/tutorials/index.md @@ -0,0 +1,2768 @@ +Open MCT Web Tutorials + +Victor Woeltjen +victor.woeltjen@nasa.gov + +October 6, 2015 +Document Version 2.2 + +Date | Version | Summary of Changes | Author +--------------- | ------- | --------------------------------- | --------------- +May 12, 2015 | 0 | Initial Draft | Victor Woeltjen +June 4, 2015 | 1.0 | Name changes | Victor Woeltjen +July 28, 2015 | 2.0 | Telemetry adapter tutorial | Victor Woeltjen +July 31, 2015 | 2.1 | Clarify telemetry adapter details | Victor Woeltjen +October 6, 2015 | 2.2 | Conversion to markdown | Andrew Henry + +# Introduction + +## Setting Up Open MCT Web + +In this section, we will cover the steps necessary to get a minimal Open MCT Web +developer environment up and running. Once we have this, we will be able to +proceed with writing plugins as described in this tutorial. + +## Prerequisites + +This tutorial assumes you have the following software installed. Version numbers +record what was used in writing this tutorial; the same steps should work with +more recent versions, but this cannot be guaranteed. + +* Node.js v0.12.2: https://nodejs.org/ +* git v1.8.3.4: http://git-scm.com/ +* Google Chrome v42: https://www.google.com/chrome/ +* A text editor. + +Open MCT Web can be run without any of these tools, provided suitable +alternatives are taken; see the [Open MCT Web Developer Guide](../guide/index.md) +for a more general overview of how to run and deploy a Open MCT Web application. + +## Check out Open MCT Web Sources + +First step is to check out Open MCT Web from the source repository. + +`git clone https://github.com/nasa/openmctweb.git openmctweb` + +This will create a copy of the Open MCT Web source code repository in the folder +`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 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 +(which was used when writing these tutorials) to begin adding plugins. + + cd openmctweb + git branch open-v0.6.2 + git checkout + +## Configuring Persistence + +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 +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 +tutorials. + +To change this configuration, edit bundles.json (at the top level of the Open +MCT Web repository) and replace platform/persistence/elastic with +example/persistence. + +### 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/persistence/elastic", + "platform/policy", + + "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" + ] +__bundles.json__ + +### Run a Web Server + +The next step is to run a web server so that you can view the Open MCT Web client (including the plugins you add to it) in browser. The HTTP server option that is recommended here, for simplicity, is http-server, https://www.npmjs.com/package/http-server. + +To run: + + npm install http-server -g + http-server + +### Viewing in Browser + +Once running, you should be able to view Open MCT Web from your browser at +[http://localhost:8080/](). [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 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 +tab, as shown below. + +![Chrome Developer Tools](images/chrome.png) + +# Tutorials + +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 +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/](). + +### Step 1. Create the Plugin + +The first step to adding a new feature to Open MCT Web is to create the plugin +which will expose that feature. A plugin in Open MCT Web is represented by what +is called a bundle; a bundle, in turn, is a directory which contains a file +bundle.json, which in turn describes where other relevant sources & resources +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” +- one which exposes no extensions - which looks like: + + { + "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" + ] +__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" + ] + +__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 +should see: + +`Resolving extensions for bundle tutorials/todo(To-do Plugin)...which shows that +our plugin has loaded.` + +### Step 2. Add a Domain Object Type + +Features in a Open MCT Web application are most commonly expressed as domain +objects and/or views thereof. A domain object is some thing that is relevant to +the work that the Open MCT Web application is meant to support. Domain objects +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 +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"] + ++ } + ] + } + } +__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 +included a definition for one such extension, which is the to-do list object. + +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 +be shown to users. +* 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.) + +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. + +### 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 +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 +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}} +
  • +
+__tutorials/todo/res/templates/todo.html__ + +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. +After that, we have a list of tasks. The scope variable model is the model of +the domain object being viewed; this contains all of the persistent state +associated with that object. This model is effectively just a JSON document, so +we can choose what goes into it (so long as we take care not to collide with +platform-defined properties; see the Open MCT Web Developer Guide.) Here, we +assume that all tasks will be stored in a property tasks, and that each will be +an object containing a description (the readable summary of the task) and a +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" + ++ } + ++ ] + } + } +__tutorials/todo/bundle.json__ + +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 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 that we create, but +not for other domain objects (such as Folders.) + +The glyph and name properties describe the icon and human-readable name for this +view to display in the UI where needed (if multiple views are available for +To-do Lists, the user will be able to choose one.) + +Finally, the templateUrl points to the Angular template we wrote; this path is +relative to the bundle’s res folder. + +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" } + ++ ] + } + } + ], + "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 +have the state described by that model property. + +If we reload Open MCT Web, create a To-do List, and navigate to it in the tree, +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 +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 +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 +their initial state. + +### Step 4. Add a Controller + +We need to do some scripting to add dynamic behavior to that view. In +particular, we want to: + +* Filter by complete/incomplete status. +* 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]()) +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.) + + 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)); + }; + } + + return TodoController; + }); +__tutorials/todo/src/controllers/TodoController.js__ + +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 +is a boolean, which, if true, means we want to show everything; the second +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 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 +the use of the double-not !! to coerce the completed flag to a boolean, for +equality testing.) + +Note that these functions make reference to `$scope.domainObject;` this is the +domain object being viewed, which is passed into the scope by Open MCT Web +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}} +
  • +
+ ++
+__tutorials/todo/res/templates/todo.html__ + +Summary of changes here: + +* First, we surround everything in a `div` which we use to utilize our +`TodoController`. This `div` will also come in handy later for styling. +* From our filters at the top, we change the visibility settings when a different +option is clicked. +* When showing tasks, we check with `showTask` to see if the task matches current +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 +`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" } + ] + } + } + ], + "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: + +* A `key`, which again is a machine-readable identifier. This is the name that +templates will reference. +* An `implementation`, which refers to an AMD module. The path is relative to the +src directory within the bundle. +* The `depends` property declares the dependencies of this controller. Here, we +want Angular to inject `$scope`, the current Angular scope (which, going back +to our controller, is expected as our first argument.) + +If we reload the browser now, our To-do List looks much the same, but now we are able to filter down the visible list, and the changes we make will stick around 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 +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 +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 +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.” + +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: + +* 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 +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" } + ] + } + } + ], + "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 +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 +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 +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 +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 +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); + } + }); + } + } + + return TodoController; + }); +__tutorials/todo/src/controllers/TodoController.js__ + + 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 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 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 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. + + 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}} + +
  • +
+
+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" } + ] + } + } + ], + "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" ] + } + ] + } +} +tutorials/todo/bundle.json + + 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: + + + +If we click on this, we’ll get a dialog allowing us to add a new task: + + + + 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: + + + + 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. + +Step 6. Customizing Look and Feel + + In this section, our goal is to: + +Display the current filter choice. +Display the current task selection (when in Edit mode.) +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 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 + }] + }] + }; + + function TodoController($scope, dialogService) { + var showAll = true, + showCompleted; + + // Persist changes made to a domain object's model + function persist() { + var persistence = + $scope.domainObject.getCapability('persistence'); + return persistence && persistence.persist(); + } + + // Remove a task + function removeTaskAtIndex(taskIndex) { + $scope.domainObject.useCapability('mutation', function (model) { + model.tasks.splice(taskIndex, 1); + }); + persist(); + } + + // Add a task + function addNewTask(task) { + $scope.domainObject.useCapability('mutation', function (model) { + model.tasks.push(task); + }); + persist(); + } + + // Change which tasks are visible + $scope.setVisibility = function (all, completed) { + showAll = all; + showCompleted = completed; + }; + + // Check if current visibility settings match + $scope.checkVisibility = function (all, completed) { + return showAll ? all : (completed === showCompleted); + }; + + // Toggle the completion state of a task + $scope.toggleCompletion = function (taskIndex) { + $scope.domainObject.useCapability('mutation', function (model) { + var task = model.tasks[taskIndex]; + task.completed = !task.completed; + }); + persist(); + }; + + // Check whether a task should be visible + $scope.showTask = function (task) { + return showAll || (showCompleted === !!(task.completed)); + }; + + // Handle selection state in edit mode + if ($scope.selection) { + // Expose the ability to select tasks + $scope.selectTask = function (taskIndex) { + $scope.selection.select({ + removeTask: function () { + removeTaskAtIndex(taskIndex); + $scope.selection.deselect(); + }, + taskIndex: taskIndex + }); + }; + + // Expose a check for current selection state + $scope.isSelected = function (taskIndex) { + return ($scope.selection.get() || {}).taskIndex === taskIndex; + }; + + // Expose a view-level selection proxy + $scope.selection.proxy({ + addTask: function () { + dialogService.getUserInput(NEW_TASK_FORM, {}) + .then(addNewTask); + } + }); + } + } + + return TodoController; +}); +tutorials/todo/src/controllers/TodoController.js + + A summary of these changes: + +checkVisibility has the same arguments as setVisibility, but instead of making a change, it simply returns a boolean true/false indicating whether those settings are in effect. The logic reflects the fact that the second parameter is ignored when showing all. +To support checking for selection, the index of the currently-selected task is tracked as part of the selection object. +Finally, an isSelected function is exposed which checks if the indicated task is currently selected, using the index from above. + + Additionally, we will want to define some CSS rules in order to reflect these 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; +} +tutorials/todo/res/css/todo.css + + Here, we have defined classes and appearances for: + +Our filter choosers (example-button-group). +Our selected and/or completed tasks (example-task-description). +A message, which we will add next, to display when there are no tasks (example-message). + + 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": [] + } + } + ], + "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" + } + ] + } +} +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. + + Finally, let’s utilize these changes from our view’s template: + + +
+ + +
    +
  • + + + {{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: + + + + If we then go into Edit mode, add some tasks, and select one, it will now be much clearer what the current selection is (e.g. before we hit the remove button in the toolbar): + + + +Bar Graph + + In this tutorial, we will look at creating a bar graph plugin for visualizing telemetry data. Specifically, we want some bars that raise and lower to match the observed state of real-time telemetry; this is particularly useful for monitoring things like battery charge levels. + It is recommended that the reader completes (or is familiar with) the To-Do List tutorial before completing this tutorial, as certain concepts discussed there will be addressed in more brevity here. + +Step 1. Define the View + + 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 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" + } + ] + } +} +tutorials/bargraph/bundle.json + + The view definition should look familiar after the To-Do List tutorial, with some additions: + +The needs property indicates that this view is only applicable to domain objects with a telemetry capability. This ensures that this view is available for telemetry points, but not for other objects (like folders.) +The delegation property indicates that the above constraint can be satisfied via capability delegation; that is, by domain objects which delegate the telemetry capability to their contained objects. This allows this view to be 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 file ahead of time to describe the general look we want for the view. These look like: + + +
+
+
High
+
Middle
+
Low
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ 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 vertical axis, showing the numeric value that certain heights correspond to. The second will be for the actual bar graphs themselves; three are included here. 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: + +.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; +} +tutorials/bargraph/res/css/bargraph.css + + 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: + + + This means that our remaining work will be to populate and position these 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. Specifically, our goals for this step will be to: + +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. + + 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: + + +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; +}); +tutorials/bargraph/src/controllers/BarGraphController.js + + A summary of what we’ve done here: + +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.) +Add a utility function which converts from numeric values to percentages. This will help support some positioning in the template. +Utilize the telemetryHandler, provided by the platform, to start listening to real-time telemetry updates. This will deal with most of the complexity of dealing with telemetry (e.g. differentiating between individual telemetry points and telemetry panels, monitoring latest values) and provide us with a useful interface for populating our view. The the Open MCT Web Developer Guide for more information on dealing with telemetry. + + 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}} +
+
+ +
+
+
+
+
+
+
+
+ +
+
+ + +
+
+
+tutorials/bargraph/res/templates/bargraph.html + + Summarizing these changes: + +Utilize the exposed low, middle, and high values to populate our labels along the vertical axis. Additionally, use the toPercent function to position these from the bottom. +Replace our three hard-coded bars with a repeater that looks at the telemetryObjects exposed by the controller and adds one bar each. +Position the dashed tick-line using the middle value and the toPercent function, lining it up with its label to the left. +At the bottom, repeat a set of labels for the telemetry-providing domain objects, with matching alignment to the bars above. We use an existing representation, label, to make this easier. + + 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" ] + } + ] + } +} +tutorials/bargraph/bundle.json + + When we reload Open MCT Web, we are now able to see that our bar graph view correctly labels one bar per telemetry-providing domain object, as shown for 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 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 telemetry-providing domain object, as percentages. + + +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; +}); +tutorials/bargraph/src/controllers/BarGraphController.js + + The telemetryHandler exposes a method to provide us with our latest data value (the getRangeValue method), and we already have a function to convert from a numeric value to a percentage within the view, so we just use those. The only slight complication is that we want our bar to move up or down from the middle value, so either of our top or bottom position for the bar itself could be either the middle line, or the data value. We let Math.min and Math.max decide this. + + Next, we utilize this functionality from the template: + +
+
+
+ {{value}} +
+
+ +
+
+
+
+
+
+
+
+ +
+
+ + +
+
+
+tutorials/bargraph/res/templates/bargraph.html + + Here, we utilize the functions we just provided from the controller to position the bar, using an ng-style attribute. + + 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 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 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: + +{ + "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" ] + } + ] + } +} +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 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); + } + + return BarGraphController; +}); +tutorials/bargraph/src/controllers/BarGraphController.js + + A summary of these changes: + +First, read low, middle, and high from the view configuration instead of 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. +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 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. +Finally, we expose a view proxy which will handle changes to low, middle, and high as entered by the user from the tool bar. This uses the getter-setters we defined previously. + + If we reload Open MCT Web and go to a Bar Graph view in Edit mode, we now see that we can change these bounds from the tool bar. + + + +Telemetry Adapter + + The goal of this tutorial is to demonstrate how to integrate Open MCT Web with an existing telemetry system. + A summary of the steps we will take: + +Expose the telemetry dictionary within the user interface. +Support subscription/unsubscription to real-time streaming data. +Support historical retrieval of telemetry data. + +Step 0. Expose Your Telemetry + + As a precondition to integrating telemetry data into Open MCT Web, this information needs to be available over web-based interfaces. In practice, this will most likely mean exposing data over HTTP, or over WebSockets. + For purposes of this tutorial, a simple node server is provided to stand in place of this existing telemetry system. It generates real-time data and exposes it over a WebSocket connection. + + +/*global require,process,console*/ + +var CONFIG = { + port: 8081, + dictionary: "dictionary.json", + interval: 1000 +}; + +(function () { + "use strict"; + + var WebSocketServer = require('ws').Server, + fs = require('fs'), + wss = new WebSocketServer({ port: CONFIG.port }), + dictionary = JSON.parse(fs.readFileSync(CONFIG.dictionary, "utf8")), + spacecraft = { + "prop.fuel": 77, + "prop.thrusters": "OFF", + "comms.recd": 0, + "comms.sent": 0, + "pwr.temp": 245, + "pwr.c": 8.15, + "pwr.v": 30 + }, + histories = {}, + listeners = []; + + function updateSpacecraft() { + spacecraft["prop.fuel"] = Math.max( + 0, + spacecraft["prop.fuel"] - + (spacecraft["prop.thrusters"] === "ON" ? 0.5 : 0) + ); + spacecraft["pwr.temp"] = spacecraft["pwr.temp"] * 0.985 + + Math.random() * 0.25 + Math.sin(Date.now()); + spacecraft["pwr.c"] = spacecraft["pwr.c"] * 0.985; + spacecraft["pwr.v"] = 30 + Math.pow(Math.random(), 3); + } + + function generateTelemetry() { + var timestamp = Date.now(), sent = 0; + Object.keys(spacecraft).forEach(function (id) { + var state = { timestamp: timestamp, value: spacecraft[id] }; + histories[id] = histories[id] || []; // Initialize + histories[id].push(state); + spacecraft["comms.sent"] += JSON.stringify(state).length; + }); + listeners.forEach(function (listener) { + listener(); + }); + } + + function update() { + updateSpacecraft(); + generateTelemetry(); + } + + function handleConnection(ws) { + var subscriptions = {}, // Active subscriptions for this connection + handlers = { // Handlers for specific requests + dictionary: function () { + ws.send(JSON.stringify({ + type: "dictionary", + value: dictionary + })); + }, + subscribe: function (id) { + subscriptions[id] = true; + }, + unsubscribe: function (id) { + delete subscriptions[id]; + }, + history: function (id) { + ws.send(JSON.stringify({ + type: "history", + id: id, + value: histories[id] + })); + } + }; + + function notifySubscribers() { + Object.keys(subscriptions).forEach(function (id) { + var history = histories[id]; + if (history) { + ws.send(JSON.stringify({ + type: "data", + id: id, + value: history[history.length - 1] + })); + } + }); + } + + // Listen for requests + ws.on('message', function (message) { + var parts = message.split(' '), + handler = handlers[parts[0]]; + if (handler) { + handler.apply(handlers, parts.slice(1)); + } + }); + + // Stop sending telemetry updates for this connection when closed + ws.on('close', function () { + listeners = listeners.filter(function (listener) { + return listener !== notifySubscribers; + }); + }); + + // Notify subscribers when telemetry is updated + listeners.push(notifySubscribers); + } + + update(); + setInterval(update, CONFIG.interval); + + wss.on('connection', handleConnection); + + console.log("Example spacecraft running on port "); + console.log("Press Enter to toggle thruster state."); + process.stdin.on('data', function (data) { + spacecraft['prop.thrusters'] = + (spacecraft['prop.thrusters'] === "OFF") ? "ON" : "OFF"; + console.log("Thrusters " + spacecraft["prop.thrusters"]); + }); +}()); + + +tutorial-server/app.js + + For purposes of this tutorial, how this server has been implemented is 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 excellent starting point.) + What does matter for this tutorial is the interfaces that are exposed. Once a WebSocket connection has been established to this server, it accepts plain text 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” +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” +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” +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” +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” +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 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” 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 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" + } + ] + } + ] +} +tutorial-server/dictionary.json + + It should be noted that neither the interface for the example server nor the dictionary format are expected by Open MCT Web; rather, these are intended to stand in for some existing source of telemetry data to which we wish to adapt Open MCT Web. + + We can run this example server by: + +cd tutorial-server +npm install ws +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: + +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 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 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 will retrieve from the server.) + +{ + "name": "Example Telemetry Adapter", + "extensions": { + "types": [ + { + "name": "Spacecraft", + "key": "example.spacecraft", + "glyph": "o" + } + ], + "roots": [ + { + "id": "example:sc", + "priority": "preferred", + "model": { + "type": "example.spacecraft", + "name": "My Spacecraft", + "composition": [] + } + } + ] + } +} + + + +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 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" +] +bundles.json + + ...we will be able to reload Open MCT Web and see that it is present: + + + + Now, we have somewhere in the UI to put the contents of our telemetry dictionary. + +Step 2. Expose the Telemetry Dictionary + + In order to expose the telemetry dictionary, we first need to read it from the server. Our first step will be to add a service that will handle interactions with the server; this will not be used by Open MCT Web directly, but will be used by subsequent components we add. + +/*global define,WebSocket*/ + +define( + [], + function () { + "use strict"; + + function ExampleTelemetryServerAdapter($q, wsUrl) { + var ws = new WebSocket(wsUrl), + 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; + } + }; + + // Request dictionary once connection is established + ws.onopen = function () { + ws.send("dictionary"); + }; + + return { + dictionary: function () { + return dictionary.promise; + } + }; + } + + return ExampleTelemetryServerAdapter; + } +); +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 close-handling for this WebSocket connection have been omitted for brevity. + +Once the dictionary has been loaded, we will want to represent its contents as domain objects. Specifically, we want subsystems to appear as objects under My Spacecraft, and measurements to appear as objects within those subsystems. This means that we need to convert the data from the dictionary into domain object models, and expose these to Open MCT Web via a modelService. + + +/*global define*/ + +define( + function () { + "use strict"; + + var PREFIX = "example_tlm:", + FORMAT_MAPPINGS = { + float: "number", + integer: "number", + string: "string" + }; + + function ExampleTelemetryModelProvider(adapter, $q) { + var modelPromise, empty = $q.when({}); + + // Check if this model is in our dictionary (by prefix) + function isRelevant(id) { + return id.indexOf(PREFIX) === 0; + } + + // Build a domain object identifier by adding a prefix + function makeId(element) { + return PREFIX + element.identifier; + } + + // Create domain object models from this dictionary + function buildTaxonomy(dictionary) { + var models = {}; + + // Create & store a domain object model for a measurement + function addMeasurement(measurement) { + var format = FORMAT_MAPPINGS[measurement.type]; + models[makeId(measurement)] = { + type: "example.measurement", + name: measurement.name, + telemetry: { + key: measurement.identifier, + ranges: [{ + key: "value", + name: "Value", + units: measurement.units, + format: format + }] + } + }; + } + + // Create & store a domain object model for a subsystem + function addSubsystem(subsystem) { + var measurements = + (subsystem.measurements || []); + models[makeId(subsystem)] = { + type: "example.subsystem", + name: subsystem.name, + composition: measurements.map(makeId) + }; + measurements.forEach(addMeasurement); + } + + (dictionary.subsystems || []).forEach(addSubsystem); + + return models; + } + + // Begin generating models once the dictionary is available + modelPromise = adapter.dictionary().then(buildTaxonomy); + + return { + getModels: function (ids) { + // Return models for the dictionary only when they + // are relevant to the request. + return ids.some(isRelevant) ? modelPromise : empty; + } + }; + } + + return ExampleTelemetryModelProvider; + } +); +tutorials/telemetry/src/ExampleTelemetryModelProvider.js + + This script implements a provider for modelService; the modelService is a composite service, meaning that multiple such services can exist side by side. (For example, there is another provider for modelService that reads domain 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) 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 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 whole dictionary.) +Some notable points to call out here: + +Every subsystem and every measurement from the dictionary has an identifier field declared. We use this as part of the domain object identifier, but we also prefix it with example_tlm:. This accomplishes a few things: +We can easily tell whether an identifier is expected to be in the dictionary or not. +We avoid naming collisions with other model providers. +Finally, Open MCT Web uses the colon prefix as a hint that this domain object will not be in the persistence store. +A couple of new types are introduced here (in the type field of the domain object models we create); we will need to define these as extensions as well in order for them to display correctly. +The composition field of each subsystem contained the Open MCT Web identifiers of all the measurements in that subsystem. This composition field will be used by Open MCT Web to determine what domain objects contain other domain objects (e.g. to populate the tree.) +The telemetry field of each measurement will be used by Open MCT Web to understand how to request and interpret telemetry data for this object. The key is the machine-readable identifier for this measurement within the telemetry system; the ranges provide metadata about the values for this data. (A separate field, domains, provides metadata about timestamps or other 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 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 fix this, we will need another script which will add these subsystems to the root-level object we added in Step 1. + + + +/*global define*/ + +define( + function () { + "use strict"; + + var TAXONOMY_ID = "example:sc", + PREFIX = "example_tlm:"; + + function ExampleTelemetryInitializer(adapter, objectService) { + // Generate a domain object identifier for a dictionary element + function makeId(element) { + return PREFIX + element.identifier; + } + + // When the dictionary is available, add all subsystems + // to the composition of My Spacecraft + function initializeTaxonomy(dictionary) { + // Get the top-level container for dictionary objects + // from a group of domain objects. + function getTaxonomyObject(domainObjects) { + return domainObjects[TAXONOMY_ID]; + } + + // Populate + function populateModel(taxonomyObject) { + return taxonomyObject.useCapability( + "mutation", + function (model) { + model.name = + dictionary.name; + model.composition = + dictionary.subsystems.map(makeId); + } + ); + } + + // Look up My Spacecraft, and populate it accordingly. + objectService.getObjects([TAXONOMY_ID]) + .then(getTaxonomyObject) + .then(populateModel); + } + + adapter.dictionary().then(initializeTaxonomy); + } + + return ExampleTelemetryInitializer; + } +); +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 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 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": [] + } + } + ], + "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: + +New type definitions have been added to represent Subsystems and Measurements, respectively. +Measurements have a telemetry field; this is similar to the telemetry 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 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 ensure that Subsystems and Measurements will be recognized as having composition and telemetry capabilities, respectively. +The adapter to the WebSocket server has been added as a service with the symbolic name example.adapter; it is depended-upon elsewhere within this plugin. +A constant, EXAMPLE_WS_URL, is defined, and depended-upon by example.server. Setting priority to fallback means this constant will be overridden if defined anywhere else, allowing configuration bundles to specify different URLs for the WebSocket connection. +The initializer script is registered using the runs category of extension, to ensure that this executes (and populates the contents of the top-level My 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 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 available in the constructor call. + + Now if we run Open MCT Web (assuming our example telemetry server is also running) and expand our top-level node completely, we see the contents of our dictionary: + + + + 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 ourselves the ability to retrieve this data from the server. In this step, we 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; + } +); + + + +tutorials/telemetry/src/ExampleTelemetryServerAdapter.js + + When the history method is called, a new request is issued to the server for historical telemetry, unless a request for the same historical telemetry is still pending. Similarly, when historical telemetry arrives for a given identifier, the pending promise is resolved. + + This history method will be used by a telemetryService provider which we will implement: + +/*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 () {}; + } + }; + } + + return ExampleTelemetryProvider; + } +); + + + +tutorials/telemetry/src/ExampleTelemetryProvider.js + + The requestTelemetry method of a telemetryService is expected to take an array of requests (each with source and key parameters, identifying the general source of data and the specific element within that source, respectively) and return a Promise for any telemetry data it knows of which satisfies those requests, packaged in a specific way. This packaging is as an object containing key-value pairs, where keys correspond to source properties of requests and values are key-value pairs, where keys correspond to key properties of requests and values are TelemetrySeries objects. (We will see our implementation below.) + To do this, we create a container for our telemetry source, and consult the adapter to get telemetry histories for any relevant requests, then package them as they come in. The $q.all method is used to return a single Promise that will resolve only when all histories have been packaged. Promise-chaining is used to ensure that the resolved value will be the fully-packaged data. + +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 telemetry capability. + + As such, the source and key properties we observe here will come from the type definition and domain object model, respectively, as we specified them during Step 2. (Or, they might come from somewhere else entirely, if we have other telemetry-providing domain objects in our system; that is something we check for using the source property.) + + Finally, note that we also have a subscribe method, to satisfy the interface of telemetryService, but this subscribe method currently does nothing. + This script uses an ExampleTelemetrySeries class, which looks like: + + +/*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; + } +); +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" + } + ] + } + } + ], + "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" ] + } + ] + } +} +tutorials/telemetry/bundle.json + + Now, if we navigate to one of our numeric measurements, we should see a plot of its historical telemetry: + + + + 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 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; + } +); +tutorials/telemetry/src/ExampleTelemetryServerAdapter.js + + Here, we have added subscribe and unsubscribe methods which issue the corresponding requests to the server. Seperately, we introduce the ability to listen for data messages as they come in: These will contain the data associated 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); + }; + } + }; + } + + return ExampleTelemetryProvider; + } +); + + + +tutorials/telemetry/src/ExampleTelemetryProvider.js + + A quick summary of these changes: + +First, we maintain current subscribers (callbacks) in an object containing key-value pairs, where keys are request key properties, and values are callback arrays. +We listen to new data coming in from the server adapter, and invoke any relevant callbacks when this happens. We package the data in the same manner that historical telemetry is packaged (even though in this case we are providing single-element series objects.) +Finally, in our subscribe method we add callbacks to the lists of active 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 multiple subscribe requests to the server, because we happen to know that the server can handle this.) + + Running Open MCT Web again, we can still plot our historical telemetry - but now we also see that it updates in real-time as more data comes in from the server. From 3701fd75dd728c70e113606fc0878cae01624036 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Thu, 8 Oct 2015 19:46:25 -0700 Subject: [PATCH 10/24] Finished first pass of tutorials --- docs/src/tutorials/images/add-tasl.png | Bin 0 -> 23938 bytes docs/src/tutorials/images/bar-plot-2.png | Bin 0 -> 39736 bytes docs/src/tutorials/images/bar-plot-3.png | Bin 0 -> 42557 bytes docs/src/tutorials/images/bar-plot-4.png | Bin 0 -> 31252 bytes docs/src/tutorials/images/bar-plot.png | Bin 0 -> 35055 bytes docs/src/tutorials/images/remove-task.png | Bin 0 -> 11357 bytes docs/src/tutorials/images/telemetry-1.png | Bin 0 -> 16915 bytes docs/src/tutorials/images/telemetry-2.png | Bin 0 -> 39792 bytes docs/src/tutorials/images/telemetry-3.png | Bin 0 -> 51914 bytes docs/src/tutorials/images/todo-edit.png | Bin 0 -> 25216 bytes docs/src/tutorials/images/todo-restyled.png | Bin 0 -> 25700 bytes docs/src/tutorials/images/todo-selection.png | Bin 0 -> 32170 bytes docs/src/tutorials/index.md | 4051 ++++++++++-------- 13 files changed, 2160 insertions(+), 1891 deletions(-) create mode 100644 docs/src/tutorials/images/add-tasl.png create mode 100644 docs/src/tutorials/images/bar-plot-2.png create mode 100644 docs/src/tutorials/images/bar-plot-3.png create mode 100644 docs/src/tutorials/images/bar-plot-4.png create mode 100644 docs/src/tutorials/images/bar-plot.png create mode 100644 docs/src/tutorials/images/remove-task.png create mode 100644 docs/src/tutorials/images/telemetry-1.png create mode 100644 docs/src/tutorials/images/telemetry-2.png create mode 100644 docs/src/tutorials/images/telemetry-3.png create mode 100644 docs/src/tutorials/images/todo-edit.png create mode 100644 docs/src/tutorials/images/todo-restyled.png create mode 100644 docs/src/tutorials/images/todo-selection.png diff --git a/docs/src/tutorials/images/add-tasl.png b/docs/src/tutorials/images/add-tasl.png new file mode 100644 index 0000000000000000000000000000000000000000..7780365c5a5539c09064d2d372181e1575f48f2f GIT binary patch literal 23938 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 LNkvXXu0mjf6F|b0 literal 0 HcmV?d00001 diff --git a/docs/src/tutorials/images/bar-plot-3.png b/docs/src/tutorials/images/bar-plot-3.png new file mode 100644 index 0000000000000000000000000000000000000000..0899984a336468dbec9f21cf76c9660f9b554a99 GIT binary patch literal 42557 zcmb5WV{~QB+BF>8cG6+Twr$&X2Rk-8wr$(CZQJSCcHTbsxzG3f{{HM5WA7TPYF@0h z)|@*`K~5YV1_uTR2nb$MLPQA&2;>9^2sjf85Z46Xux#|UEiQK3M@eo#ap{|pRJM1a2Y|Nj2Zv`q+LQaalORg0hh zH|g&b>;KcdH)9wX`+H@c)NdBsT-O_O?|(H*$dtfaE_$oQm@IgNKWl!Ga}s z@R+3T{Ky)0i3B9nuj9uoC^dX5yO?#zvrQSAM=|7?bIaE?h9|z~=HHDHU?XJF z&LIQ403#VJ(dq_DA^*p;_QGJTig+&~Dk_ONIXMCb%&ks_0p%4H5fKq>XBEPas-xFeHClAs~WChc-Nj$MSM2 zH|gMoD-M{T{?XCV{Cwgo(T`>o7@#Mat}Wo)jnlwa_;Uito69g*N5O)Tp}C3u)PAC% zfBoGX2&8}!(3iV%-r;PqchA`kS+!G@(gVx_bmA`R~ z+aIAeLn{#0?K`M0>pTp5uhbZ}M0<)x@)oj?F`u~U;H(YlVR*R2{UnEG!J&F>n5{LJ z^+#N!RQc;)nvAv`zBc&2vZ!K${(38S0?98AJa}~)KdMurDOO@>=yIiZZL95!OLrVZ zt%S9)$N#70-(0c<-oXa=|5DF}kU2)G-3^J;78AHG@G-{gs2rD5Wu;_7a_9D9aP3ie zIik|HolDL_;1T_6*ldwp$C_)cnfE6d{QlLlavYbt1RvS)jI%mhm5qLgE^+U{NC;p; zF~K}?{(Uj>5zbt+JX6+BGuANd`XP3!w%EROjVJ(4EXavj1nfTWny zzezC54Ar~4sHh&eeE-#jXAO+X`+G+2OS%V@5TQ$Py_w+rs8O*@k+9Bj{W>g0lt;Dm zz^OLcZl4ZfC!t`T)*W*}S=`o_T4BafwMjHSqEWGC{8$}wcswi^i4mIneJwUT{yyMK zVO?J6ArOnI`1)I9G^5k|OhgP3t{U8CFS`t(=w9L2L_U#dU8hy+8VV&{P9i%|LdA4wrn)dm20i)t za))Eb#5z~XQ5esljkl9{g;L)<{W-Az53Jm6q7N4T!w3~kXLaVtW5IKwb90M# z_UG$~WIzzDHbPb>zF*qeMJcM#IpBNSva)7q{3KkF+Q`RaAjL>+x7|aw1;uXSUJ!P^ zN&{9IN^PVU1>4rWX25aa-9+MJ^1Mx_+IVR>NoX_4Jy)OpkxU4Y*hDH3_C!Tj#Imr* z(lS^P?vX915S1P=K?o6myRNe|zcMeYQRkb9obu$U^V1kC$J~8*@J>~P{)g9|tQUH3 zxX_^dD~c^cx#gUXPGQLP@lGe2#^hY}wtb@*@zo{;vxDP zy@0l+PF1t}%t@`77MIIwIB7SDvqDPNY6I`PSO|J^hhO)1=18%q!k-Sl*S6Jah11bz zgm1_AKW&C7+C336-;Tk$Mu<6gPRhO?`-H+GhZ+G7Wh7(x<)2YKpN^W1@PNJW7Bu{~ zjgOs6Bt~Hs*TDEVJHjvf;=n^mWehKBK#;BL;;(`RcnYw{WtTRtnIg}UjRp+0tmnFy zhqN|xKLL*9eWgn4CS_|%5T?d$FGx!2f$Bt6PbEIBqZKUig~zC25V9_Sv2Cn;0+VR74d* z)CLwB!xC<{py<^L`dybMKn%3xh6Onyqc>y2%mCs?_Bv2Z76I%J<&G|kn2pb`@F4~lsebV({mN62&uW=~70l7!Hj&j}Iz{ZK={ly5CS2Tipk`SJ zR#C%?E*SR2N%C&SE85&gJ!_UxHkgG5av;G<5su8jH z994YzDT~zERgQ8v<3SNMN7$N~MQIb{rD5B@|%$=!2guY9VH^0hT2rL48{| z9vsa5L1G4dAPhjo^mx%mx8-pkq->fcN~WwQPT6_`UN1M1sW?YN$}8P2pvhu&t{ui} za)l?43k5u17lPrIk9Ro0dkrb}6b~FW5v<`@*Fs=UCMCn)$ac&C{P0{kIbnJK60P{R`#hcKOUL@XPrvT_G+vNFt*u3=UQE3>)M0x5MBW zhleAx`TD(xYh0XBSUlFg`*2nSHq-V`k&JJtxBjbpfzA9++7`8Lhx74{?biO&+(=1! zuh+eq)_DW@7R8NLfPn1n$qLF$oYj+6<)%{{7A&njy>g#d0 zbEzM;RU=#onwuQP@#fpN;UqwE;DYGN>)5#1KITVX9o$2M8+F1O4?9d`+MM_2Fr>*+sWt~EaV(m6ZRCCw~ z4>Z{+)6^09!H%|guf?U0@scH_FpEy=QN+Z?POP%DQGl*G8R>Q;^V3nEh2GVk1SDL1 z=`HgJS+JWwrDk zjlQs=|5yX&SX#^RZrub1h5klUm($IMq#>=lI4mzkjQrg)20_)M-0jlQ#kN=8^aIye z1Wt?FFH-%u_l0Je*wF0nM949Wo!F;h;Y);iif%d$GSm^P=tkD2&S@}*&aVT zA1_U6&FJ@ar0Hhm2y=z67SeTvP4~xiWfEj*nXksXFS8PH7j|r@oTB&Xs(R1Y0y`3- z#@lrJNU(Jc0UxzgMm7$Joyc3dbcWt*gv*~EOvDJ2te>)&>>q|h@8$wnR_;8ao*wFw zo4?0yWm*!F^Oet!Kh97Q=|eYy?ost7-xe|xf#fAxs^{b;9i$=*xiiCBXFDfqy4loc0dRz zd+ZJ!Vnz=-j&oZ<*X)@qB?#r)K=4Oo6-&rI#a0{8=0+93+7Q}}YsEix;#D3h^O#x7 z<8skf*^>>!{L1=CJvwbp(uTh2mmw%Dh#E9!2(oF;ookZ%WEzkll1p&pl*+fLRmp6`vRQ>L9Sj3&H! zz&?gUL_o*|OL%uJI#2D=81b!wevM+7;@jNZ-lLb{;%L!$T&#nX_<)Z{jvMuF@>UNt zd4Ez%1AnkRXuxoj(OqEm?pe7ey7@4O5VZ)%p%O}G#=eZUR_&2b?dEbTE#F)@ALSdw z3&@LoIkpKNvLTQ6v!`9>jT^IoE+oKf01Zo64>JVC!9lk*4!CE*%%SLodbJ< z>!l;+n+0P!CU9gt=522jg7lzVsf@ZbOn(r7Lxft!lbAqkWk8JcxMff=W6r)&)MzlN znem~Iy^+c4p`dp<4=BE?66t}O@iY9Gr}=pUbHV?0_4VmewWa?n7sROFgo(+ioJ<>K z)%R)ObE-cO!;NC+k$URcdU|mnyt)?}ksh0fsV(u<=z4U~GDZZ#yXn^GuJz}YchvPB z{^&Dfcw*fTKBagzd4zO}fY?zuf2K5mh16=0F!{aFyz-|BgpEy_@^Zh&CaoF+aveLf zoZ}VVwqKhxxxKaD16L~r?+0Qb5;5>^)iH;mh zEgEL2Z_}TR5!t4GR$B6G|4$0O$2mxFpW1A4Lq8qz1H|;E+@@9zqr0A(F0bpG9&|A0 zew2P{xs6AWqsH%ejeDh8wWVK`A0q3`sxFpWu2R@nRvRlIm1LdS`7d%WuE&)|Zei%w zfO0Ss4zqz5p;pF;GMx_8wzxqWgqAkb@o-7Ueq)_QX=Y~JH;f<+;y8gaYE5(#4I<{C z;+2r-4m(8;{wp~)HuUQc2jy&&7vq>{bm}A?dHFx5O*vT#g4(%>KD&b!e7fEb3po*5 zg-Vo#jXLX%oVG(MX(Ht-Av^bn7f`wl!voR|2OZBn*DZ&6x5(sI)ik{Ke~OJiemsAf z{Z_XrVCfCP_Dd>)T=}q%*5#m({l07<*&BkDspRYU+9;T!%Vmx)Snw_1;(lh_KP>VC z4m9y)zr15K^Rv~j6h(J`Br=p#WM8n;OTy>R-~f06g4uQH0;)Vgrw!sm0?*zA5;9?Sda3F} z@T(bEss=0;_8uiiDo(9RvDpP&>}B{Kyiphwd$OSIumJtM(C5rYG~J$ZoDlw8xn$_^ z5!x3i^AIv@Sy-PQe-TWm-gReJwY>#l>(32ql zU4Mk&Nj=GDB#<-G5K6+s(|CWtYnVu96wEu!bY8^9^OjnGZmJ!5@A$j;84)MzqO zhgP;T9cIt*YSTBZeUUnI@OgEgKonR#(}7j|&O&q5An68T5nunnL>JOG{8#;soSb#cE`(Zk#O6IqlTFrG%nBx5nT`Gvmv zdJrbX!T2&>V2~w{VNqMO&J!BH!{zYcC(H3P5xkNsb0r>AIqv_=Ni5~P`{|kDdn-Uy zPN{ByIpM0*ZFCi~a$oK2@Nav^<5u1fD8YFrHC4X$|X@6G`@k3`6EiiX# z=i?$j@LfGCfRaPZjR-FdCN;Oabq}D>bP@4OK_srVV}*ax)LuN>=VNS#p;A;oq3P|} zAyy}6EEYxaEFW=Lw=uaf`kkCk)G`4d@|c7J(1wO=zA<=q`tDqa)&zp zq3#q~n%|%*{vqd+JC;#fbUeoU_GUJ3qMKuiB?b&pkD<&lnV|?;^8F8VzuwZO<$2wq ziF`c<7E=as>(F#9QT-B({4ScFG z!715jX!2r6wrHxTaDY(HWrp{eM)U2)u}6+fWoeHY#z-bBYg=9`;q$XxRiEJJzPor7 z?q1%)tpI9ma{7nk2>lXZXz%0_a>Y*iD@{zpb_f%E;?G@bYU=QSw$G|lEwmQ}9DBUS zf+=Q97EO3IY-Q2ye6;*`mzj8XKeN#d2V&7O5Qv`O{`pSL+r!Nx#!RT>M2I_xC=hi} z;l<7K4q>+*1r!b&uaqg6_q<6Rwsb9=)G?|GIngag#F zxWo$95?Wp5Fsu6c2R%HqID|$+zr#-r>@k+?{ZnUd-cH$j!Au{d8ozvGK2YJGpWRq=u@nk}_Y5%UiEq zYsvw(7}&c%5#PHB&l}EUh}%hbrG%6n9((@EQu~p%lU^i| z$JghNf%2ce85-=#&Ym05+;%L*E?B@hb#0C#jjH9@>L!t7_eQcz_Yk_;&xZX0%vIqs zv~)f~%cs|^SVgy>rVKYLpLYXwFi&Bc?p_PsK0R4DKf@vtb$(h^g+@z1fx1@hU&M&R zYO3e&UmW2Z#?ZqZh!0A_tyK~gjcv5Q;~cz>@_V~G2%+eEn!QgGH-!=>9am_f{4p9n z7oJc$*j*Y(ZTSq5c1HPGLd#cwtJ4z_`~p5K8fU%23I~azAU0fP4V}`nV_%t_gIM~HMi`}y936+R((-HVSe8J!GLjt zIA4YFzJj+HxvL-mA|Dsim!SyY-Ji{r(| z8CC|-yZK^CvBXe6#^i%A+sv`T=j+lT7f+qSofQMU%&Ota_W-DHfJR1@xfXrHJpO5a zQR|SAQZ& zIV_8jKqspgJKjmo_Q_*5W_2wP`uWbjAmoyhM$oGiMFL41zl;pn?=(lPLQQ=sg0~Y~ zNQNkf9gjfc{PqTj!;rq|QCSBMB?GhdJIyV>%6kuX`uRL;;3vrc4jH3nIGI0ev(c}Gn zSaK7~O)KzZ5`=Vn5f&yu3!mf{lr@a(;yg;+Q@-ngvowI)-3ZPOPIB=s%zTSdK5L$C zMQOZRiNl#{tXO+1+yz!^Rmr8?fC+X}$Yq^|3F2CzdLuOi{F2?O)z{EvXl}v$#XYTw zkM9S{(e`55!zO72)p3M;i>hBp@V&m@(X7w5Kp3Sw@RG~}r81^09ILf0=i{93c&}2q z)jhX0UBr+Rl=p>`=<6Cv3`6h*kglD7Q^n+}?6mKrreFb$-9U??uLmg|qkng=NzV7# z0!dRgDLv8UA4(=d!UDl5OsESj;1hpRJj(6IGVYx%_5V z2~3=ju6uSb`~@fKmgJOm*l?J+B7ufpXx8CC?qq*xT9VTh#)(AJSv3fZFufXX9xV7y z3nMlOxH1E~;HK%oFSiRBr|u0A;Xa+Cj~%UuLVG3jX$>m?mO3xgXRc1>DzL?2Q8niK zL@W&5z|JkBbZC2Jg3Cf!%?tOfKVY(AKgT;QXdl-XgLn z8Z;}ZV`;#0b2s?w@)sfw26{Addg3qN=Y$Cq{p{t3Q1);UB^!d4(xIQ5><^cq{ONiIt6q!B$l+TxM667)?|Vat0xd;1 z6tq2uB0sWKs|H_#?XTESxq3%%5shAn-2Nhy*%)=Yp4J#5qnX^uRo%HHWrOM6+3F; zhu)6lq%vfdg7KDazq(TlQeG>3+W;@TBC(F@~+6(~6fx7;x(6i<&7CU!*D);*_ zgYrTrV60XrFq`FZd`SXAd4o~_7#&wX7KN89sHY1NhsR;z*~$;Vwzk9xUM;ToXsyP2 zMwTWNd|Wzrb`5fuZrzce{&-99((kUG^PC^|LpT5GMl`a zGi#!qydzo#2q&4YxQCG$VdXD*NY$vurqfq+ToqlV|{u`;`KTzHHcQChAg^447E zJWv@Pw}Op)vFE+Oq>dlfFE>{KJG2=DWpRe3`TF<%uyI|)J=?ehl+A<0+pnZINn%S4 z$y5Z$UFA5_`YVB)Bw4IAqjuF>=J-Ozf%k?O7S~oISz3Hty=#IS;8cW_urrAqyIs{J z6!78mIk&`0wBn>fQ!e||C7!$VCm`h^H4oM!5`7U8WZxA^CX_*S#NlRJ5g%7IH!q;D zTle;CMfvRj?e|ay%2XUul~0qSqG-hQs19wS4pQYYD{A!i=?W@n6e#v>TfW@1RuLW19tJYx16bXj9r}1eo`c4sXI%H>R0XesLt3uxdavk|>=IO`5u?nLqGj4_{4^ z`>1xb9fxJHH4yObh|@4KSnI&O?Thi6K0G9n%M+jxmvhvARYaa^8LvC~rjg~!%8P1| z9^E$X4(vAiqu5lTczH-FL*lQ-BQhIJJwA$ovhska`B4dym?I8h3P29d;NjC*ay9Hl z=mteaQ6lzq#Qt#fLlAv`#++@M{Ds2^V*$M56akFsPg;GDO4dL=-|ls~?vQU~<^fDTd`RLl1>3yo zP}D||a#{7;oF)M_Bj7_g43j3%%o0Win+iz61Wn?|?bLPklIQMbhbL7NxS`~Ac%xF6 zC(FP048rUP>Zv9JuG!~??IaBHe3F2R>~-g8ru?^jiB?g}jTsJjw$!dnagBrkUVBK| zm~l11tg+fytA8Yj1E@<*KL#wre0!J8pe)8-4uK$1qzpGLEo&^4OD<0*nbrKrkgi6Z z;y`sI!(o;FMmu{dOf)VMk&5a%5+?lp7hP_+t8e{z`w-Cg>YzbfwT?MCQ8L^Law@Hn z?;ip0yM~$R3|C6rpdVv^FD}ujBE0juy392Zw-x4yveBP_!P47!qL;L!VCj^lUB0@w zI=;Pu3jPhLbCFQoBE>K`YeoIFrdy;ItaYr0huc#ob3}-adflMlEUhcFWk)>EhDQ=- zNyHlOvn)$OxA7}YhDiMR3YX5iRcdZ2L5;_eYkJynKzSi=*pGm>xTu>&qyEgbO2?v3 zgMw84Bx#UPG&p;fF;C~&EsXS}_Wh}MRJU>FB{RzRp5>14i#q~|h!8aIsWI0ao!bLe zGmNum{Tz=`7m$f+DA#pPX*|^+oiErCOuPnL-XPr0Oae<|H#L;=rP#qttzTF5!?>W) z-dM|{aJ~xMaKGdxgp!ThhqOj$=)glo_#)0?gO*&6Q*F2vKRzqnpM(nLioa0^!p* z=m?U_G{9pakNS+++^JM(J!V$x5NE=HrVnh^{YYf>`FMQ~G<(2ZDdwDphVX6N_r_5raF%(N45?2@lTy%Tfod`C=i^cG0sWQP^2XdIPU8(pkn|0 zXwthAf;AysTyO&KZG`1uF!&4?)y`|@Q#=&EPo{Xk=BQw4HUn0`UmT_=Y70rZv@tSsB*J>9O<$u=?Y*P>PWm!{E~BCCd@jKYKEX1zL5D*VkZ%NHj44J-_;U1%J4|QzwR#}fnaQ;*4LYpR(sIe6OdyO&wA+fm zu6Q~=5|Paz$mg&9$kb-nE@)m{9vz=0kv9%|F3CfKjg9JQZwKs22(y2!$pN0+u(|49 zSNa%~sdAi@Fv!7W^#TO=LVq&u=q#m_E*|cSnE0ZeRL@(-jWh*^p^_!X<{>}&$U`Yc zqs8w&ykgO?ax9{&X$0Sf{V^pBrq1nI!+BOj)c~`k=pZS?#G@nO{?yj&+tjNFoFWA7 zSAj}JIn_CRwM9aDRt;S%hkb>$5RqCDe}my@ZNagH0H4F-Z%VCj9FWt+7K*))f$;gR zu*vK=AaQt?b?sAo7`4b?){KC$;ZO;_eD=k{9xMqMpID7-M$Ak2e!*SRtp&$nd++3= zj6_yr(Zn3v_Nv?S$I?f6K5r4fbmN7s-^Fd};o(GmQLZ!MU9+Y2yyrlw9|bCQ1YdEr zuOxFkg?z5744U7x==hMO;hc3j&7NR||F}Rf$!K*mIxmht(z>QzD+}Tef0!i6$`Ucq zj4-Ceq;$EgD;w1bjGNP>GeI`w3G<=KQ-nCc(5Cdtjg|=f#YHj!_LsdO*8~uAP)aUP zmK^=FT%A6c3J5)QlJ?6B!*1a0?uTiVHSWv_J+L z64Lj9Y9La#gBh%}HbOTUDV0@ww+0=nrCSsaMz+fFHrMAvwHaHx3euSpqXHy?=NA5ZGd0l+^x_}1O(P} zO~;~kw3*ppUXIGBd)i1BgUaa8T}1KC=dqU^L{h{41~b5#kju_PA%0O|l@TnjsQm&> z!hxZeFu19=Y`-da_Oy>wiZ2oDh;eevKq8av#?tK7?G!DdSGRVzXoF=tZHBg1@g#%R&Z@ z)k)N&C@njbzAWxS=tt)RK%<15w<5rgA_|)nxO)!fwR*Y4Ft2EpcBnB&WVGFVH4U@m z@Q4wOyVG)8TRT#J)1zwIq8OguI?_T57k;6t@u29dG)f1tnLS)^>R+t{L1 z>q&XOSpaY*AK_9zB?Ksc?WR0NKqC#H76QKO9NeZ^68nU*fp+SsuVOe`Ol!uC{F;_b z*Iq@YFQWZZMlW=3U^vP~w6-lXsp!>WMX>p={NG8{sUQ6v7y`Tx!~QHlJa71Hv!L&* zI3Lf}rUJjaX3w;}bpF}I^LH-mBpjH5Gxk$8@%i*UUx~!ycsRh`OZ57Rsj>BB?AX6e z#3IJ=wm5G;*V1q6Bs{(?I%HP=w*B@rBg2284)L@|{^9)n z(P1Z3DDNmNXiDp*+ttNu0O2^IFW+w+1Er>(#tmZS|B4q^`%9m^^2y|Z$|mH%Oo3TU{~CN%5JOTJPI1R z{^)KWr%HlmIa~L+g;|UNoKJLmCG%xw2Z6CkV+q!#e$@c8rLi486Mv$fC=FqnAnaEF z_B>Et6p4xAE@l1laP%kKhW$@iC1SwvQGBL2iLwRC0{yN6Lsmtes8Dw1qql;`P(STT z7~#2u$(iYQ0GUAW^vcT2%{{6oeme!Lwk4WGkmRp8<`oCYs*B&-6p`{6)9EPN7P0QB z^+3_5B*to&)EnC}U%SFW`TM?53bboJmorNN{0O8Z?-XB1`SgqT%HBU$qk0#9X7f}L zG4TiMa%x>s4K;7MEn9*?WIs^1_7wI^B!QC;)gi{&;m|RjS6|X`j?z=;N61Q-a+!+_ zTs?M}Z!;@jUE&jfo?rjKLVxA=_0#~}>7B>*GYX(chE|yvgXedrOT)aTKBb7+n@1DH z-Z53_O_6&nP}6FLm#JuHSAyGI#er z9IV)l95&f1yQ0UVjhmEBgc=w{aOU`xn@(vpyUqrzgaywo7?71--+9FZzK+Y}lQ0}= zMc@5?Gjg-Z@i;H-xv(sYNHoW+FNO6~l4ZO+m3+_JA0LnP!?|xR;>&o!E9|-UwSfgP zO^VV?Y=+AMU2E9$fGIQ}VWEuz@mG}3(W`PD&&w#>34>#N%(edB`_x%bm{LqF)-XSU zAmFwk>HZm-`{3<;mwrc1_gNrc0wsQw?qxR7Opl?sNE})7BGMhvYeI#_xyhp#UYe(A zGO?u5nV;4C#D=542ri{{3;E)v^uyrJ^~Ph=OM&nTGrAhcanII*+g%*qh0+DyQ8TkR z0CpRC()bh&?s_ww$!*?IOerXVofn)Ou#Ppbx+|5TTV#Kxf-=uL@y&Q@jpemAETrqT zT}YT#lqA4st0i3__mT;pbG^3*>;Xh4ttk``}htFvGg9!cA-4f1)s;`Q4Y zBepnw8WPIok~=yiQ^TgP!Fo*W#!euS~0JQKD#6>pKv!oq~*bJktdme*Xo=Ftx6v#53SgUKTEyPc^ZHCa{< znmgfiv+8@Qb?*ZH(5y-OE~>wGltJEKP+48GdbUqk_mFVEDCpX(xyJJEUB*y$h8P6B zw@37*aK$M9^Owg9=~fV9!Z!}Smbcj@{fZ3SeKC%}?i!(df6Wh!?;DGUTf_5Vu=~cM za#x^Qk*4sW)V`BI8;`6q(^AY@~g%VD&mM%16P{X|FSzBukC6~nr`1tinmQ{X(FF@~1URBf1-rCyDj?4wIZ<&hePY(MmQO}r zR8gv=q;AQZ2{kJ=HPvM9@R4iTe8sZsa#Jo6GVbSeq;v?SPE5eIaILDtGwp9@aH{9W zq8lYLfS`|S!mu5QLi_V=E`X(eR+m6H2Drzq#NDK*u#j7ML{1I&{IJ-Go`Eert3L@H zy9+=6vpl3{S(vS9vMEi-MjCEO$D@U~jz_(c_5M8b7T0E4hzF6-5YdpDlUTK@H&hb3nEBL!B;zwrYm!BD*Zwg?`Vwa z=GW`jD-ryeSdMK|O9YV`UL3S|0yP85mdRY=lUHlup`QF(ae7o7i7{h(>Ce1__BX0A zSTLP15P^M&E1AsJm=oP5#N{GvkwCmrXQ6jkQ!h(0i_aXBM(JeW9h@0oM8rXzFx2KR zhkD_0h1EbH*YBsvIS`K-mE#wSvoQ}lbzLB);O_q1oS}9o$sKG|4|Q$>)vxn{m@tBNQ}YW2lCMWIgrq$}n>Q4Lo;0YZ zDPIjiDZx-fb@ZnOX#z#vZ^qR;%%k}TnEfh{r2J}A1?yGrpW1a`hrrvYi9@{-?#4jC z3CP!&LlIeYUW05-A+omoel#L+Z!0aO>Mdz59wtYZ|4ZBM#qigA@pBf_`OAPGMhtb@ zL52zu_2sq>Ehgc`>W{GcO3_aA`smSAk5Nynbo&%c)%qh{fQ93(R9L zYR)<8+(VW}_qN(@*|}?*uNZLe_-u1!EPLgD#MO=GDqiH$w3_829+kULhcS|9TaL4| zE_fN)+N&}BghH?( zs^!e$hAdWlidF{!mq~` zu+<(&3E@7kKBdYQ=%WjypKZxP-AEqvKkq2K7h>k8)vUEQtFPw_{!v}D;{szD8!XVU z&#%%-os^dga+VNAXUc^>coiu)BQ9>J=C(Bv^kRd|ig0M*7|H9Tc7807l3$X41vhsg zySmtJQ(A-3Dj0$NN9W#rd^E9<Ln8n3lDQgqU?ehHo8v#Jbt)^h@sjaaO zq~ZzTUT0mtZ)o#z9UQ+GFqL`_K>DhS3Rn)UR9`i=-m=7bB{Ar!V>SNV^C6flZqD?KjEvM&H;NXdjo|-6oXvuALIo(s zHpPgk{)F33&@vGk_Bod%Zsrx@tKqaD_&AJF6ae2`C11q3Y=xbIl_caJoKSE(vag=k z*(3_XVK?;Ci1Yo>$R5r-K_m3O@#7Tsa(E)3F98TACnqCk$ln=wr%W)w;i#VO{ecAXtCw zR)^tW2_kG6fLWf-Y7h!V@-+4JN@>~E7m4s_ZM>ml6OiPGdvftY+0zu`(T zzz82B3&cnt*A2w~>*&D0yH-eoL_DO+$vIneR(hFmMW_rylJ$38ct{_8YQQ*wl(q07 zulojx3`3OMR~AM{R=;yw_bU`Zd^)%V5t3F?tHi39c{AKP<@!m;0T{{`dAl*s(Qh_a z+(sqT&y-kbzd4PW*?Pa7VTYLMFgHmJJb%NbV6&O3hE99k1!-G#U|dXI0tyyW)AX0ha9G!*T~VZ_)hY9mY_;WQf4OBblBXj#NwNN*mgP(xBC@bhMRcPp zcRUaX$ODi<7MgcJVYx@YL9P9TWW`smM@xPh{yC-hp}WED!P1ako0HYAetk1(qjOr! zcj8w@bqd1Pw_8ms*#ovN8-H{EL<}jXG#ropX#_`@hG#;v+47Vi-_5Tg1Oh25Zvkl` zrxhRx2XgX%TDM&_B#RX+nsV+QpMku$WpBYVX>E1$M9!L(gOjkcxxMLoogzIx(!+bh z_)#YLL1+-b9H!Z^lFCP}g;0bNO4kecak%Q{6RGM^k(L&XeB23ZALXtxWw-rwnTNMy zl$!i0tFzQTFpgXK$7DXnwZPot1xRcR)D2OfyfPWLi@vQ|vWcpNC6(QA8I$T(QY9`B z!X)dhVS6}XG8b1-PwU}q4F6XvMUS)4xMfu8Dg?+bz>);B;cv6~rxyD+Be$-p#I>>L zyPF=U2M(5cZVPWm+ zrwy`Wm|(KPv&sITtfy~;ON+%i_gvynj%~lt0i}rr+gRi6+>>C7jfkT(ZhInzXypS<1lK0_yIRDN=YG z@6|uksrdB;JrUT}Cav3C@(k}v4Hrrxc!EsDBR0%v(Hc5eiHd{R5xhG8;0zjeJl-&V zR!mr;9Bi_=;IfB;i;Y{g)aWi9{)mVsU~}*HJ3T14dv3R9=UfRatAg6aNl~L=_+JeK zfaxeBnYX{3-R*0cV;?o%;D>}$v=M=rVZZmZrO6$K2}JJUfcUqbeXxT5+3O2gBhtv< z(8^>wG)cnZ{S6BK@W*koGbVzF0mh=6lu4zWj%x8#h+ID6`53w@csh;$mw{Db;+C6< z^88cDx0}DQxLm8NS*A<@RZY1PYPsm2U#2lp5Ile|AP*fKp#bcEO?D~J8xGkh{8miN z2Omc|c3*7726F5htPfqhnue3%^fXu+EPq8scgSe%YM31=8-PxIp)cg*zV=69R>7@q z@RXcfEm|oNqx?}Y0AnUG<#IcHtM{DWeQZ305DQtXuR5QEb@$X#6$+58HRk<8Y(!1y z^zVZaE0pmPzE+1n<7B8GkEY^GYh7#Ps5kN>NWH9d_ye#-|F)c8$hq_L^S_jBPb>S* z(>_dvxpIF~C4V1S+S!-XMmTaS#G;>rz;KnaRuaB zUR7me8GX7)yr5RtR-E5fZ33kt1Pj|ZK1N}t2_FP`;&}XjFKV7+h!s^YaLtI3k}MDF5(IYqRXSY zI6t4iWM!z=`)2$RZ2+w755x>k90wJhb>GTSK9sl}=J%))H+0}&E`$CWslg!YbT&eQ z-i7Mk2=gC&{@jTEblFrO7$A{gnLk;)W}BygOu(0$?4`B2808m>Z6`0fpf(u^F#XL# zd0ci+e*7R9!hf?;yuQR~{BJ>S5y*`QgH=GX&vz>QGpfY}meK*M%|my!9qU)uP+ZFo zFg+R7qI9)mU61*vCEJMSo`+4?*rDLeW+1m3^Ux)EcI(JWkJRWe-jH_>ZExaQ;Qj;e zDntO2e%hHIXm&qa6zK1B=ms}qoBwuPODLzhkw0<)kQ)n2|Jhy26A-iAcIQ)Q9^8M; zE3g6eYomFJ*l%zM1YFDm#Gl(NN}9W`zI$z56Kv?;dRB>C>j^zrMr>`*uzd;jKp7+5 zQV@pzk4Aa+8L3q@CL;NU{sobA-k|^3N4D)+FqGCsB)5&G=_)>yv8G2=lKvE z`}747x2Xmq@r(=1Z<>pQ&NX$U==?@V_J3(CwChq0c)jtJ|E$}p1lJvl@?QH7o#ET%sBavtW{Sc~ zPk(}RSJh}uDVomNxnd%?vuMcHG*RRA$-kI;Cq2BhC@0%?>y^JL+2(P;p>;WFF zmRuGVHLr*D80~dB(*+?+ieCd62zEW+B|?}eUDVhstq;{-0@nZd-JTuH0~SEZtNT!M z3aj~G!hBV=sv@5K=o&;O8!7hQxqGvAr_dpiu8)Q34o;W5*b{ zTHerq%_3Qi_2qdwxvcifk539LRym$3>015 zxg$lc&oLRADQ&;fGBh`>CuKKk^Hx@W2NQwd3+GCQ!PdlP#t;<@WM#xUPl8(m19Xku z0|Fw{z2B*y~i+yB}^{124YBL z{uFyAwjm-E)y+;E{_3WC`bb5*n}97nkpDZ1r!VRpJrJ*mZ&u6?HV{j?hKVoi|FQR0 zQFRB+mnaq_K!OB!ClK7-9fAacOK^90cMouaL$Kh%T|#hocY+0Xhxpsp}Jdr+is&`lI-c{Xn!h>92*czZ_e+6^nD9edUOK;c+D`W^*I#4)M;)771S5 zndt8)G;a_Fyi3lrG_-3^_bsMA8Tr|)Qx^sIc72La#A&^lX+t)YB=kKU|58Kra;+6*bGot2d-^@nLm#yFSI8WY^os{=H>K%{7_h_1zl|ho)3@jTGIXSIH7aMuFMa>*!R*%h<)q}rMDE$mA<%(4V1Tq(oF)Ytj5^)o$( zY@ekhC1w~!eUwAop8WQ7@N$+OltuY+umAjYSRQ_b;4N{^V~zSvNNC(h+fOg=ZqbhT zhiFvn5LgLUk;d!ICxhor2hT~!f~YI+{sZg%d!Hz0OM^-G7U zg)_vZeH^7Az~lo%xnXmmp)H5ZmuITPjN>WZ_r0eWU;cDtTR*X*DkPP^`hxk6RM*eq zk|4ELNics(ji;!|g}EZeqRf~wqN5W|g004#*7Y%|Z7bE7de50l{z7-TiGXloYZE>* znEGEED++y&coPPdGlR-2J-jymciF4K$iOK^qH?n6pn&*7R?pLSwnxZLuZ`McI~{SW zjy|A(wlBH8@A^8mNZor+JQxLz_0697?S$DUY;y&7sOmX-lY~V5A@po>Gjb`GL>r?$p!3ToPu6(YBjK(av3gBX1{O_lIf0m!> z8Sz0R{Cs%J%)#eW|xTvma4w!g~Ze@j*Gk3+eh3vad|j#EMRN~FGJ&6lJ@Sucz{OkT|TMCp8#?o{tR+j=)c+27=N1s`ET8U2Xd zH*~BRmdyU5&THObdtg^Ij6%LjL-_gil4Xsc)e!E`PD!-v4DOoDP_|e5M$u8;@hTg2 zAnlBC(eF`vtF|_#SYpuX9E~~pKb;gT?1ll-?j*C^5NN%L)6vFDJtOkj>p`?q;Ex$y zs2~?W19gV)z2dCyK8Xn5AkJsBnyViEaBhSDl;gdy)sb6|)Aj*0ru}TSF4XK9GN_2n z>3FIA5CE+)+b0~tTQ@tvE-(C7k&1!( z#J7>FLjLQwY@k0MT3!B__NvxT+rLt*)ONp26)>V$_1PY9Ba`sj%J$O|d-Q#UYFE_P zulXd98A=Swk|aD312^#2M@9W*<=7Nlohx)xm1}s_(PY)^JfoKE4t0VGuWv&fUqp%{ zutBi&>OXIi=WqT-gmE<`$AS7k5ffsst7*0Wo#r4{#3TML;B#K$#5P>~ljy$SXJX$D z@igNb>yp7G@5H;MSyk`h_kcdy~vl6c*aGA?#Tmg;S^K|e&PKB9M?G;)U&2t?cxfv$6cS=->C zTg>*42ss6tOFS3?w4o1B2pu~2E}>db(vp1IpIOo8@6`^27oD_{YTElhs3$osVsI7l zJYN~^6a}-toU1Qo_~^tkC^a9g(@}4#_$NDZJNJM4o}ni327j3bT7dC*O5OaQ_-{iG zc=M)8cQJTQz4gw*6?NFTmQ5AQxc#(q%P#p2v%MU%Fmu~Z;m|-+Os#mbwsdb+^=f-^ z-C=)dq^}-$dItO)&oVho!_=0ju}hr5*`<*s)X_P9GxY9#<;GA^_>`;eGIZ5?0@8RLttmQr;crb?S~}h;T6{ zn7h6&B}7&%0gP{`r+l4`vp~-9u+q)CikIj?4l!~h`{vfvkV&ls`!Av4n0L(X?#szZ zrTTNGkocbp4i*4vCR?oKmXa;9s_I2kc%`=k{%1Z^qR@0&x8pv2g?+zv^Dl+Z!BR6G!Ut*X-bWiE7q z87t!qi%_KTB?&W&fNX-#`ZDT{nVY#@f4D_Z9nRYfCZmtA2=cgG?1NI`wG0Z|IAg>E z?^(E?(5cc$SWH{>kDrk@$uNTsSVuvQ*tj798@?rxbXabSZn3~>@%h9s{5x#nL$fD{ zLzX#bZ_p&|LHNYDRLjptGDXs`=fA za&!ME%fgZ4XUxo_i*QIuHi*2;xH#U;Y-Gz5hl7hF9?skF;}_d@yE2)o+(cHT8;+x1 z%FhV7UFtXK6GBefxlboLrN1{HrH{r6h{bmCnaoGAR8`|Zz$|Al@-HUN zELt^x*Ie;!=QlgLm=108EeLiu45_lzBYCdGBSD0PtXGap{0Xz5_rA-qe>wvv;|{RhMe5T^=Iq2Uw^UF2hH_vFCPkn&-pt?d4_N<%+u1I_a52! zSSf2Oa%*R~$UA+0HdxG;XRf>H*rE!3h=GVSeW^|x__13ieKmsA9xDET;A=8v{m0Z< z0*@~0fI_|Ngv!uen?Kvg)9bVmuU?BOCkdCKHQuH$Ru;5R3-_RC-OiKx7@2rjeEIS` zV}xgEI$K6w8TmRoIv#(pYpX!q=ia=q)z9785Un^-Le9cyLXGO|U8IGcNJXOVgs2S6 zZ&)h$pj^_63}vKMqPFe8?43E=H1+IKGBl;;dk;+Ce-aX%Jl9Zllgu^ zC(B!+YR9@4F3aDl-k*1_$J1uV|M;VfNl?RO$FDr2q$MpaZ2V2i!H|~a4-Vasc8SON zx99JrryM=G^S$Fjkk!_nMx^1PshD9%{XQn( zSe)}KLrUBfH6=+910ebTN)mb{5CMt#^5^)9`OOc>FR~{i*M@2*fg9S>B5gYax|IQm(ZuJM}dS6qJ?w+yL$5 z_NANof161&477TXm-^nZ7SZdXzpW*0_zD+nTy&OVU!KZ%{+LWLULQ(h^eUt}jTt~6 zyw64X8@c>@!R4p*=k6jTHK#iHz9`w4jj0}<eCM63%24 zD&6u9d7qbHesS@~+Oacx{TKjvAk}CFIkwkDK>14q;hsx{nFU zS`fn54dGzEuGB9x{ZdANYNh3vTE|3r03&nCv8{xakcyCL`yM7BZP}YUR*RGAy((R@ z=T#Jm*Bq@-8r^$UHtqPq^AZgzmpM9IKctSP+vXGz8OqQ=*+SzOq_!=&d^2xMod`rC0X1Dd|>Pn;se0-7Elq&U9}*XY~7D#fJfG zJ|(mtPRdKSts+Jg>8la*N66L=Nk@Feg@S@pJGdLJ{D|RZcX*3THveuB#N9FCWAgYu zpP!Yzk^SUh-Pca-+E(8F51ZjmslP>U)wx9yb$W}4&Y$Sy_|0EyscyJk4_ zFDH=Te$=vGxLtaEZyue4#$;VAtny&uH@UyesH65FYAU3ZNhquxj&XQsPnM<>%^k;b z`$B1<-m{EK7qs+S+F;bh~@Gb9v2je^$U|h#k8t^y`;@Lcj zV(-jkHdg#72YfkiUq8wu3Z;K1(|*SC+RoP?Z@LSYwkV~iv%YlR3uc{GbNDJtL^nGZ zGO;x|R|#b;^e-+#!Ete#87@v9*}UqvBsLh0KwlI?bFMV6X8n4^<&UUo_b)%WTk_bt z&LW*f$A)i18*9XpP(ZJYd0m4DV#5s==2%<@$}=_=QFYDJR_krHPmqroTBEo|Bt4o; z(*lAqL)$g01kfKj!e{?)?ch$EaO9i;52el%7(zg=Jibc^X@vR|AdZ-UaN!K_yD2QbfoJK|F4ZUi`aJBX!uv%c#P}1fRgk{pXQF z-~>*1(~*Af%fcH9Ai>{=g}#DBf-qbe@DGNs;3Zo~pf4Mpg?cq$|3SbCV=J#?z*pb+j#haf1UW>&G9_ikD-T& zo$z0q#34Wb{8&^e^9bCW77KfY4s_k2xhNPrwamp1n+D<%jnmg6DkyHy6|i_7r{YVc zgyEayD%(_mPHS7e)j0D%kp%-5Ss8f}TMyj@1$h}1^bjFud)k;3C!w&%ExzwAd(k<6GNklos)P{NhasRNAsW(GJjbfrzJt3sAo^s=l7y4>K0}xID{RE!)04#a(j7o79a7;1#eeQ#2*I@ z4~LWr-o?j3xo*^^G0$g;*Cp0UhxZi$HbgT)p)-@Mb9RSNdZh6w0HW@F{-LG{+?_tZ)-<;=x{W|7b ze^%R@&@!Hz_X(3@?yXu3Zu;;8)fWGmOqSQ0#?YYQQ=R@S39Ws`SvC8gk5K^B9}kqR z-$J5NAj2qaT7r%2IWxp}|X3!|`*>z=p zi?q@??$+z!B!gUYazfPD6CP7r7)Y~B3hsMz`nO~}u(an2o!Lt)ElDJ%q-4k7dmYik zmr2RpVlMnIUJ0eb7hlqTAUVGOKh%40)NU2nB_vYX&3Cbq#;+yGdqWYMkH!o}kS2lp|eba9~+;qs>ufkjuKyXk!52?>cnmQmUlIHqI zPviI%UP)&Cl*uUu`TQx}@F4KGgW<@Ps1p8S5Gqaj8PWZt4rz2~%DoCK7MuBoQP5>f z@^(Q;$ente$q&4Z$W}T!W>!&4TI4to#?^ja>iP4=M1)AbEH*X^&&8pA^wAv*J{#4P7ehh9?^B&iT2qP5I(RpCanboKVSZWR2~HBnc%$vEcG zopIW^O65^|(#zhTJ5ifCV4G4iF#_43%=fx_z#0))U-#fLlLn6`E*4C;7v`&0oINLy z6{x7Z9#2$Z)FIJQ}p%=?2d`fyxj=QF@YVgUl*nPDNzNts(^7740 zjz$7k#^V-9)!TeS{%B^UR{7G$qc3YDWfHk3S+`42;0aWWZR_K%R-M5G%IqMuB>e&+ z`CzZ}NZP+gj_41C*qGoyhX^{uozG{-Hq!r);b5*dUqSD5)WA#~HTnFlyogA5F zH_RXGU#os^vN4-sb+T$xS-zf9WXhmjmN@@6(!AP6+ak)wruUq%TH?Oe1v{cCcacsL zm)*TXjLxm)n>1b;PX(qOkstwLz zb9~Yjn4yAi#-!y_DXFY@XRFJ3eZ%Io9C-&V>X1HQoI}OWB4$~`yM{WFYU}-Y=F%oU z>v|j&Wkc!7=yyLs14_dqX>%bdPPrb+ZoiwQ!Fg1uLu27)6r-VC6@h{ihJvFO>$D1% z>u>m&$2=-fz{60`wT$Ds#hFeZ*cfJ|sxL%AWVUFfZSl%P`$|XTW2;IricG)G!(Itu zR%vNz0M&fL3h-VwEx^i_*U4#e{kv7s?ew>#WxdzPpAS8$5BEEENSTn zS}Ct;^#mQ?KSpr;y=u|Zu&WGX1cdtd&d(0{Kiy7Yi<;Ttnl3#VZ#TDrWXS9+_Vo7w3EKe=Ya(Q#bJN4XX z?G_|Bh@ScSXjlHSa2;wJyxkX!>M`U@H!=!A_`|)^SX*1GHa z(W^?Kn+zi&dQ9?ox7(jXkHUX3hJq+#!!AVz$=@OlGY2SV7w9=H zf;kj}E24ls;7ES-Uh?{mWKymI4i0|xC9*$Ni-16Cnk^S~ba8_G4V}V|Zyjc#Tbn0l zQ1@@a0so0&-ILkG(xth89r*M05OU^5>Z83V2fzF9WR93yi@nu1u6M?h9fAqU_plxe z`C2+chieQ9Vg;C_{ydDB{vl=Hq=AAlLUfPWB7buq?kGO{B63O-wpV56yoSbLNUqS6 z{61-O4_0;Po3NKG3?YtU1l%Gt_%o|AK=>kWVAUbtG3F~v*NrQp^8_Qm$CAyL7^Z0V zG!yr3or&dP6kJtwehUtF;Z6a#R+&aL=rD4!m`#juilCu3=?oVbdmU2&ICCg4)wpv& zSEg=l%BM~bxJNm0mU)pv4?CQ7=H$qL-CUP|YgK7BnuTgq>A*NA8hNE3mOsUCH5uyhB$U8nxYRHoOGQ+#bJQv`<&w!&b({*f-so@mtJgwBG`>DwEAZW37a|`Wcmxg1Qc5(B&WP7Ko(eJ^&`{YBUyP;#0 z-gC^ta}dfIoJx2tLy4b~g=w%&03ft@`Z+ zKQ~!FD^5J^OxJseE4z-B#*q#xkV0TJ-LqtD_r12wi-;OnX{IcBUR>CG6MMcuUULDW zIzU!jvu2&5W{S{08z-O2oNB-i9@#rYJxHM(Q)d2}E9JJ^hb-ap`6Dfd=SG2y)vQ!f zRx{C0ZlU_E!*b6-v~M{wDAjlmsO8Prc|$6BR%e+u+>Gs*a+h%6WiSo$pW|lyK;s@B zCs<37^!`YjN2aX8j&|ivAjeAiS7RY^rN-m%SUrREn}%ywm+P=%l{uW&Mz&A3DUw#~ z3m*H*h7t+LDck1kR_d1B3Z>L`5t3igF*@CQy884y48>eCCMRbKc11C&XOG>lQn#V` zPnH9Jxd{4R1(r<f+y=ZHmis-0f6&iE?WPyRlWZ?$W;pjj9*TcoRJJ5G8j$wIEHT!2gPcHQzV)}N+7 zG7?rAZJs>t^Ou(ptgde@Rty$jH6# z_F7YoU_`b^$kdc;D9{zs;?QbA`N2xA@lbW2L-nfueoLaTe}=C{6m`Z-=&rCkqx!k& z=pv;;j*frg@cWZs8eCtG6l|1j0FbDiDCqY60eHNtf8I{b_EEZOGLK{EYkpoz6;BMh zy6kbjX}IiVos#Ym4&vM~h#HY>z=>%%Rl%pm2uALk^Yyx)Rq+_{&u?l~`yMM3Q-j6z zezH2Lew0pEyu{MBOkob6o9p91nTZ-~uE&s(`Sw?`8BvbC(F{5iEp`K32U@+v#;ep3 z;IRbD-B_Hgq*MNet7Mu|KA9&SKDFgO8O@QmS_h9Ij)Ut`$j5Z&c?Nl<5b}Tr=NRkT zN7GK~=n`q;fJefV$5Fps2F{39Dd5?^r#SmO%3$!>D>XGN6VT`z#5~iOMQlc`%=}tTN>jRp&Y7q=4vUEQnk;2h>=FY@ zB}4ZbKl%MZswvkCyM^`v6A=>upG)h`?-jJOlUSlCBs1!+dVA8B>MQG$GTPAFJS2oy z?$~;Wno9~l@o$Mu;xJ9*n-$2}xP)L$(@4F~)(_a)ydaZAfMjAhT&rYb(#v1B3gOG% zHizoF3Y9Um*gy#xto^%_&bHCMU^(qE|E$ecowsb%4w1kxyv91tA6%YpiPW}_VXW9 znwMKDED|m5PhKuuHt&4&SF6lw3NA$*OhAosAQ}eUCV#qo1x*b~qo92;DxW)|*eE6` z93NOfJwV@Jp5kw?62DG&HviGng%|cYWZ1=I>kju^Lm~3Jw%45%!-~8Dg=9(eBSX3h zw(UsM_3_H};%i$E%h~E4x`^_MaN9czdYh{95sfS(lQKnZvxIL1CFO}BB0)Fw9NF#{ zIjWYd@Rc4GWBjF4b~I;^t!Db8$XF`a9k)&@Y9DU)aRTE=9t$P#x(QIWNsgGzye|jM zZa(NV@R#k0$E_`E!}Xc8vf=H6;{*!kt*Ak3)I(5G<^sPbC=A&Gb)T3p+rrX1uIR2t zEUG@<%_IHoN({f-$_|ktL*hav#whQ&hG+ZFpsN2WA(5O+y-MVO2QO@0^HueAEvR*R zpM6bZwONFOX>Hk3QEG!}Or>o;@3!RfOvS_rwO4@p7e{>@s$#^)wiSi$|EM^1{~i;1 znIfV)+a=rAI4aEDxgX*Kf*K^9mkkMOVsKE4w!7emIGBn!X~`bBy<2M>*$I7nyn#P6 zH`dr%5YAegJXwoT|FwD{uj9@9V-Ewo^9++h zSK`sBSiBU--{5d=7GWJ_oV)(KiK|&fG+pRb6;sT3*c=my-q~qRF@Y`z#p%5EGmPRdYb2QW<9;MwRV%J(yAQahZPa}SHJ%39-&l;0NW3MD$+ z)blgP2M@U~7PRb@HrUY{swz+WIVoCcubr+4CkF5#+^peMWL528B3$#?u@y4$-T3bJ z^mFIBceIz!39k-s6Hr`8!^^I&>XYX`ZPy*d>Sok!mcnxUa$xy_QCeJ7khzR4q{0vk z9UHZ!)A<^VqkktQpx5jZhPLbDFN!-VOBbIMKX4g}tD7Wz2s<9h?dcbM5%U~K_U{x< zJ@nPxk}r>mq4=9R4sUj3j*gH_tt#Jdy>DfES$alBBWkacVPJsuPe^sa9lNXZp0?EHn+-46p!AyQ^WXgdtXX(Z*?{(B+B=Rary*D=Qg-zDVpiyK)(5~K0XwQ{t2QE#nJ z{I^(_&^yl{wAO+I%!+LIdyr^5vc)?UhRaXlZ#G0_Loh7RFFLKx=4&uwqxwSOQ6Z#K zNxH7k72?nP4$0yj$eRdH=U8I13==SSKqd?ek(;GR?y2_ZnqrymCp-v!Jzhp(DM0{u z{xoRzPx+XN=6H+iU*Y;4&IWvQKmAL6HZiC!6I1Rj+8{vvt*Zjc5BT}^8X6H)JTA6? z1h)Rgm=?kkr0e2PT|G$fpH~#2Vxu1Lwh$o+IRw|L(KvoIJ_@kkK-fKeUr$(ILyspCwTvxL4V zoUN)=48V6+hxs}zh3ln3ZBPuP+xa$4 zyVi(_hyAvthJ59xKfe!>}8a ziUOPR2>u`Ys?iH9NuBiUR-8<7ac_)=$7giBpAJ_fF>$`OxX7qJ)z$P65pLH=nC>#k z&EjOWFBlgB?^ri%=1LUu71?nhrcW#o%reMiKacO3Oq)=8@|8#RyzDX0me8PCQKq>-D zf>9$SZ=y`M7@ey;t(6L2K~frO3k#XVYdZpU!9i9|9h8ME)34RCHDi{7aN6pG3_o{w zJA+)U0&C(xKetmD6@z_!T0JZfC)Dbq?)!5{g^}(&fs&h15JTD-79wvW!18u@LdB}a z;p07NMZWW~J90MYZ=jwxJQ5B_h}`B=Ap=sw_44g{P^o}w{BB~l`&B|W$B@`6WAS$N z)O6LMB|`DnrU5eJmT#Lis`V)ItG*~)WVEzF`V@gWs=$Rt=+dZ^2WJdIoa{GDya-?} zu%%g#hqV=&RDC=CKWoJ~zu76Asg<=!f+2YdELhaVZ$S*41S@*%t6g#y-=xtP3H2Y2 z_O<1uQd#6Jbw)2PX{zGXwOU{1rm9I%Lk-?xh|5h@3{^#* z$>DPOLQ=_%TnOR%uc`>%6#wu6^16MP#9Im#hOQy0{KY-9-r6?#WF2YjSQVJ z>>rc-ufZGKAF*>lGCFUAWl%D&J~O&(Y*jMWI&&TTEiIuNWXvQ&W972cZZvmEoLr3m zR%{Hb&ZIY&PgQ8#^D|}q9hF@v0=TR zfcR+`dEC@|-8Rgm+U@pBDh)r#mcK3;i8PyyED_AiRr~&5TaB#j_E<0DQ+%pHh7zmx z=W+TfXSpG4b?Rb$iTS9w5HUGJo?V@)yPbT<_z*fjm&WR4)_ z2}4cmb2IfXMyt&lN3{T7b_tx=p>d>0H!!b31q`482xFX=Ptis@DO!A#D`vh4S@~ZD z&Ma~^A-DaW zzt3ISVwcmR+w-|{#KeIQ6JHgACt2Tiz&RNvMCSm_XYzFpkbLsB{-Cqp7YbjkiPhXz zqQj@HGOj?)dX|}S%-!}|C$zk#rV0~H;e%}oP*=7_o?gd6$kjD3x6wtx)>_0CN6KHi z-|%YY+R9i=d$_&(axS6P3c_MYcr;z)R?Vl+cmty`3l}7w-t7WCi}nvC3}&zJI6p=y zq<-s7ubh?H-9d1DQaY|Rvrk@>b<&}Q>aBY4=6V$yb<&G10G1*FsXzhWB|&{M;(p<0 z@p5(oPa?pDLX#v=mz8`|CGfA)c)Xf|0H0`&r`Y0*<8yUwBN0s(d2e@YVRJyxP9V){ zzq|g-_j`tI)_b$lch{!((qwm)sSOv8=H1C$!xesidXu-2G4X)#z|wf zR*dVu62~GRMAv*l_?1ogy{~=3N31qHf*nh}P$`i`Ob=N#s-=s0+jFB)9*>L;BEmX= z{&QEzcS=i~b6x4Yc+1>uaz{iQ6}lwpsBZ{*QGHp{-?lcZaz%bagdaVk`&WCioHr7p z_&rqmQt%+J1nvVs(G7bdjzd8-j3ta7TVGEqYw_1J>|JY(8(|pW^8q3vUm>55LUb>% zKO=89|4R#WCnceOQU6oj!HDh)N)y5Np~J+|on||Yr-_XmaY7lGqwvknp!VCs#ZAw{ zNmwy|YH*XpUmcK(n=>01?Y$4-TV3?(0ySPDwRM|XGG^lUv)KZL)jUt#g*PFu7~sL^ zRFXk$Y;xqD$AsSZ65=rWY<*W+8WB6mV0he^M_6-ewUpomSch9|0vQ;rNCOPsM?bH+ zz6;+X7dH{Md%04uurqnzCt*f?uaQ4a1ev8gcvh(nXA7V2dI>>1${YOZ`c^5-vE(LI znvGC*^CXBkff`Cm!j)2S#8{lT4>pQp>#`d2>#tQ1hkPvFz2nA>l#a-vc%>Th>7X4& zY8%hPG`1k}-L8}MB>U?_zje&m*rzGz0>q%8R-%D*WDp3n+ZyA7_RSRrJF3T;v#+<8 zcW!xdl3Z|So@YXPHpnU4+g0}k$M3m1)i)qRz2a$G@spqT%lM3l^5)*FrvVSkCSKwsF`xf2rh(ENKQR%lS6A_V+?Rur9-Mf>#C1{2y9%3<|BIpb562jjkh>gOf_suD2VxB|o(cEFh|Y;+R4I3}QLPE7Z&YtEkt6&xiM+T%v> zs;|e*C@KrmfRjh_w@*bHUG^_go666Z1ld*5a*BLm zU=W8Xfu1>=3_%|ZINa7K0Fb;`uJ(w3Ih^gC<~Tqqjx(T<&Mzq5J|hhDQLz*+;UPjZ z4X`SYd2_J^486dFT0sDF-g^wd19IVa*|>mN%&m}RumLC-3MApi=sy7rT;Sk0?fgLu zw&0xs%pne8eySNL!=}gsufgv6yP^BrpH;Yf_*iW;y89qb{No5U0EL>!wU;+<`g&dr zV~N4FL#H$N%RZ+4IA{m5co_$s_1}D#g8E~hqoRCMBRE3nfzH-1|J24?XqOdJizGVkh$Xa3nvuMYV zVcJI^#9=~umxct!hFBIDDh9h%Iuf|GS(+fx0VW#*Qy?0KQOpQU;(?{ic>NE?PXHUR zVtLZ?eF0pcjz)O}9ts7Q0gUDT)micYtgnNR5g{}L-BbWwCaF2t2cX)74GR3nJ~&`> zzZ=|%e%fff6@52>0QT#f02tlARi-$Uz8dcU(?PPU1I$xgx!2LLK&cwdor49zu^hm$mBUeQ(UzN051*Tz0PGll zFyL4iT$g?tbYG7?d5mu$c-g&xIW&gRTM~r<)uBTtDIR#J@Ol97Yko9p<1|KK#)VOo zf5GRD&@Yt0w`DtI*XjLsNaeWXW`-;<9I(LIh=-jF;Fu{C>l+q%;An^$xeS~=P>AwCKn``M=61gW*TOLT@BVF2255}jNTx}E zUwAz+K@buj1H;%9@97!_oY%N|kMly7*BP9u#AGB^B!oC5$h6J>l%PD&JO~YVc-R1r zA|LSlfwLDNbfl z&=$0{=|CnD1V*N_6tLi~*4WxnWv|mJ6uJiup209Rus93(Ct-cSX<(R$6bRfvF~A41 zPo6?v)x{40@puf82W$Wet{2!YSOp0V9bkc6we^prgLGTs0K2!!KLv72fuk_8*XFQb zyY5T?wkI)}>h*-NQR<S6%7j)9B; zwhH_Q!|?#Nuz{lW`(#jkCZ62iu_2JD1B0LO?==5%5rQ;f;(kM7Ar7FivjkyJIW7QQ zx%}~ukfHKmbn$SqZLokc5|x1$B5K$~0dysqw@Af+Xhccq z^BWvBxQ!hoZ(^f5vZ42czy{=FfQe1t=3c^q&5|-3gLx1d4Oawk4DO9}V>*z(ZkUo! z5NwtJ3*LO~#B+SWzR8cte318~?~1&IiNG&in4) z=0ytl%Z>Vflk)#2<^LZ^Y23ada5*}K7{3K`v}qwC;w9WELV9xgrH}Wsa3>oOctH2{ z*19xD%bT-y4?-Kmhyc;z{fZ7k0csf#8HW46tpA1r{CYr1EL8?9JuIAQ5h0bAFc8}I z_`|edsTE0p0HK&{u+(xQyE=RWB*Ecrjthh!_JNW7zza{afpNUbf=K=^;92zVDOvG?#Z0;s?bWdasV1>Y2a)T)tLdmD$^*Yh?gmJzaFv4h!2 z^4pKU&tR#QjqV8M5-7xIMS$hJIp??F0V-IA!cv3qHwoBoo`ILBI$&4y-8ejiV)OvJ zs|0+N);xqbl_&)Gs1Wf3wAOo!L^nuKfrCsE2T3N#9;6GPuq=Un)l9i=6g!lG6(`Wd z_{&5w;UAjoZ(vy6R3mvs4ESLHsoWp9#lC;>nJE^zL@jzwGygxl!3Ur+75ffrHOhaC zn^D=S!A>u^*m!twrLj5!yNz1_mT%p(!L(KI%FJNPztW7z@KQM#rx3*x0$TGoSb!N zCvpNXHvXMKz&-{RPHZ-Ct*UiOFCJHa8PW|yuqrCH>$U`zpTfdpi3NCV)|Jp9RQ6?LW}uOZd33*F3hYM%lGhRs)UR2VqMg>dx)$?K_8?iK|pngDQ<-Qzz>(W|8hH z;GPoD4oF4G%7#Vy444Ogj)8NFp(i}DK6Z>#mOc~wfgH$H7pMRPGXIm7x1vZMg)#m& zqYkN@a9>OM(eA9z9-XmC&C2XPF6`HqoKPh`cpt>~S+aiLL-I&jyvsiD`oGjgYC8pp&Lj%po^s}4W#7dm;O}djpL$_JJWxptYIMLOmpy8$kWeE*Cj9Lab7Hn z1RuUl591vj*Bf8w=n>Ag_qcRvdRae4UP15XlK1IK;L_e9nb_6+Tf?mDVWDEy2g4^(_Eg)kPcD0p{j z(Hrg~`mn|MsGMI;_ocXacQJm)SCHJ0G+EBE3|S=uWe-M$FEo6Wj$qnH(6 zG75?gA0wu=i9Q~sj;M+9#-HjR3${BOQ^+4K6UZIw;3*o*CC(i!369+nS~cEofFvaO ze%C7(ZU4C76!$wAJJJ8*eVIl>l1(D;p_oT*D1?G!ZQseKy+H6HyZ)j`pAY+N@B+Dl z@OFwJ?}KMg-m56gV${z@Wv%N_Yu{N_ZhJqjgW<$M z7EcdV>-N&AG)Dh%|AE8rqH%LLT}n1==jkl+mEyCtg86=~Tl)dQ$ydw9w1{%lJ}<-V zcmgj8gE+s$r1O}azb67|NdC} zg!uU}tXa^?{QkH0SDQMm<_}@T>?&R&9+vQUtkDvtKP2pgQ!$Fxn@nwp?BrHk)s+~0 z+!Xy>PuG8S`1&*z>SJWpT@8Ggn+rOPR?RAHUd3tQUY>O>ily5wP?1RTJgRWXZ6)t) za;RzhC{2L#D5Avh#Or#5Z-wW`dTYOu_0mGoU8imwp)XY;F{|cqz9nWy;(bz@jv76p z*<@rC-c&tJS|#31vzV}$WPx9-6h!T#gf%jNR_Lmh#CcXdY<7)UQc!TZq`2mDv!$rG zJc(+*(G+(wXdlsyn_jqir`^+*cIO0Rc|7hE^!*~6W`=xbZsmRXenNWT>J9@pB>5L?NZmr zUaATocBxgbme}L9DEZjB>{V~`6xmyzpX1X_n;t`@lY6a4(u^d(TjO@;3Zi%a^eo z@j1UwzmKwtIYwKL?@L{0R2-sK#@~=|x;(`kF*S?J`nkxt*{I8npsx9OoE|m2_4O=K zVN$j{cgvT-mrMH*3U`%5DASKIs%Z6tT9Z6D6huQMV^dvUO{a(7=D<{pE#r4}$0W#m zd#vBls`}lmx>ms}1s?UO0EJ6JLvAEiWqQp#ax{8lglZ<9-Id;||Q%cyU%LO*^6V!WPTLbXZR@P1-vU@7Ml4M@5mH+On(i6&0n z@L_N^ARu=3FTIoyEsKo%%=xr3SJ9bDfl6vh@~DQCna0j{G%IzG$RNEFt0?7@+p5pW zZBNY_A+L7`BSqChaZQBd-h8W?kJIk!FQxUN+l)qumYm$7Mq^bTl?yXCwt}Rz!MhK3 zgfCU~E5@A8s=hB1`=-5tY?yzDr~C)+NJ{x`txx6p=ys#V- zmCWgM#f1>7TYkbeS*+)|5%qR|@x4N3HnSGpK=R;elJ#Vu&Haxa%`erXAEVf5eUv{) z>`u7}@pv8IZn!aFs34l74^H1?2m3juU~Z)8}4$>vO3v; zcZq!OxPRP(o)qKcJ?BWreWiBOpIh93%yA1A*qE;mk9wT142S1?$fb>=?-;)|C1L3~ zXp;5ym9Mow4vw@m?+DVHabNTB9Zc%lqH0u2Xrn5`lGAA$)UrCzlR7@Q=$$U~ zR&=X2SG%UDFD6C2e+@i;EiZX*zwLdu;r2JkV{3gnCYK0(h}cW(Hn>`IA(EyZxmYvq zIEwpfcDG1~AA3-SmGfA`pur{deC(J{_wBvP?81_Pp?Bj?*1s_E-{jRb)v*(DdRe&y z7CpC3O^oOjFf94+@0Xxk7s$8kcvF%Sa^4ta`uzQXRg;p{j9KTAOReC0T){C>@v4rO z(Y-Bgxh&GjgnFkj(y7qNuET{Roa`2#ox>PTq`B?N@gqd_`HcZW111x5=fg0#{xLAtwpNayGV zX@m)kMw;LB`~Cj*x_|C@ZO`qw?e3oE-tW&HR9Zp~m%D{-wGPMJVM`BWAtBK@O~V_k zx{FoYee%{qj(__^gLSs9X-7fby_%Dw=(^g|j+ik{xkKB%YG`&+PiOXd9_^O%%CE+v zO-fhSNswdiYZ;Obx6>R z0!@VlQzel=sK_H5T+_oo*W;))Se_`h4YSX28py|*u^Q7B*T_rBF156QT}V)KfZw#? zjZvE@gB;@5ua}yb>f;%Xo0Im)k=ng0w_(*sNs})Et}4CXv-5 zL-_}K9Jnm_Urs1KUIJ2t25b~=en^waZw5^_%NYp!apKlLv8P#KzmHbfQ9|#Xr-bVd zB=yrk1#PlZlx3w`ZVJx29;HcoSDP&@uhhPcdbeA7x|4C#=KbuoJ$f;=q5jF=uNXCq zPv$%|(W_E?15}if)Q#-#f4!X5>cdld-~NL8_3lg^aZ!)=c+VFC{!z)VI=O&dhkTUGQ1gZDX$#hf ze!Sz>T%)D+-nWHW?dh+vI;XXnW{R2_f?Y#6RW!kyWs$PvJx}hJ?Ot^-_>#)zJ9_ar zKJdHR--i^jG@<3iV6Z^i4Y2sLRsG5Fx}%XJ1Y)-MGk6d*xohnVg0%CoU0Akbk(U?D z;F;hPk~)nj)}%cq1UDcxiYAdSL;{f!Fl8xIIC!cQq1_;T65UNaV$uyB#r~)}(UvHSEL+^E)y) ztkR{961|&V@MH_zEzZbh*t&D@O*AGxxsey8I>r#}tL;t`T&AM=f@)$&s;_6N(RVLt z;QAO%vFREYZ>62s@I=U_;awS{O`JiiWrPo4ul}Ttcd^xdQx>z|O4D6?|HvBvRF?WyVQi|WU_kqqM%D}S39c_3snUKs0_gB8r`M5EA^i)(cCbx zIo9goX|?v*A6M9wW&QFxyx1RI65LB83!j@NG6!oiG8RulgELgUeH?X+ zCdosBBaCR(^DIL^gK0|z40ge#bv;Uw3aK^iitD;gewHp(E4cCgr~PRrzOSs0gUu1( zB>n9g%aZWY{2BOg1*tLJU;=Vvy6T#WR>G6X`aFl+$+0sG|Lk#b6^O^qT`~{|)JUsV zPb0tTrzD$KN^o!WJ~roR2f9Zp8|a(G*ZgoyD2T1jGQWZdRB0Qk*2?d?P+Z^Og4F^I zqQlJh%n}G~R_WB4^yCfd#-|PJ!v~|bBBvZvl?E210qk6!e7|6w=IfnS?JT;Cb{tp} z64u_U+33*Hd`P3C62@QyW_SL=>?fQX#J>I+khD)hpBCZsVnJT^;y6aR}WO3Z*M1ta@H?4@>@^-{FOo%7Q(E%7##Mit@Hm;iRhM49M z6lu9{XWXt6=MHxS_k=#M{j64`p>E2ch|sC}@b!mf(HuQexUN|62l8WmiqbO{y0+O{ zp;nHk>=T1TwLktKohuBAMnmo8(!poMxR%A$(p8e%3N~-l&!i1!`gS`={hpg%j;=)) z?T;jp)oT%JhAY3Q_Z~qty0Ez3j|hK60rY9mo9fskh)Jz1S!C6&nbCPWI6+pjV&QP_ z)SwDMl9qN7%<_%u<2Vqw^loj`KhvDFes9XS&||$D4(6zf4>4W*M(=P~h&h6tSK)fB z$&vq@8e|+K@~tk2CnoY~(BKLoH3L&khA1hFDMxRQ8~Vuu4}Oe=fbj~JoOR`fXZ8G| zQ`jV7#uvM(kgcUtywqtDM(JQ-WJ?))OWJ|O$KP&}*KNW@p^IJ+s$<}fbBA4hA71|VafT(F@D zQ{R&c@adEd{N}8zI-9eoox1@nl)yg@sdG6`jsfZ@u2X<|@R#t3#DjWS3EXtqW)5Ub zP0`B?v9vPme(v+%D8&7>y9ptK7eS`5bmtoF$>Nm8=JH!cgA+=X2Mi@=Bq9=(*coRi zBdN-VpVVE?a%{2w(cj7JRzapdRuiEU@w#Qc9;S7ESvZ3Mmt5g524`+lwHgqQ991Pnt+ z3=w9SUU`QsSvqz@^miIlYA|v6%zbMev~}mNHz?J#y4Hss&l>#4yx-oTT9-1UxmnRScRbC0>;Jg%fZ{5{Uz%;OyL(xt>JjI>>`ZVys`yzM2VPW zvamuP@?!1YEO}B+YoMu~K6hPAED9q^w%SeUy$q#^YL z(vJD!-QDet91DWY9gP@`ZQA-rCsc{nw)abuUVP`4l*j7MZ)iAwnPwivgm?cp&HLtX z)IFPB_Tt@U%##YNUc#7;K!n4Ks3T5~qc3haFU1tQ98zzcO6N%9oA>)q3%O!qxWM{5 z9wND!s(W^O?+-E;)p^g2OEa|&79fR{0drYjl8=N|J0n~WHmba%30_mj98Uep;Zb0D z#E+BR!LNNG)C@*U<*e*c>}C9vn{GsE?uwD4Lcw=rQzuti^J_2FPrm}H}G%^Va0YN(40iA9O$Ur#@$G{XC6Dvq=ex1f> zAiXU5NwOjuSkuP6SD(bh;M6&&T82>XMz4HCCU)H++QcloxO1%#mp$PH&uS0>Ny*@^ zFxSgPCFPf9ghiL+i_!U}`RRX6ZxhmNXtSOsqf7KZtuKKwI^(e^#-=bBJ)_=XHF9X+z>fzk6A)Q!eDrx^rs9yOj35EG zP35M3c9)%O?81A1IsE;sLGD)qv1PXm%jCdV}n>DPcVa=quMXHB06T*y9$0&5%saHR)cd@R}I-c=Xt8 zL9N(@?AYNtzdNZYjm3UMGbuxFH$6RKa9IYQj_@c_KTE@c;fA4>l`leN26Wj)lb1J6 zBqt{9v3raT=Gh$_QRRU_C{AqxZmzTZwvw?J)8A)i^;b*vgV9X)Xl1mkpKg5kPVd@h z6%j&hLjn6NvHVF3%B6JmYq|5}M1Nz=M_eD;E7oLEA*{;lyt9H}=0PuuK9rv?tu3KD zXTYwDpg{2{*y?0gXhBZDPMY;x+Fc%01=t!6e0dJ=vYerCznp)LJY$7oifBG-a7v@` zOHG=Md{}rJ@+fa!^5xImlS%=hPA0kb#$AWf1$t6~R+UeSoz)APXdgoSiH!Q|{6RDC*07{7^&vgL<@tVWmy>;>)9=x26zR}A_R zH}iHHd>y?dmMg=Vs=1iLI=x$E%PA~662Fb(a~w=wW2I;%fK~Y$7wATm>O5}s_;^SR zhDZbh4lDFlC5zd8`?$9oP%@_Q`Itk~o#~$yPX!B2@=z8@pV^ySx^kc9+$vt7s4j%o z`uC2v=h#%kUniI59@t+!RBd_~%RAfR;R4O~2*ohAa&Mi3Wmq@qgk$H6U7^%v+Urgl zf)}tz!y&lK>0=x}9PTv9hh&^vUNcD9y<~d^e=RZXQKl0KvS~W&Cr2D#gcU!cd;?** zJ!(jda>K*X-MZyHbQVLQ0&wm93!w`@Qi!ev^H|Bz6{hK33@X|6re0MXFMzDZMp5DH zQ*ZTS!@2V2i*A1RwN{6Y#SBb>syaF!lZb|9*@Z41FrRUXR2lL!_2ti1V|;r;9j5@L zA&Y`0|AepU<9vT9z%6Fji!QN4s!Gok(J#osC~Xz6~IAtB{Llpg&p}h0xw=48J6K#`k+I1 z2*A-o2Vw_`4-AQvI{LkoI=(Rp)Ui z_kdCDfvTpOMZeR!uL1IQ6+jGi(@By?CU=R9Up81G|L{X05t{fGa!cd>1-2t-hIlP_Sv~5xsLg{t>LUM_b<(0)rzo+K^EMV* z6bvPt{|hd^{c*zFEY{=L&>l~mnN0p4;`XKo&1;9A*-^>UVDw^c{k3@Bf7klsuLQps zP)SDLIs;xx{Z&dZ?!7{vR)w?TZ+P(E(&x~nzZMo1oe@GykVf)jw0{Y%I{Iz@=gfbP z{2QyJ|0nY&eIQ5M8ldcko8vbpR}^ci4g#LbR#|S3O8$YT@4xGzPhpWlV|59Lyy=dr zWS$7nn*5i(x;;F286irhF|zYCg@s;oeqjNbq=F3D?^aeIx9{{aSs0`HrKYCeg7keErydNU_ayp1|9!}kK%8d{!Ac4W28m?T;AX0 z^hav-=o0wLoBv`}D8Vz&f`a_~{LIYuH=Lt6K}exz|9D~Oa;X``tAEEUSecuf>**!q zd%AfvGin!VSs04P=GN6sJMew^5lsC5YB+o{np>Tn{SkgF)f^dO8;)fE6y9eKv9p7j zx1tC5f4+gg;(PXNDA(x%s=0QZJqUY_%}InF_z&>MxR?Iw?S2GuvC%Dw-B_W#dXSc$ z-qM3|c#jaQJt*oqu!zQY}MMu9q*&h479B}u$?e1n43$pLK-|Bg`JGXp;@@%`DD#yaY z>iK!m&EbD|xl{f{*)vDrlCb&^hFDzicgePxDIoDa$ve2Xb2hzsd^5isHP7Ir@Id=s zjQ1y)TJhiysW9}lSg+%?xBnwep3Bj#ui9?*oC~=ofkHfmiAzgM=v_zDy-$2@3gQp>5SOmrwv2I~ zKs#>;j#Nq>rCDf_vd$__e;7kNEAKcQH%2RjRdmn)tBW_|5BsKQG7p9`+g_aW8MOEh z27m4AwMg6qYYa9pH8&+g^7JuA196WwR|+$fpnT z4WYck_1{{E=6BGWr-Rds+WddSNP0wA7#q118pv)o_U1(mk!X;tdS|@_ATHS)fs$K~ z=tXQw#FTb&Alp-mhC()nJMk(zor=GTe|X-_Rg%YEhFhe~d8WX=2x-t5Q888g6J?2U zAH^U(E&-*u-*J4t(m0+Gv{b~uvFgp^Dt-B}2D6a#NO3q-aNYJX?%Js9#gWy#3^GTH zs#*SBv@+d5j79Hf!B6SC)(T7I0XOy0hA1>7pEt|QTqigDyV3!wIBUw8Z^tBQmvED#r zOv@4+-WSN-_&z8jr?qCu_yk|!)U6#X2I#*&6;I2kXZj*dL03}qjUbBLApe@DI4KTa ztZGEdgpTyY;%(BjsCX~tsW)^be7Zem^H10GB zluQ^N8TJ~F{;ZpzSL=dCz@-TbvQj_%YW-Pe)+;>ERf15r!Ouj)~=Wt5v5f!QO92Z6HF=A}#W_=LGl!ZFtOo>9ue6 zMUnh}w0TU=d4{Ek34vjU;rEJ+Kv|v|d&szmUx1~em1x7(lQSjBsm4Dj2#$C2sp)gY zpaMMj)VriK>7EZZKB9P)HPDUKUy<2*5-NzE+5S2>-BjXXltzQA%_zcxLx?N#arj>N zi?5118}Lx(lXx=>(EKCGOY`>*OAvC;K z+~wb@Ja~Lxr+CV|ax@fQE8?m{1hqH%(745Z(XUq)@&A@W!%2IeopDR0;5F#qyP7{v pR^%URj%!lj>0jmQ&k-%a^_@>%dH{G6F9ier0?VjKmr1?}{2#4%=A-}s literal 0 HcmV?d00001 diff --git a/docs/src/tutorials/images/bar-plot-4.png b/docs/src/tutorials/images/bar-plot-4.png new file mode 100644 index 0000000000000000000000000000000000000000..50a9091fa706e56c3679ee3ef72c8b0994098ae3 GIT binary patch literal 31252 zcmYJ5W0W9Cu&CR%ZQHhO+qP}nw%yaVHEr8HZQFRWyZ7GrtLj8mMn+{s#)-_6Uqvd& ziNiu+K>+{&z)DJpC;=0AhCl%Ql^nnx0RFuIIxC3_0o2XloB;p`07!}ms(1ii z=7Rg6jP<;l*XO&GdnL_Px)UxXh=9*m!mOZ;!Io zv1G%QK_KWt5C%e7RpfZ_O0L@f^lkTW=hi_$k|gZ0<7-UIeNDSRyZL=&XX;WhGba}a z14#ft5&)6}fF$&L5cgx-0~L4wp9BEO0HOdXcoh&y;D26Ypo1(xVy5eY4Hf&ph6<4b z*8i&m00|=v;Gf&Sx?`mO3SuVhgbNe@M?wGyNc3M1 zfn5Os>o#F(b1c5-&89T$fkhMu@ng=mKp4tB4DA(jQH-Qld%4$jv=zfJGqA@0DlpCk zU`HAFg@uQAeQ`mJuovr#(p6JhT3S{%I=ZEXjBb=L5yjRk!-R$epGbQi=P7qB{eAf& zBnuC*SK#Wn02Ml@*<>avDjGR*z)f~IM@2_TIX*qjiU9*>TX-AWBMcl-`LF55DF84M z1_6#;RhdcHm$P55xfb&Mo*AZAC312G1qL=Y#>68N{)o<{9sZyjk z9p}++M3~qaTg)URV>axc&*Gp^$GD!PT-}%E!n$`J&5t`1@6aVi+`RvM(_euYkcf?A z9*ik&ys(DP3Z9r?Y`0sQmcUp#@avv48frji@3m8lBXi#(e*0;GL>tOtKW5nz{pU(3 zxS<3w>38PgLH>ijTJxGyA^3k$WJeCz-ZT^uJ+>*AEgAlU(y5JH1H>Du;;iKhe?_46xNuh{dym z2W02HG1Tf51}#kgU-WIk0bSEDD!BW8`s+p|Arcb;T}|14?^R3hHW|Po&V!^? zu;u9bS>~%XoSmjfkOfAB9QEbbbiz@C7#KLKlZAC%DKd-(ut9GA8k?D~t&tgiZ5-xc zAE`wWrWo2C05i_@KIafRwZ~KkOF}I=c8?xkM9z&i5?c9gesAl0>e+wCC{#JGGfJ+L zw|6tVD)dzH*Vj|GhNr1Kh%CYLF^&&gw_-&Zdw`FIR`rTzRd91xmQ|J3_ovIEPlTTK z#Y#_oCdZD0*CH=F!>e84OID!@Rrb}#|7{N)p5nvD+t{)Rg+ELT0}~Ce<`rd`Th~^t zsakE-mTr|0^M{w}Y9GHo8xfsk<#hRcWs+R~&4GWFXo82)TaRYC%j;7kv^JLKPYz~v z59s+yP7ZUc0NK%}K<1Xt!*YX=TQZQy9Sn5e$H_<17cb=eb88hH?(65!pXFAXV2@+7 z`VtuR_`jJZo1K2B=I#7-T@c9_1qmYufX6e)Zr>*)w1SmMp9ryW9aIa?wx}qA-dWrz zCmf?uCnB{` z|5H72t7QovhxCn!*nenbVC8KgXgGFvqV+ofC)#q- z(_WjB2M)%i*rBOpDL; z>HU%5des`T%w@X#*^b`EuxueHjqgsNq~Pke_vS<{)fK%d2A}ptm3yD3g6X_Ej30BJ zc0N$)TH8!oU*%EkW0YPa8Gx#qVU`e(N(b>CNT4txH)KRz3k{@WjYH7dxnZ!_P6*{Xq|_K%;h zZ2jp+u-Q<3#o3!_lOc&5($Z%Q=UeF$BLatQ>-@fUmeFso8NJT^(3Q`=S?^2cNM8nZ zfgi=!9$NqP6oa~Nk7uQP`R^CM%6B8vXUc3Nkd1}>);>;gwb%0?^XI71cWRt>?a*Fs z@%~&_5E3RvHnhq5yc3qH0SPjO7xI8`bqpYb3s@~9Fv7jo{IOKhV>$(+t=dRN`RK+A zV&KXcHf5SYrF}4{x zK-$~}$LYq>Y9ms&K({yIRhvVWjGeNH!NI|*EV_u2kwE4Z9t`Te#`YNt_0ikzlzlH* zd|_4ss=2g9e!`T85AtW@($x7&gjGG2>APQ~__Xk}F2tp=3Jn>0ZX;_TC&5nmVa3C1HMa`!(7;QJ2DV57)ve}%iw3^AgS~hjx z1qWe!A)VEDfBAjAyH=}B<)MIBB~N03Qx2rz-AfIU&*o+d#7y-pq48DuQEasY>jChZ z-4@6E^h6%KB#GXTN9sYaG9$|em-mfmtOhX^%Ixm`hWso>URWTL&&x?m!$9v}yRu99 zV|Fl2rfFeg6R`>_;$#=K9bBnv;_jhe@fA@nLxgu}LbA6=TmyAFrRCnTx9RLj#}nFg0z+ zU0f#j*Q+?WD;=yBvC`S_Ic^s*tVfE3FR21u_1Jc$;)hdj#l5Fgk;hJh5kaf^!gbLsHVqVDir zA0APlax&E!>d9X7ru40s4fJ}NShD+=n{W4xeSQ>SAaigjB{7o2E57^k*Pji1ILIpZ zUKt59^{w;1Zx7M?kPr$|89Bzf&1rc``@8(e`h@=A0B&*wq(HNU6Z)?C+ilaK+dCOc*e_KeVi?xFvs9p=Nq|inZ*C_#@b=qcrt%W-HQR3Kl1)`_y=T&W&>^zY`=JApvVevne^H6}@RnD5Nvd{1YUlA-f-uAULVUIl{#{r9$TQIo#rQFz)c1UC_t;XNf* zCS0QF&LCd$Ox-)|NPPl)faUg#FdX=9K^VYfve@SH8$d3eyegMwnP+C{f04BnU=hH` zcvqWaorT=?3)D8edv&>Kgmx(y30z;+SOb$RAhTMt>;WASmULtfzrsl@_!E%;7O)M% zx+sVbi85IL`JEnqoRq#rdyw^XUOQ20u+ffNjy$3^1paT-0N)@+I3_${uZ0f8E(vBAbC8NtoM4UmB!}QL+keb2a z-8I-7q0?j@(O>g$E4S?ghoM3!BhWo*yP;*j2OX%4J~j=R+(%s<&0ER(L>7#7^Zj`e&(wzZQAX$^8@WV8CLgCZK@R|aF{9JI|u4;4^ zms4eUhxIj z^*?VUJu5^QhR^+&+i2~se-V(DbU62gE)VfwK!=lG)pdH_Yd{L)M`5x);wdfTX`68V z&WY4}zmMm8A5Hb!%KbQD9v&*nZuWf+?!lg^i{2CNni?E>Th4m)sAO<*iW&z&OKb*b z@-46cg3cxpNqn5Vj@$Kmdmg5bG1(>2^WImU6N0ldI>~rc_v`)q2){D(dHy^Y2yLP> znUwHY0Ta?QjcRZ%faLv2rfjC51O=IIlgR(MD1xv^R7-!JiRJ^PSDn$zG;GV+p(_q% zC#|n?7*Yur7+VDX5r7>{9N+eaYqPV+d6@SsC<5Oi%+RjxNgJXKbG^J5CdoCgCIMELS$B4wNRozh`oD!_piI zSTQJx*`Q+>fBk?K0I=58k@_G;;b?pHnmHKX!yE?9@zRIljsc?`b9HOIsw*Z~GSR?3 zBqThBhsX17QxuW$o3)ql(jfbeta*~}sX1F!77#F>!<4v*#k2ha5tceuLm?Y{+xSHq zP9bc-Wx?>77nXy&yTow8{$*C9hjoRUN7Ln1n(jGK6w1U&T-!OuAfS~8-mC~NEnr)U z$$JQuxV|+H94%lIsFWEqdu)2Xz-Y#zRS%qsvSg>7h3%z7!I(hqukH1&Mv-|WLLkr-Fm9u+*x)`5JQE}0tQnAtX z=F4b1$IQ>{DPt25A7HkE2|XkneCXr++z3&JqD7EEk+fpB)cf@28Bq0-{W$!t=|l&Y zb~_nNKBJG1_xhBkGj=4{y-@{+bRrEN3ckQvJB-6NkN)0C>94PRaIfMiA;e&5Tj+^A z*prd#(XxE&hZ{KVyim5>)IKU84+WA3n3!3OcL!#yiodH)hyH3(idSzi9PrLdZYJ{f zs?e#X;An!J&Yxrp2zvAzW!`y31pLPTMMCyxc_?z~!O#ZL0DxOZm=nWqn6{jMONorQ zrxp!XEb;5`T||uA1V{f8HLdZq@>c*nKmVe%NU(AeX=iHqeZ11dk;%Q1oDO_p!kof< zR-{iEQS!`h^dTVf1pbIVPv}MO@oQa`NJo;8&t;{vqhcP52-v8Jqqh>%Z(rQUlSVD zji0{v9r@h}m`hIkx*BX}2&m7y{2o@iDWnQ0P5a@DHTp>{-)A+g_20dZrTU)_W8+S; z@&50|)P|TDoA^x?U%Q$3kH@xIHwwK^D)hTgO5IhPUQzxxmO#%BSzEE&c;L>(_^0po zPjLHFGRca?XwoD|63R)t0!%1VpRtK1wYx0TOZOpj+V1@O<2><+a%-H>6Z= zxBh)+W>aYi)LwRt2^jY~-X~e(lS9G)SYmf^^+kEQ$cKdNnA6ny9z-i!+ti8Uo6bM) zmLFC2+vsN_ye^nn5AHsmjg($jP1LBox}N|Y03nPlxx06kbx>|a0hy=sH$UkqkR<2S zUF`U^cog10nH3Mkq1>$D>a&;dSbK}&IKHfP9b-mVmn*rmFfkwwJ}@U~3+c}GvppoJwZ>TTClPXj877V&P1d10C~I2XytGI6TSKUvS; zJaPPil;R7u^YuKd!Zq12IMvvLoU5xd;-||I3hl5Lb+|lRXBX87k^LCX=Toe^no`L6 zQ21>TlQ~$&wjEDr%T>@P(iQEL%!7wu!IBgvkAO{O4SP0_G@20d*?3ozk9_UDVS~=4 zw%z?Dm5_o+e2ZYl_Qvz;)!Qh4!jCivARktzXE?~F1x~agGdY{ijw)fb*k5Mfw|Bys zI`iPk68h&v@%-4YUUsQpb3-^m%PGm_FM9)%pvNs7FNfk^hxBl>d3<^$$|tt-ltoVq zLZgQY5l72DYrAG#@5!fhnwXZs>x}tgrc&lXg1De=&Pk#g7y7_>L5x-TvBKUaGOG=+x8GTeQP0EX+5!JXvUBR^Gds*nCjc>sjG{ z3Uplzmf=g>?`~2xJBulGJ{-2O>FKs#rJTbHGOO_=5Z&jrl)Mjpp;>{}mY#Y_wW3m% zqfXT;X<_F`Z0K!OPfca_NLnJPzqPmAi&AIDXl5Ia$1nOX1#4 zX!P(=OJH<)T(7(1l%JD{FbdaI7EYNJ0i+W4JGmQ(vd@3w!JbO;J){MK31g~~yIuEe z*rA2a`dMfuEs@(ges<)VnlMO_xOI4YzH@A%vncC2UY@3+xmNG+r!E_8#M{5sl&AwB zYd_#==I`xeB9l^XygZXfzVIj2vaI?&eD6RZE7BR$L;BjzlzU6}yA@@0y4iLLVRev9 zzQjIQndzclY()2Rs_rZ=TXVL2N^-p~zvX!vU?qyl`M#>Yj$IN~b?PbkJXZ7jnPpyg zbU#R)5G(SLFkDqK?Tqp7m&Iba*Kv2NW#rG7j%a18zh47-s8ZO?R0{jt{#4HR~-WT1!`$9-DQyCbI*9ExS11p@*vyQsZkT zBD0MfE5s4|llbawNpH%*2Ihs#j{=`BuhqBFg|V#%JlZDgOFI-OCl~A@-B@UL_NN@v;M^=Is34@ zCg;Qb&vKIzlooU=Zf&G~H<9T=O^Uq~^jn?!FthwdCq0@CD~=}r>IzqNIsIM#jS#o@ zUbVy&t``RxpfqGKy&OX(ob~T|lffU&{EZ^!)R$9t)@I(O`dp9q1QAXP2H99yc(kfj zvytyP^-pAHN4wc!ao*7Ntd&1P^UQv8jPF;VpYSVpeNIm3&v@uBm}g)9@LgSukR&UJpyl z=B6-_gM$u+Jwa=D#V9VTxxANglJSZ&5Xvcf_Hse7Vk)0D?k02Zn9D*z#>R9jZ>6A; z&bjMzd*%?CT&0FXmYSb0M5k22vtS|^BUEN;ooa_av&X!72RIu@NvzD&r5PY&f93Q( zxkmako#_iB%j4~oed(OP0-WpQNL*js0#HlKJaVb6VpZ9~}Xo{*e%o&q2gwN=+D_!7vZ)%w0tCZ4|qTzwgn0&G6*_b(gZeXtsY|m>p5SecF z^_Z^R+r`0|(}l!?IzN6(?>v$vn`SH0*f?a$`y!v3_=}Ris!}hnUngsNtd+bQ%jGIH z#>8>y5oNJ&O_fGOcfzJlj&p8K5F$k^x-cEX-Sr!o-^&Q^q6No-sf*P$*0vS~UFsn! z9$%5?8GIDxWxo2OG430ZrLf3de{J^Kwb8U3Bl|uU*LExE$?YW6oPCY-D)#jdbcDeA z<|?V^g^sv?iz9bu6WZ1!{akBfLL&g2%kHre-dP_U zIs3o=uVj~A z-rN{Xbs)EGQiY9e$` z2t%PT30|Ewjt8?#+Il8Dx)Bqm3mHaCr`L#4Bq~HVwFn6kAh@u+tZ8gbPxB1*TzW%i zfb5hBLnCk9#M`G(Q(J=`Vgp;)jimhT6M@QDX7C9Y$--4M|>)zhq@{e5_cnNZ$J)dQND1T?j9C~X$5a1bCEPbLCqLu`UF}=w?N}|J?bp0x^;wNcx$bs97i=T z@Th~Ym<5&mMI|SsW20a%VNYTdbkJU=Kti{x`8qh*Rj#9XS(Sl%%8+sJ6_VQBAm)I& zV_?>Pq)ZlTl63%GTb#aJNJ6?Jii8wFv_yv}D&!1F+155Y;PyOP+e0eUU8SN~etb&W zMW_1kz7-XhcP+Ei;jCEGNxmVYHup+`EN$UX*H&8Q1y)9ytTg?wQgVv7=*)<7x(!49 z5L2F?al9%y)Z{>Jl!dHgpZO0M$q zM&&rt?r%(Z+GulvYPy~?!t7_Wy7P~5DaEx>PI=1pH{#`SV+7516g(kJ@w4`P21LyB z`l=0|3`?b4F%*PigUdlU@8}6LpNnnMLIc@g(c!t0aeiOkY+B{kz5H28w_exoEK0|W zIJTQ{FJb5Xv$1u}wwX0}|I_VN`#l^p!werBs}{^qEMm3TFs!@IpEh$2;C_b^hDbH&P& zi&;h*v2e{bR<6f>dh9clE+nm!N2q38B2%D;AsyI68cXmrMiX)L0zIzH>q9mo0xrHW z%?v0tWyH8hmWiIeUQdGoVCEE;;N))E_R5JK^HI)xe;`;(@_a$^*9@`enUS3;jrOr>O zOy_ayjvmxryQAAsZ8i!!#B*i_FR{{Ag8!vbF<@HEO4>l5G`WT1=hnhR?iItVIG2^Grn%KNbC?HYT(Xnt(QT<9OAiRAkHU4vmR|K<%OC%escqY@j5@f zF}+R)I*mwD02~EB476;SA4InM_NEbB%NwgQLn_|~|KUB;vJU&3@!z(_=glFo<#;Z?`MKB%-M$&(hX`5ES9klq zxtHL}P3Tpu4s`GI@!Q{s9;ZxU`m(u z)~J`E;FPhZ=0RKBPa-ewb6{DVNslyLjW$l#k9oxa4EC;W3gD>CCUf;P{8iJ(S!XZ* zEX|CfZaP%Ik3bBeYw551^q{VZ{)GY@w-l!Po)IMECbChJnlmOPGx^z+5lX_O>j(Tk z|BAA5+Ox>M8n_URIO;VXjIR+Z{Gk3cI$TylBRi_65!E%oqMOsxVpz%n1`+JYJZ8*+ zA>w>_#2?Z1NG992))#k_mgi8u@B1=|R$MWo90WRYPcAjaph6|`*4B8~h6=(MCu29e z&>oV4v5eC#Q2hkr$>TCy|7rYd-(N;^Rrc#jTn>AK9V;CiLPOh@3<%eRJD7xNAEPup zgIX=wT;IK_jhC4K8zs_l$z=Ca+_PxkwZC#@a`Td@K)vA-U3%xy9{v6N>?J%?8a|e> zI3o?tc|5FMtQiOwgc3a)>La`|vA`==LsPy4uUWvdTv*+(iAs8JjS?QHkbebDQ#O_; z%!zi!_|8Wf@)6lij|-F6xmzNq@#h{*|L*6K0{C#uunT9-a%rog^sn5?FKPw~B;)w% z6_FGsE~Z$)_kPe2Cpaq|DwBytZmagx({W1x_u9=>(ZTvCh9(U=Q%^p-T0G=`xKiGD z^OZ1*OwT$ub4iCfV6R@Li@V${GNB96b})0JKmiH6{TtMj)HG(On_*{!>6eI^2xwxV z57P8kb!W38XVLXbbP+LE@R&ifHNj)nbJCOier^7>kf<2hr}UTBjo*HklZEHiTpm*R zHy#Qc19nPAhy`7pTMU!>D0xVb>Nv~&S>^@#N8^xl{w^O!y4-Tr8Dxu}m*n}f&2p^` z(cZ`MPj`7N&S^4MV9COQxUwE8a^S(80acv0$KMyN6JJJ1K%!u&162VU#4=W5-T|c& zO+s+^Dw@LUePpxECY4X`!KNcq)8gLGJugr(X_G@VFj*)U<_hqqr+}ir$c2-}uA`uH zQGnmtQ{}fWt+Ra7cp}}_??m$jG5T+4_37b&xTOmPyyxa>>ZpgNo9u_Mklsj#o4t~;BLWg znQ~s{>Sncj^fsuIEF#oPS~~hPwO%I80Q=!u#jcRVX5Hye3K^npY=(PN5h1xzr_{1g zbeoMu&g9c7(s>%T+$3%_%C&b*<+5Y0K-|B#Niga+f)qo`&?=ty=HV%yPKDz|1$c)q zftMv4P^0o67ls<*wjA_GX-JT;_(fyZKRb2cRI*E;memHXIsUJ1b-$^%(Ew`UV8>?wATqsl$7{}f4r5LBfj4Dnde7#V4ELQc`<+AsYcTg^TmhsfB@)QG!aPP1hP0I+?QRI-1+B!+jy;@)f|(T_5g2 z1FmN@MkH9m6t(+@zL=VmJ7I4M{Vd&JW;a&5x9CIqneclG@SawK$=cPI{5O5cn_|((P z%|#1Mg`trB9tElmM^Jh2S|%gfQ2^R-t$vTgOb(}|A6+VjvTKX&?;EP{=v3Fwya}kD zP|o_^K_>lCjc}DU|*EXiFgEWCQM$)pKS|O~`5s1oxGZ zM$+y*_n}wlbFz8O#rFDnQxamTxIOiTRB%0ij5Congz<9#%zxTbFUpzwFlHL6lMFfa zHe@bN<)q7N^nq!05p2Wfv$no(ejNs)P7LtHWQnn&I%37_(A#oW4`j&1O#G&#0p)+9mKq4^K)$X$dUubvHhSsc=ed}LYRog`BZv*;f$!82vIKQU8 zd?qS3u{X_T)ziOQp69qe4E_>LTi4P+RrN@N2Eje8*H>rs^Bnc&{_Yi;t?E$7@;sQ` z`7{&$b;Pxgv>Eq{;8-Va33{WXe)s*ZgTY^7#rNoG_wahhx2UEM+2(9M7$i&-zy>~H z=gmStV!(>H0X_sU1an}YpTj!DnR9D)mfvz^i&B}XseJe?noIim$JX2PdpwBmApL79 z-tY1DQ^3_3A5m`WH6%z9mKwL+mK?2;K!Skcb&d7S)BoBw+}8T!wM7B?l=VIVoP1||$&GXDEF{3_YoL)LvO zo<;H?8FKHag~1hX&Fd6xV)BY8^(BR1<9BNlC%C*l{v??@mjfE2ySUU(K5^`u7ZU1W zZt_un@?pbZvdZeJ-%AZwn@lZf_n5i-)c7q_@DW1@jdZMm6umufNqFmNZ~KB|2$GVs zuV$#)>3(t(2^eC4DC12Y$eI%v01$)BmaM!F1)93E#i zr-HdPtsL!TuWJiYY*OglPNR{}R{*J97dLslk&UHkvUZ-#?(4YcBxgT4NC!A`A-pf+ zgG~OkHF?zag*)>EOZ((U|MIW1KIr%K$Ex2`w3Y@@!K0f>olm#M?0(!UoGUusd$~R3 z@@-{P3KC@7FFFRUiqQNtlrh@^vo&&tI(>5~on?zD7!((O$2}9mSr%}nI>+xh! zAoCCGe4HJ1jg{(D>L3&yuI44zQ=HYA;nd4cF3hhgxK%4bi3C>j%-9#z8?Xlj5~L4# zAWx~la@=VhK^xWQ5?st(ko8T~EE@Xe;+4{>St4!~c0HqUH3@?{`n%$w5~NLdyBJ#Y zGN7WtxpB0^%)X9R(}6@8UxiZiZ8Q*p;i&B*QFH-9Ll%UCor~!#cV|j{m*ja-)pm3D zpGAY13L8m7zl_3Y2d)LqX0JJOyZy3KJk096m|B_|dKU?vA&poeCqc(b${tUgt(nq8 zrI6D3kMz}7ty93o-WIq)3^3J;l+R>t_wLuOV{{-D6hozwA+0x+*lQlERw9Cm zQ+4Q*A(hiaR`6&{QPLS0Tx`5J-Vf8g;oHiFn9gHZXTw#|m!IEda!{a*dkdaa{JIj- zHgu8F7QjC%Uq?PlrTqb|U5V4b@0X@zRjc$8br=V{NOkz1@n_=(QUX!kk(J zwVt=8EKZ%5&;E4k_rY*1A8Ats)gTA!40wnDu?zS6`QYxg#ZlN4iWMee-n{1?K}Pr@ zn2Ed4SqVUHRlswLJ+yt!TGGNWILIG_VXie0p+q0Ke*D~bEixwR_2{O;-sVyDPDz)o zKF;^`2@qhd)U8Pce*nm!d8{6HKb;8gAT~e&+FsjMScQx7=5=-=zu7KUZ(5} z##2>M)!S_Jff_ddWVs=CY?*{lCX#2EV?={uy3f@ z^pjYN3RHG>Cmr6pk0Ks2O>v7Y0$K7niEpSjKlj)YMKUZ=s)+0^FV~wjMsEJx4{tp%84uY}%%=GI%x@aa;oA24%oYk5dE_09s#f zSXFNU1Sq@9Ae!~Wkd}+ICorVS;HC)CXHMF!!g&sSo0WQ<1bh;NuMn~iji-Ux-Fc)h zc~!udi4tTb^?w@?vBbcm*I!$%V`7yHdH zBD=a;rqVz_Ngb839$-inA! z8GxaEx9`hJI>X-+%|2UAqFplu&3;={K5 z%{$PCPR;~x1gYQ&W2VMds0&==sB?}xS<$r#40r@SL^(=b+iz9p_d|#Yax<-%iq4Xx z?KD%gabcb}mx0#!ua(;T8U&|wkdkoEidi=_<}hiqRo@>{Kt;G2l8uIY$T#vIFTh?N zf~0UVjg@_Ys_P)&AD{PBAMJPb__V&Hfl~_9a<)w%+xo#`$+w0p$=he{?2o4jOI-Ua z4;o?^8};5q#h3*sxmBOTwjYGg$2Mn^XJb%w@lT|IN2&As9qxy1e*yT4$r(-=QO$*Y zd=ds9Acuhg`rSJT#Rs`>5UAjWiTe;qU&fo{SsTJBQz)s($?oUUfjVeD{4k15jGB;+`$7qIef?RVUZtt6bOA8!ivpA1a5IH^=q8ui>@xi7 z?a{;(n78mblf*IT1&skzizQ@Kqb?$!!Vuk5$^=2|9tavf8tbwY9s^}L<2xhfX48bR z_%b_lQwzEdw?vv9{5E(5N4dYoqh93*j64dAQOVL-nLNRq`(cZ8@d8O)U>}9_y}3zF zkM4YYeF+mT9QpGKOG`tGNP2($K-m2jD=aU^xE@1*E>DoAOCO?i0Wo2Gd=xhY#WArea>a*EqugSEovWj&A#YS{z(goVoiV zj1?bmoLwxib7++==YKczUFc^B%_;iu!cwuNt0mJJm=doQZQl7xub_-1q8W~Vdry+w zTwh=EfyPG(DH;ss$sB3J&8_XZ9n4B}ZgvsUD9Es1YGGr2W)klCy}JvoXTcPrCGtH% z;9;ttU)I!A8}3qY8N7SpgxBwt#@fteu*)u$Mi6{18+{;K8%DRVjfj(Jy$be5d4gS>gO~wa<$@kI_ zenL8s=P6ZBN6mdVP~PFDwA+oo0>S^7%z5WTSh~8Z_kFz0dt%>9GZhIECJ(Se)?YMP zP(YBbxxU$MT5xc^kPrhMqI|mvq-%z6M1b2w)Zw4|`y(#q-}%vl?)4n4}RcM^>$Bb?C=kWoWo>ibU;knpQH{y`lC9qF)^E2}#s zASo~KDdOe=)~7)RgNt6f(}*aD~yU zz~9=}MXgLU$jemS1HY1neVTteex}V*B ze67$b&;PYlNLxo1br}L&3i62Ys+ywa&HIqf5)l|379B1SEj~;G4$N=GfP=t8^bh@E z#}9aY?Bk9fNEKt~aOlE%yH)wzd8MzB&E%J3D}c{W#4Y6oObV9dTm@M^ArNfa39XLg z9(5i~tj;6;FJBUvNCrR*$9@{)GC-`3$371X|7_e3W}Ij^H|jX_?!n2$el#itlMcR& zV(y0VFE@d~4RRe+e%=7=>>vpy%=`O8OQ0!mgtYCgBG7f-4!2Du)Ou!(e-ND_SKCOl z?+Pc7@ITyzK@TFpS7@v!*|}SSC+`Ski4Ims#$W{0tc^LtaP)V>LASIbAb&`;N>xLB z*BVL4m&J_jKJ_|0W6a4Ur0KubZe4)viPbL;MsH_skGyb2E86i}Fj%!Nr4 zSJAb8gd$X;??YpDLAP^z(D328U-}kizF7nWR}i2}KwAO&oJ4q2$IGhk6^(A(y+2*N zy-i&(;A}VrZ%ZZs;{FdmV+ypzrnF`cgg3*VR*JjY7)aGmL4WD}#?e>HiSyR52pt>& z#T3tKX^Wxj1!g&^usYmMgAV*3Y-2!!5YWZb%^upd>KwmhELhbg4@NL07*_0jLEv!t z2t-U#?KCj2sR9vr+dy0^4QU%FBfZrOnu>u<`uHyL!L>=j z(7n2b!wmf&0u;C@574U2sdBCs=_q%efGq!iPXI|Az#%zi&bI{>e_8z(#{a@XFu4F! zjKb!jl>hf;AfpICY6jyHCI!@gL-p5U2A99I5lk*>naIBZ^-oleA^^5Qd)sHn{Fw+77PxMCj@v5@5+(en$+aVpyGCT7I`e=##hy3* zUc35wjF^7_67V^+4}dZWQmiBIu^zBfeTzE_ojncmKP-&HEK>WBRLp<@05Bv6#*|nK z{GK;j0ZFVQEEvSrf9lUg<3j%>ajseaRbL4*XV5PIBn3l(S;v3tVC^f^w0jOh3Kj)u zJXMRzcHVKrO7NoPE&c^G2=y za_mAWhg-mw^%ZU@+=$V?P+|0icy{$t$!As%gTFmCFmLa457+j$S{!cG`fn4BSkq2Ei_E^JSZ zpc0(Y2tfw})PEq*C{rL~;c$$p00s8{_XQhdyFRk8G_kVLX;slhkGqnS;H58ixp$h~ zu2Zi(2bI&@Xa8D=KxN^vm1f)ed%N5uu`#w76G&^XQ%#pN9H6B1^_38MRm!u5shv&;Fdsj^rjptzLKRpChZKWQwGs)XEA;?PQ zrJx^cr8k>xFB#jFx#^$>=DQ|Y_&EuO+v<8s(qLUA7M3zz-5t+Ls^0v_T~79wu4s*q zhYi@~Ln!~H35R+lhw9;s(s^mK85`yl9eUOsL5`RDDLE(xMe!YcTGFrxGP8-~m*BUn`RA4QU)$AT2|N9`kzc{1QQx9PHo2i z^JBG0!$6o6eH4CwGbi2IWi|MA5-*sMRq4!HJvcbJjL^lh7R!2-mfb_pKQty1`+VVA zwK$|p-DXagr-%h5LwAI5RPvv@G^`-KOZ86k1Nt>75Q^xcDj&mXafSxiY z-HW(mJp7~Zx8MZ@QBg2i>8&)KQ(9_831k8y1cs#kOj@nEEN-il0XxU#;z~wy$f*r+ zhc5xlo*A*pl|Sn*1*%dP-?raU*o}^)77QxdJKR67Sz1pAi<*F$*lankBk9vh**0aA zvq66kEoH@ZKaJ8kpSR&_r|x01nd=%{!+BQ33(P5cJ<4>f4*@Sgg9K-;;v9xQ>OjaV z-w(8k34*gEUcqJ2qb!2;l$Oq!0e7F2$;(?rrgZSUUrwvA5pH`;AN*Lz7+iN}7Z z5pjOTP?Jzd7+vigGSp*6ivQG(AuthfS{ik*b60(F?M^(`3?}9@rzsY<0y^2Igk4*Thsz7n-6on$9g88 zA+wN#`VCvkBPsn`_PqB!g&sMfnIBvKdu;fz@uG5Nm+J6@6*rwV_oBHp4+nVAckMo7 zy*lEM*z>_t*NbEJE6)cBUc}LXl7zek)tfH| zf7)qmBX3Hs->C`3A5g>JWJU1*GxZ4r4P%PuxVY^89w6!=74PNSuFz?=vzz#$#@_AS z7>tIEJ6UXrS~HM9Mk*#*ts5JDh1Q1ZiV&b7NSQWaG!Zx`%kBGklat`P_~`42r*w6} z?W-5P!cIi_qpPzaJR%2G;7zB0CX+cHQ((q;mnseU7awvn#OV7o@m;LSr+kxNX99Zf zfNFudjMHc}blJaCMcgIl21@T?;I;C;-jeOCO@YV&E^6XICjgTb*n8wde5#tz^x{Q6 ze)|MDLv&bBOYoIST?&||F5j<(Y4)WchE#05U{$Mzc`dvcbn4powmnn;dB#;m?|bjh zZM8)eKBw=pmb3g~Y4Lo{fYgh>TVv{?zz4BUikd~*P%!X&3AL-Q1B=7fu`KDgFJ>@; zacEM8RPVYfp=_VM+F?+pps;%JCHX&F9JD7Iv0l43p-s zWzTsuYw#4Z^Oqfk&TM&tId#;Qdthdy!ib0ziQdlUk|uw?-vSgGw~Ufjc6@(#?PQzI z@2_!Nnl*tI$>@M=g%lbprLyXNbE+*5T~P3)l^;tQDf3u%vYsrq?QD1%GsDof-9x}V zu?(;?6`BBAF9F6kl$C}f@qkM?TeOz0!aq4h^VK}GrhIj7We6w|V5*yB@RRGov`8x+qT^Ic=UwQisRN`PrTw(3+m7fox zBf_jXon@plc z_vOi>pbPz6lq<6s4KZZW?OjoqL!!KNAu_sr(|oY+|63QWAv-WaZHOZy$8@c@ zLZ);!gr*lk94#(iX35GY2CQUF^T?Wp`pF$^ow2BTceJm0>kX!WB2(&ixF%pLgGUN0DP{BIHR(J+w+79)DU zh8daXYeUrAzoewOY9*KdD4_MG4vI_ofHpSg;;KknSTU)}8wrV5G1G` zs$z7yWtylBUj<}d9}xmt8&cnSjAMbkQB%c)U@G=p)2QpY5s_(qOd4sWLLNJRv;4DiR51*@clC50V;?~5Z@W|XLl(!KCOQN#g3y-zyE z8uFHUw+z3)Z3wQZnjs0lBdeAC*eY`7h#&H=fJ$>m2T> z&xQ10_y$8CZI^Hj?YWXLYYq9dtfJ ztGReN+5-8nJ1^w^FyZHinu4t!!D53L=mV{P6AUt=-J`!X0AYcz2dU z&b@ll3dExu(iFTdq*9Z>R4G?1^ni&<*oi}6yBS#0H?LDgR9@x2y;j>qViVwyLr9=r zKT_q{tFNy7mFc615LFVz-z}eP$-a+LpmBD^+g1z|1Rg))HxISCJ*kXC0}>wYAaU*Q ze23g#ty}%Z<}rB7>P zPn2TqIO5w%QK=ahNEeUZk>u*GCjSFvR@=-9TK8ni=yCxIsZsF=&^R_(lwm zH!zCSw2uNprFnGnddP{*pHvq;Ap9v>-vih0E3)4KfhK2an=diMuqUhL=5(Ds(5=s= zEn(o2lQ_FYlyPUnpN%(E2UvtqJu$`SRKy>Rakor%EYz9BQ|JT0nOdm|BdI0LBgS95 zyH-|KWS1Te@U|yK!qGVQZFNy*Txg8z!bN!g?w`U3Af_+NyiKb7R+0EG7?IR}k0VTD z{N4c%E$EMtKR|`KRhsq)lt1+(?ks=V7bf22v_wV8e>yfrcu%v8OJ@uNSpK7-|ZJO;R(~MtxbNA|EJvo7Hs-8td@@4 ze^;JJ=PljaSK6BNZ%0yU*jJ?fUseCDNW!fEOB5uUeEE+p^GU+K;zJ?N`?s=*Rf_lY zDRjm?0({Tt@01~+&}8un2`M47no8{5!eSUdnHG-t+67E7A72|V{GIbTiZ@v+kD`&V z2g2-Ud#tYgqfA)c6#J14cB;Q|s1jM&76G$;etuq1Ok4Yz3CKs-N0=h2YvB{*!8I%D zgD#l9(uX#1LI_zNQ#c3zk+}Xxl%~Ng#c+?nxHuv+b}aXqWZ#GA_=4*iDh5gM`Y3-! z_tB<#5KC->mGSd&OVc(^NR}UtP0oHQfjN!=@#lY(2SP(ARi9a!wB`3}*4Y%H)baSW zFxKC!BA{Y0Gt_GHSIm`PAFu?v>#J&Y?d}-4HWa9#)LE;F4@@TWWac|^h*~cCgSA-6 zgx<}*L2rpG>OTAtvS6x>u8_>UQ`Y-2UJP(L4_O=1J~_S-5vUYp{5YOq`o@VTu%YzV z)j{3dcLEs$fv56TLbDb@7)cFw6_LQzM9Yj>sv?&Cnj#&m;1oQ{$~frUywm33!cz=t zGTG*0HuUSif(~!$dqU5`QeO=UM<0Dm_H|$xy;;xx=B>QdZr8{<*Pd z_xF#WSAjjDFgiXuI->||_z{8A@CEF0it&jjVLTx-^N~#`SB8mXuI{$^2*|44JJd(_xwN~K(ic=vI86XDG()S8LH|?4xpVj{Bp(F5*am&Mnozb` z7l2+5BRQqi5L88{kkVs9C0b9?wRe(TGYy2X}~!+jVSsjy>r8nGLXSU}GVPaxROb$f`%>wn1GmlMmikZ(h^H7|T`SpvyCeA0yxnu7F z!d{UHP*VDjPMkJ6x3T(gEfQbJ7#SOfy(w0t{_Coi6CSu14C=EZKYGa<)Q3AVGsBFJ zR?WS^gdc1>+ceGQZqCFltqGb4&05N)nFT#uY%`RI zR7WXbKI&`YLPA(-d4`An>7QQ+kl$+=^cqjQ#?+=oq|cy$Q|t>7Vu(#>;r9@C+NDOV zDuX%zfwj-U5+zHu8O(}juCA_0+J3cB|LLqFs27HDk)Fz$tb4PJQfaQ%zZ=)dfI8C+ zK=3xZ9G^oRbog!Z$D8W)&gV8{EolBIR0^?TR?1MR#+G?hhb#GepaKSJG|DHz<+lcF zI&U`q;`SL+L{=66P%FDO^`^EHS5!t6sTb3&(p?9A-gn`7!iRk7{lA5gco07Yk=O*9X?1{ z%ga+B7jn~C#;e>&oeZ`;IM4lXOhgM0ralJ}O{=@#wM`#wkK1lqW%M3^PYW-$z2DMu zWRKCF1;_Aw>;vv%=gwTWr@DU8)vUJ55JgG3ze0j-gqQ)b)@f%cxa4-*yTImh0P82B zbxTy-x$+BA@Yi7pLeI@<-B!cK2R!V%g#xv1nNiu^QX{w+Ci2lQ*NAd!6LE?J&thZI zO=?@-LqFia#Yf?jNVBlZFo_v8ic zMnK6BJRO5ovM6~_Edi-1moTI~$;~z_TH^V;{^_^+4lID&r^AC?{AI1#9ap91_({?X z0!Fce?iwfh8K}%<*Ap}zj zjf|?;qW$*PViO^0 z$YQv7K@aeY?K=$%1*yCWvV(z=(GG~T<7l(_uLQ>Q5Ul*snJzG{N4&Lf9$C&xAQ4k3F?kh=}Q< zUVa|rXE<9GF-{4wH;!PYc{q>*3|RfKsd3 z!wSVXGMxIQh~(BP%e4G-X65tg4+7-h z6f}_z{&W-b!VbyW4zf_LO zC*tcD@?PyGT6?#%!){N|`Q55j5lzjLB6WiF4?R3(`VPVD4Qs>8@cSK-} zir9n}nG+XFPeX;gIY|=7oq8ye^un>~imG`Xe%e+X6Qx{s<6}zqgxp!TZ&g{9uNvPd zD6d}jkDZpst#JFY>$@zqzJ00}|B{MF>%E)%bcN+(ri-k2i}KgbHoRR3|KayOAac(n zf7H|4izyN^T({arBE43z)R$3nEb9cg|FBQa-k|vUxZ59HV)H4QH0U5m>c&$J?|iMh zXKdD#&^-LYE|pe*kFq_hmn|Utq-WpY%7-`H-ZnZ2EMTA}fVRofkMagt1xp9Jc0vkz zHfb;7dMu7;hEcS(pPFX$J2J|cMLt1HIK^6UUNg1cM+_~7m`VRC` zq^Ue;R~lVQq+_nJeR+vrQ}crmLGPCq%K|FQZOY{Me_uWQJQAJz&F4h6^b-nyUwKRa z`3MIg8=(u@mc99-34MC1QB&NJmiF`_FGtL)AD`X2dp8IPM{BB8U)F@sy)ivzFEaza&krMLvPnjl;a}pBE z{fDd0AEbHgzz%knkLO>zqf%H1h$Yq#nN~`yy<9k^9N(EGQ4<=n4-8`ILUa0k?%v&( zxYeuWJXTcmqmG-k8EGx@XVFO$8j*~e=(*9DFHT1Bp)`w}^NbeBW%r~*bWNc&AHz$> z+2PCypr^-q>T%BR35n7iec*7HnkUXC8xB5QN=2L-QmBudC`ln{w!Gfc>iuF9uzk12 ztsI%g_eUE%7A**mVZ}e~g_~wQ5h28FMH=%v$)KJ4QlK*=h$o_;p--H|7(PBe6v38q zviA0+iuCv>R%5<{3~t3sLq{JtBXfhUma*%bdRthxNyWukb9mfafAg{(Y)-Kf5Vwud=n-M zp!IY5!9J;Oti7HLa&L%hGbS5V-819i!-pd+9+K-3%G|F)i8-H+2e+!ES(_WHl9}{` zx;=5#dKU0|^VP$^3hupaS-b|%!=-X5bs_ttl=7sCOY8~b0I25GP^M(y! z2N}dvb}TBkD;;o7rKs0btSkkbK0;>%>|T)m^Be>S!{~-{5HIgTfa(pY zlzSzE5fJ%>+COOk97L)H+5nC`jIGy!&dc0rG2nw~ApIX~0K!J3vI-(0?<4bIco#(C z(XN36m-5UsviJg8h*&^{UB#*1{7w6r0xdCrT$2{h2 zuiQ)`!f_1na3jNUL=1`nkNhzofiQIui;X&DhR1nqmn-^B8H#X!o(fgSSCO-;nBsK2H~wWhbVFLJbQD6nm9FfyrU7#cL{ zDnBwZ(l+e(rLn20six-Sm7sVS)?G_|apW{TgDcO*f%Tr=-qEjb8_6W>Kpo4rwX>}1 z8~&p-GO@j#eo3`_t2^JTFJ}%qp9sTtx0Wl+9|f*}eDm9~q6k+;`rlC9oyD0qciZBtx^<~#q6Pdu$ysf>?_wNu z*W(7Hq@|^`5+z-_V%tCUB~^j!*XHIB`PtJ`lz1ozX^|FwaY--GZ6qrgF{N9ezypPA!jDU27EcfNO3X@uST2Vnzb19<~nW!JS_ z>d@?ci`mHY|vBiRG+S?Zv2DT#XgsF$y4Rv-mk&*-#WHKb`S= z(`i+kvmDYK!#F1|j}^ZQE(IaSy_j^bDqi>H5m~j}ITI(NP#tcY#SL<;JqCdvP7#d< zBUF%buv@_l(!jQEq3ghNmn`0`X0Wzu{!~%i>3g1&=8WAzHK)T_g%~Td+arJ1h2}9s zU|RIp)%rZdtZVZ6=WbHlT7&RuOcc3}8FrZ7ab_i{0KvL88u@#^%?ZLvh{x?3s(2h7 zbY>LHcdug{aD{ME6tRFku?0V{VUEZpy~`;#HTEE@L|lk)fLl)mljp@ydk$iOphO9u zLr;9IXUy!3ndU`}x!j_i5!0&E>!X1|qb0S7)y>3{{33o=mgg~%NvSb>Y>ViWo1GhYTxx1Yw314HrzR(oO-)=}E=LH6=eGSWJMk0( zT8?QbpI;o=3e-ONmBySmmTvtTE%>;J)>RzWFi-L4JD)c(CCjO`xpQpfI@9x$@8@$K zbL@`!xFPw4%B3iZd360uPF0!C$YT-ZrE?s6E>xZ7pl%j9t#)IR(J6g!z0G^OSls-D zY_jB~>v8+Q{8j#kqjzuTIc_e#zgkvbc?CBt6j>>Xeko8&4YPKwrH=G%Cr_gvOU!Y7 z)GmMr*mJSaN!@&frfh3LNilZIefbr?Xk$v(7S|hQ$2HXT_6|7RugA4{8)mjwVr&W0 zt)27)Zk!gHTTcto*f2(u_%CAU3X#(%=%8<9>`4EZC_0oUXozjLa0YYQUV^-7u)1by zu1jxF-ExY|Wfj{JCp)jmbi$g3f$P2UP>u0)rm9mEU6DB$=B8;# z&(QSIJTQJH!0nG;@V_@m*t?WECkJ5rH%UrMU&eMK^vJ*zy86?&7Dm7#Ya0 z>f)_Y5ff0m`2`;i`0PQe%#lePna9yIcHtOz{|2X~Lr45sUis&8zPXt50`xu_5@&y}Fg-SqfgL9-lf__X>)zd1$^e zgOUoC_Dr*b_BRKWysCFnEScWtE=P83zQ@zzx099u6&&SorpGndXXQ?iF%Ft+hFP$? z##I|&s_OkNWScqPA750SFUuC|8MmBokjzQIO8&U(kXe1EO*)jIV1 z+FbcoI}hMOM)T49naJj%?3%!-Nf`a+=i|jH+A-o$3b^v@h~wqa zHZXU0`)r3Cd_Oqg2H~W2sH~R+utY@v zn|4-ke}4*Jl)eGkQ%YJ|bac4ZCBb)%s4cwEGjS-5oB79{)88HHCJa<)5)|2QC@ZaZ zkSdX{cFcM}&i#i!IEa?{%{>gDh(H9;0R#jD0D54&>g97Kna2&VPmlx$(mJk!JI#QPxq1|2pF^QQ=8QhrL>H{sFnq4jZnz_5jv zu!dNRdgItX)5s6L4^d6{FvKd0z>*?|0yv;A#)5kvWg>lC4#(O=E2+S025cZ4+p=52 z5b+2tNXHuoZ~Ak_pZF~8EhU>q-7!Y_Kcc2SZj}CDgq=V53F-|T;t1cNAW+^5C`z*u z;l2MrplYHP&n7?ti2V2igPKs-gPxc@6anEd8^J}%jwoy3ujPY7g)E~9R{`ij6mhB= z#eavv`LAsLYd8Nr4FBIbia5pDYNeCBJuk09eibsh`^dqJPw#SltS!h$3#g<{m_m~^ z$XshzgL_~oQxi3LJ`uT9?(PW9n9#k#&!&#jU3T0|@g*TOz^Oouzow2GW zpj?Z6n}46N@NO!*ivS_ku$sGosA(H)cmxQ-fw^#$HexOm06 z4hJb|r>P-rn?KYb{s~1hO7+0F6e#&{(D2y|ONXvYmI%2^Pb4|H>$i^w)rQxx1in9# zKR@HpvFz|X_S~2Y5g4ei11=j+OvdE-%30TYerM$=liz3|rd?`MxJPmE_^@=_cOh+2 z*)y9jb#t8`CSoTvdCU)In|p3-Sc?(+AYK=n`sOQhbypb`tD7hv37N`&?F-#VGW5*b zA1_x5)y$D97nGGbIGn3fO?VEE;8kBl9XZ&4rjbrfP4t;Vq1?ZHn2dANCrJmrO2)Y^ z!F76i>nGLg9DzJr0fUyfDrQqg7VF)mqK2pj7KTqs?KMt5~V_3 z$1(PzhDz##$+bmC`>_}u3o(_qTOmh{UbaQ0l@}}Vu`@f^D{QE`7p;vDi)TWhp#n8E zwO2bG(r`?K+>ZXX5qfs*2^olskIzwmnhbU3C##&^u?ux!d8YSy+7`W+v18Qo3z?4f z7bTg~gY<&fn3UXs;T?4Hke-Su?RqQ%M=XW*YXzZN7MYyp;ICnxDnqGzE<4;@4Rdqe zMjrM#JMrW~)kbbNU8m2MGWEsaxauv;3k3dP5C z5b1sCAvWeHAs>iZ6kf3@m8^eJPbazD%q> zhC31t23VRWzUwi7{8Ruowvu4c1kt;SQEtb%zK4%b3JQ5mExk&bRrJ*L0LszAG#^=H4*!c`fU>^zib9>27Gka^M0(IfaKCuQ|veTw$p9 zz?T&jh8|rjjMW2!1Un0Xw<wB)hB!CR7#8oGWl|>9O3FWNz9-%k;Lk3wlS7jV~ z3wSP6i5Rr?7wwa=jXx_+CmUypQoW_e+yd0?IC7)3(4eqM1TNQ1S{U`f^Dx-^#3jE? znjzU4Gszc(N}58jvCsLr2pXe*lu%JQupF9%{(LG7{g2F8VaRxcA@HK?>mV6DJlEXR zLb#AboVpJAPb}=60@bIa6b_6~%=nYA;)bbl=7;14mx!zg4?_U_cq7ArJDwzNsW(m( zu|)1&DjS7@J^n!yxa(tPbrKGTobW4&djbNZtFi`G2%KZJr~u^{Lq7Nxmhkj(@gO6X zMPh-~mM>r3=iP)CD(+??RKH@vGgzuL_(PB~Uo=r0Gn(02hz15B1*yWhI5+?{mFdE) zg|bL*pF*1f0H?{2xn#%7w`@!%>OF3x_+gksTq$$^9KzDGJK;0tboBUN8Z2g5bZ6m^i!cbsfy{bX7Spvb%zbbGNGL=60a0@rH%6PT4!p1QmadJ>uT=iWpaW^$d)dGJGiz8m<}jzG=T!G5c}tD1dTWoCUaL~%c3^MIvLM$S zbK}@@J&lH8e9#-Xa`aL{N!l!OXXiLzcPwrnt*}WLGLhu9*3V3@6`!V%d+JoK%00V{ zjy1zz+GY05x~bebXm)-*QSa(GCZa9OIRu4lo~XZo&NPr{Q?TVF)adBv(8%tpT(c%;6bbBF2aPTGqoQ%MzJOqm+l z!Pm5AT~8F%_}e>=^Nd4~?CWi#(1BZf39apct#kFg5!CiE$DM0#&+8t&>$ROyOlg+c zXI(YR7@Co8BE(T~tcmBcdsl z7O_;$TY(N$fC_#%DVP?@WD-w5KW1E=HCvZ=EPrX{6_ST_rID4CO|zD3--YY3lXyCx z`-^zrgbW(5-0VMANB%Dkc?YnU#Ln8h=x!y9Tq1cl@W+vl1nXyn~t! zJ4)kvFg^5#a%#+G!2>)T@m}qykmCVUcfvNuJ=;)Tvt(adMvTP09EQ4dK zj#Cx&i~BNcf|ncZ1d9UuNInlK?_8^C7}pKK6annCP`p9p{dQC|OT_vrf7>XPY12?< zeeIH&>1aZ+Lr)~?>k^VenWyK8tMZo3pc&)kBwLgvWZqQ$Rc@%*q|0V*sgSi{7+SQK zv+Zhg!NbqHcyUP8=E@d8bEEXu2<7i495?oKj>dLh$9G-##wLad&s~!lf60_0#baMB z#MicLeJ8uvZ61s%7`U1(0eW4`6_+${Lfk|*Q)?xdY##cYvo!GlLFB{B*Iv~`+>I@jA*C+n5Q_`6ILRyXOX}%RHEbPS{wWHFf=eaMp75V{Gdc49UWPO$wN518CNkndSvxx38 zR&=|h0jmqEP?ghVUK@K(Kq+1!l`ZH7ixemVM1L)W{fAlBlwS6!Ems|6D=)fxra;bgHT4)D81Axd%ULT8@*5#je|(U`at zM={bn>N0ksGKh1;sO|EMKTAzYb3OJ(%hBb=h>pyhhNrAO_Ou`JoLW0HXae59o7J&X zaFXgW%eW{~J&zf4DsL`+WqsLS7E__BJh3}G5~sTtZnRFoa^fYhP;o`j=$7%Kj4$sj zBM#gD(Mu|#Y1OMMY;1q|rgvtiK*-QKTAt(AtNo1bPIlvuEH`5MsdtGo*G!&b^}Bm! z6m*OmEtfQ>VuP4N&Kz+__lO>G`e@gD`^@%B;bI@)^jTB3yoF+bzYIJEui1g zd8MkOG>dK=WNo{kZWo;(yD-00hsPZD>KW!LOW0s!Lx5Apdp@$M?4qx_C1lf%%gFKj zO3BtYmnOG5h4XwhhnKfkl0t*KcttnUOt%NAo_wYbOobFbY>>|!A_whBmx+=GQ|_Qk z5U$Mz{|CptGYI5MCR;>^V14D!j98*PF=-ta6YaM0{VqDH)3B>)#T>)#VF_K=-MK4J z4AV>WitXE*RQ`-dOj{zK--oJ(E`|>&yLr&Nx9RC@Vp>nDy?d53Z75 z75L^Pcg%cz&+V`+3%#Yw^dnn@mmfp*=L(MDOTJAlcQoRt?zdCq zU78Vx2&G2e;!><{QsDMmg)!50qg}2bTjtpL30viRA?wdsw0Pui_Pd9;&zvdRCZgA2qlxF7;LD?O)e%l_Q0P<{-t4@vgod-xRpf(p1`9 z-YTsS)UDxCN?$Df>IQs1S_}KPd-lPzxyvOTI!WXkR~NbGYxCJ$SMMgOgPX44KyQMP zf#C_G94zgLLoi5QHa=gI#x~?Ogaq!ypye!xUoqfQdReMQ%Xi&6C$DfhNNqlAP<+!; z16s8rApT`#PL6U&JH%2|<%WRvhfR?1vUwie=e;X?+K@j)f7DX9Ng^bFM#b)j$eX$G zJe^9$A*|ZScL@6@nyYr=udMS<4?jz})PeU~=&1K3jFtcv$Qk;Mb}68;178cCfZzEf zg0zi~Y>Q!6y2Mm4Uj=)?5okE&Pc`c|1$QYaDcHXbmQzrGY<0ZsA-}yv3dX36=W2+p Qe*pVPiOIh$6w&wnU;irY2mk;8 literal 0 HcmV?d00001 diff --git a/docs/src/tutorials/images/bar-plot.png b/docs/src/tutorials/images/bar-plot.png new file mode 100644 index 0000000000000000000000000000000000000000..2f113d4c6da09568fe23c87a426863909b1b05b3 GIT binary patch literal 35055 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 literal 0 HcmV?d00001 diff --git a/docs/src/tutorials/images/telemetry-1.png b/docs/src/tutorials/images/telemetry-1.png new file mode 100644 index 0000000000000000000000000000000000000000..2a606e83c772eae06bee198e6a45ae9acff27d70 GIT binary patch literal 16915 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 literal 0 HcmV?d00001 diff --git a/docs/src/tutorials/images/telemetry-3.png b/docs/src/tutorials/images/telemetry-3.png new file mode 100644 index 0000000000000000000000000000000000000000..c235b1d543e5a2ac2b0a5cbfe168dc2ec4659f01 GIT binary patch literal 51914 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 YdfBfr63qAO7Bc{Wr>mdKI;Vst0Nm`%*#H0l literal 0 HcmV?d00001 diff --git a/docs/src/tutorials/images/todo-edit.png b/docs/src/tutorials/images/todo-edit.png new file mode 100644 index 0000000000000000000000000000000000000000..3c1ba3f5cc73089d259f8d047628686059dcb546 GIT binary patch literal 25216 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 literal 0 HcmV?d00001 diff --git a/docs/src/tutorials/images/todo-restyled.png b/docs/src/tutorials/images/todo-restyled.png new file mode 100644 index 0000000000000000000000000000000000000000..9fd7008c2f22d8f040649e3104871c63c1a5363a GIT binary patch literal 25700 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 Z+itTPWnY5(1q%3)msXalkuVAVe*k%0+b#eA literal 0 HcmV?d00001 diff --git a/docs/src/tutorials/index.md b/docs/src/tutorials/index.md index 2920b27f05..88bedd19cd 100644 --- a/docs/src/tutorials/index.md +++ b/docs/src/tutorials/index.md @@ -123,7 +123,10 @@ __bundles.json__ ### Run a Web Server -The next step is to run a web server so that you can view the Open MCT Web client (including the plugins you add to it) in browser. The HTTP server option that is recommended here, for simplicity, is http-server, https://www.npmjs.com/package/http-server. +The next step is to run a web server so that you can view the Open MCT Web +client (including the plugins you add to it) in browser. The HTTP server option +that is recommended here, for simplicity, is http-server, +[https://www.npmjs.com/package/http-server](). To run: @@ -792,1977 +795,2243 @@ To support selection, we will need to make some changes to our controller: }); __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 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 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 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. +* 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 +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 +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 +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. - 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. +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. -
-
- All - Incomplete - Complete +
+ + +
    +
  • + + + + {{task.description}} + + +
  • +
+__tutorials/todo/res/templates/todo.html__ -
    -
  • - - - {{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: - 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" } - ] + { + "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 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: +__tutorials/todo/bundle.json__ +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: +![Edit](images/todo-edit.png) 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 +button appear, which we can then click on to remove that task: - 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. +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. -Step 6. Customizing Look and Feel +### Step 6. Customizing Look and Feel - In this section, our goal is to: +In this section, our goal is to: -Display the current filter choice. -Display the current task selection (when in Edit mode.) -Tweak the general aesthetics to our liking. -Get rid of those default tasks (we can create our own now.) +* Display the current filter choice. +* Display the current task selection (when in Edit mode.) +* 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 states in the controller: +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 + 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 + + 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); }); - }; - - // 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: - -checkVisibility has the same arguments as setVisibility, but instead of making a change, it simply returns a boolean true/false indicating whether those settings are in effect. The logic reflects the fact that the second parameter is ignored when showing all. -To support checking for selection, the index of the currently-selected task is tracked as part of the selection object. -Finally, an isSelected function is exposed which checks if the indicated task is currently selected, using the index from above. - - Additionally, we will want to define some CSS rules in order to reflect these 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; -} -tutorials/todo/res/css/todo.css - - Here, we have defined classes and appearances for: - -Our filter choosers (example-button-group). -Our selected and/or completed tasks (example-task-description). -A message, which we will add next, to display when there are no tasks (example-message). - - 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": [] - } + persist(); } - ], - "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" - } - ] + + // 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(); }, - { - "items": [ - { - "glyph": "Z", - "method": "removeTask", - "control": "button" - } - ] - } - ] - } + 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); + } + }); } - ], - "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. - - Finally, let’s utilize these changes from our view’s template: - - -
- - -
    -
  • - - - {{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: - - - - If we then go into Edit mode, add some tasks, and select one, it will now be much clearer what the current selection is (e.g. before we hit the remove button in the toolbar): - - - -Bar Graph - - In this tutorial, we will look at creating a bar graph plugin for visualizing telemetry data. Specifically, we want some bars that raise and lower to match the observed state of real-time telemetry; this is particularly useful for monitoring things like battery charge levels. - It is recommended that the reader completes (or is familiar with) the To-Do List tutorial before completing this tutorial, as certain concepts discussed there will be addressed in more brevity here. - -Step 1. Define the View - - 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 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" - } - ] - } -} -tutorials/bargraph/bundle.json - - The view definition should look familiar after the To-Do List tutorial, with some additions: - -The needs property indicates that this view is only applicable to domain objects with a telemetry capability. This ensures that this view is available for telemetry points, but not for other objects (like folders.) -The delegation property indicates that the above constraint can be satisfied via capability delegation; that is, by domain objects which delegate the telemetry capability to their contained objects. This allows this view to be 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 file ahead of time to describe the general look we want for the view. These look like: - - -
-
-
High
-
Middle
-
Low
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- 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 vertical axis, showing the numeric value that certain heights correspond to. The second will be for the actual bar graphs themselves; three are included here. 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: - -.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; -} -tutorials/bargraph/res/css/bargraph.css - - 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: - - - This means that our remaining work will be to populate and position these 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. Specifically, our goals for this step will be to: - -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. - - 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: - - -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; -}); -tutorials/bargraph/src/controllers/BarGraphController.js - - A summary of what we’ve done here: - -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.) -Add a utility function which converts from numeric values to percentages. This will help support some positioning in the template. -Utilize the telemetryHandler, provided by the platform, to start listening to real-time telemetry updates. This will deal with most of the complexity of dealing with telemetry (e.g. differentiating between individual telemetry points and telemetry panels, monitoring latest values) and provide us with a useful interface for populating our view. The the Open MCT Web Developer Guide for more information on dealing with telemetry. - - 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}} -
-
- -
-
-
-
-
-
-
-
- -
-
- - -
-
-
-tutorials/bargraph/res/templates/bargraph.html - - Summarizing these changes: - -Utilize the exposed low, middle, and high values to populate our labels along the vertical axis. Additionally, use the toPercent function to position these from the bottom. -Replace our three hard-coded bars with a repeater that looks at the telemetryObjects exposed by the controller and adds one bar each. -Position the dashed tick-line using the middle value and the toPercent function, lining it up with its label to the left. -At the bottom, repeat a set of labels for the telemetry-providing domain objects, with matching alignment to the bars above. We use an existing representation, label, to make this easier. - - 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" ] - } - ] - } -} -tutorials/bargraph/bundle.json - - When we reload Open MCT Web, we are now able to see that our bar graph view correctly labels one bar per telemetry-providing domain object, as shown for 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 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 telemetry-providing domain object, as percentages. - - -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)); - } + + return TodoController; + }); +__tutorials/todo/src/controllers/TodoController.js__ - // 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); - }); +A summary of these changes: - // Release subscriptions when scope is destroyed - $scope.$on('$destroy', handle.unsubscribe); +* `checkVisibility` has the same arguments as `setVisibility`, but instead of +making a change, it simply returns a boolean true/false indicating whether those +settings are in effect. The logic reflects the fact that the second parameter is +ignored when showing all. +* To support checking for selection, the index of the currently-selected task is +tracked as part of the selection object. +* Finally, an isSelected function is exposed which checks if the indicated task +is currently selected, using the index from above. + +Additionally, we will want to define some CSS rules in order to reflect these +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; + } +__tutorials/todo/res/css/todo.css__ - return BarGraphController; -}); -tutorials/bargraph/src/controllers/BarGraphController.js +Here, we have defined classes and appearances for: - The telemetryHandler exposes a method to provide us with our latest data value (the getRangeValue method), and we already have a function to convert from a numeric value to a percentage within the view, so we just use those. The only slight complication is that we want our bar to move up or down from the middle value, so either of our top or bottom position for the bar itself could be either the middle line, or the data value. We let Math.min and Math.max decide this. +* Our filter choosers (`example-button-group`). +* Our selected and/or completed tasks (`example-task-description`). +* A message, which we will add next, to display when there are no tasks +(`example-message`). - Next, we utilize this functionality from the template: +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": [] + } + } + ], + "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" + + } + + ] + } + } +__tutorials/todo/bundle.json__ -
-
-
- {{value}} +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: + + +
+ +
+ + All + + Incomplete + + Complete +
+ +
    +
  • + + + {{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: + +![Todo Restyled](images/todo-restyled.png) + +If we then go into Edit mode, add some tasks, and select one, it will now be +much clearer what the current selection is (e.g. before we hit the remove button +in the toolbar): + +![Todo Restyled](images/todo-selection.png) + +## Bar Graph + +In this tutorial, we will look at creating a bar graph plugin for visualizing +telemetry data. Specifically, we want some bars that raise and lower to match +the observed state of real-time telemetry; this is particularly useful for +monitoring things like battery charge levels. +It is recommended that the reader completes (or is familiar with) the To-Do +List tutorial before completing this tutorial, as certain concepts discussed +there will be addressed in more brevity here. + +### Step 1. Define the View + +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 +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" + } + ] + } + } +__tutorials/bargraph/bundle.json__ + +The view definition should look familiar after the To-Do List tutorial, with +some additions: + +* The `needs` property indicates that this view is only applicable to domain +objects with a `telemetry` capability. This ensures that this view is available +for telemetry points, but not for other objects (like folders.) +* The `delegation` property indicates that the above constraint can be satisfied +via capability delegation; that is, by domain objects which delegate the +`telemetry` capability to their contained objects. This allows this view to be +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 +file ahead of time to describe the general look we want for the view. These +look like: + +
+
+
High
+
Middle
+
Low
-
- -
-
-
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+ +
+
+ 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 +vertical axis, showing the numeric value that certain heights correspond to. The +second will be for the actual bar graphs themselves; three are included here. +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: + + .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; + } +__tutorials/bargraph/res/css/bargraph.css__ + +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: + +![Bar Plot](images/bar-plot.png) + +This means that our remaining work will be to populate and position these +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. +Specifically, our goals for this step will be to: + +* 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. + +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: + + 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; + }); +__tutorials/bargraph/src/controllers/BarGraphController.js__ + +A summary of what we’ve done here: + +* 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.) +* Add a utility function which converts from numeric values to percentages. This +will help support some positioning in the template. +* Utilize the `telemetryHandler`, provided by the platform, to start listening +to real-time telemetry updates. This will deal with most of the complexity of +dealing with telemetry (e.g. differentiating between individual telemetry points +and telemetry panels, monitoring latest values) and provide us with a useful +interface for populating our view. The the Open MCT Web Developer Guide for more +information on dealing with telemetry. + +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}} + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+ + + + + +
-
-tutorials/bargraph/res/templates/bargraph.html +__tutorials/bargraph/res/templates/bargraph.html__ - Here, we utilize the functions we just provided from the controller to position the bar, using an ng-style attribute. +Summarizing these changes: - When we reload Open MCT Web, our bar graph view now looks like: +* Utilize the exposed `low`, `middle`, and `high` values to populate our labels +along the vertical axis. Additionally, use the toPercent function to position +these from the bottom. +* Replace our three hard-coded bars with a repeater that looks at the +`telemetryObjects` exposed by the controller and adds one bar each. +* Position the dashed tick-line using the middle value and the `toPercent` +function, lining it up with its `label` to the left. +* At the bottom, repeat a set of labels for the telemetry-providing domain +objects, with matching alignment to the bars above. We use an existing +representation, label, to make this easier. + +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" ] + + } + + ] + } + } +__tutorials/bargraph/bundle.json__ + +When we reload Open MCT Web, we are now able to see that our bar graph view +correctly labels one bar per telemetry-providing domain object, as shown for +this Telemetry Panel containing four Sine Wave Generators. + +![Bar Plot](images/bar-plot-2.png) + +### Step 3. Using Telemetry 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 +telemetry-providing domain object, as percentages. + 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; + }); +__tutorials/bargraph/src/controllers/BarGraphController.js__ -Step 4. View Configuration +The `telemetryHandler` exposes a method to provide us with our latest data value +(the `getRangeValue` method), and we already have a function to convert from a +numeric value to a percentage within the view, so we just use those. The only +slight complication is that we want our bar to move up or down from the middle +value, so either of our top or bottom position for the bar itself could be +either the middle line, or the data value. We let `Math.min` and `Math.max` +decide this. - 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. +Next, we utilize this functionality from the template: -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 when we return to our view later, those changes will be persisted. +
+
+
+ {{value}} +
+
+ +
+
+
+
+
+
+
+
+ +
+
+ + +
+
+
+__tutorials/bargraph/res/templates/bargraph.html__ + +Here, we utilize the functions we just provided from the controller to position +the bar, using an ng-style attribute. + +When we reload Open MCT Web, our bar graph view now looks like: + +![Bar Plot](images/bar-plot-3.png) + +### Step 4. View Configuration + +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 +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: -{ - "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 - } - ] - } - ] + { + "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 +__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 object. +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` +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; + 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]; + + }; } - } - - // 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)); }; - } - - // 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: - -First, read low, middle, and high from the view configuration instead of 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. -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 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. -Finally, we expose a view proxy which will handle changes to low, middle, and high as entered by the user from the tool bar. This uses the getter-setters we defined previously. - - If we reload Open MCT Web and go to a Bar Graph view in Edit mode, we now see that we can change these bounds from the tool bar. - - - -Telemetry Adapter - - The goal of this tutorial is to demonstrate how to integrate Open MCT Web with an existing telemetry system. - A summary of the steps we will take: - -Expose the telemetry dictionary within the user interface. -Support subscription/unsubscription to real-time streaming data. -Support historical retrieval of telemetry data. - -Step 0. Expose Your Telemetry - - As a precondition to integrating telemetry data into Open MCT Web, this information needs to be available over web-based interfaces. In practice, this will most likely mean exposing data over HTTP, or over WebSockets. - For purposes of this tutorial, a simple node server is provided to stand in place of this existing telemetry system. It generates real-time data and exposes it over a WebSocket connection. - - -/*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)); + + // 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)); } - }); - - // Stop sending telemetry updates for this connection when closed - ws.on('close', function () { - listeners = listeners.filter(function (listener) { - return listener !== notifySubscribers; + $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); }); - }); - - // 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"]); + + // Release subscriptions when scope is destroyed + $scope.$on('$destroy', handle.unsubscribe); + } + + return BarGraphController; }); -}()); +__tutorials/bargraph/src/controllers/BarGraphController.js__ + +A summary of these changes: + +* First, read `low`, `middle`, and `high` from the view configuration instead of +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. +* 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 +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. +* Finally, we expose a view proxy which will handle changes to `low`, `middle`, +and `high` as entered by the user from the tool bar. This uses the +getter-setters we defined previously. + +If we reload Open MCT Web and go to a Bar Graph view in Edit mode, we now see +that we can change these bounds from the tool bar. + +![Bar plot](images/bar-plot-4.png) + +## Telemetry Adapter + +The goal of this tutorial is to demonstrate how to integrate Open MCT Web +with an existing telemetry system. + +A summary of the steps we will take: + +* Expose the telemetry dictionary within the user interface. +* Support subscription/unsubscription to real-time streaming data. +* Support historical retrieval of telemetry data. + +### Step 0. Expose Your Telemetry + +As a precondition to integrating telemetry data into Open MCT Web, this +information needs to be available over web-based interfaces. In practice, +this will most likely mean exposing data over HTTP, or over WebSockets. +For purposes of this tutorial, a simple node server is provided to stand +in place of this existing telemetry system. It generates real-time data +and exposes it over a WebSocket connection. -tutorial-server/app.js - - For purposes of this tutorial, how this server has been implemented is 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 excellent starting point.) - What does matter for this tutorial is the interfaces that are exposed. Once a WebSocket connection has been established to this server, it accepts plain text 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” -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” -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” -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” -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” -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 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” 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 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" - } - ] - } - ] -} -tutorial-server/dictionary.json - - It should be noted that neither the interface for the example server nor the dictionary format are expected by Open MCT Web; rather, these are intended to stand in for some existing source of telemetry data to which we wish to adapt Open MCT Web. - - We can run this example server by: - -cd tutorial-server -npm install ws -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: - -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 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 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 will retrieve from the server.) - -{ - "name": "Example Telemetry Adapter", - "extensions": { - "types": [ - { - "name": "Spacecraft", - "key": "example.spacecraft", - "glyph": "o" - } - ], - "roots": [ - { - "id": "example:sc", - "priority": "preferred", - "model": { - "type": "example.spacecraft", - "name": "My Spacecraft", - "composition": [] - } - } - ] - } -} - - - -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 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" -] -bundles.json - - ...we will be able to reload Open MCT Web and see that it is present: - - - - Now, we have somewhere in the UI to put the contents of our telemetry dictionary. - -Step 2. Expose the Telemetry Dictionary - - In order to expose the telemetry dictionary, we first need to read it from the server. Our first step will be to add a service that will handle interactions with the server; this will not be used by Open MCT Web directly, but will be used by subsequent components we add. - -/*global define,WebSocket*/ - -define( - [], - function () { + /*global require,process,console*/ + + var CONFIG = { + port: 8081, + dictionary: "dictionary.json", + interval: 1000 + }; + + (function () { "use strict"; - - function ExampleTelemetryServerAdapter($q, wsUrl) { - var ws = new WebSocket(wsUrl), - 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; - } - }; - - // Request dictionary once connection is established - ws.onopen = function () { - ws.send("dictionary"); - }; - - return { - dictionary: function () { - return dictionary.promise; - } - }; - } - - return ExampleTelemetryServerAdapter; - } -); -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 close-handling for this WebSocket connection have been omitted for brevity. - -Once the dictionary has been loaded, we will want to represent its contents as domain objects. Specifically, we want subsystems to appear as objects under My Spacecraft, and measurements to appear as objects within those subsystems. This means that we need to convert the data from the dictionary into domain object models, and expose these to Open MCT Web via a modelService. - - -/*global define*/ - -define( - function () { - "use strict"; - - var PREFIX = "example_tlm:", - FORMAT_MAPPINGS = { - float: "number", - integer: "number", - string: "string" - }; - - function ExampleTelemetryModelProvider(adapter, $q) { - var modelPromise, empty = $q.when({}); - - // Check if this model is in our dictionary (by prefix) - function isRelevant(id) { - return id.indexOf(PREFIX) === 0; - } - - // Build a domain object identifier by adding a prefix - function makeId(element) { - return PREFIX + element.identifier; - } - - // Create domain object models from this dictionary - function buildTaxonomy(dictionary) { - var models = {}; - - // Create & store a domain object model for a measurement - function addMeasurement(measurement) { - var format = FORMAT_MAPPINGS[measurement.type]; - models[makeId(measurement)] = { - type: "example.measurement", - name: measurement.name, - telemetry: { - key: measurement.identifier, - ranges: [{ - key: "value", - name: "Value", - units: measurement.units, - format: format - }] - } - }; - } - - // Create & store a domain object model for a subsystem - function addSubsystem(subsystem) { - var measurements = - (subsystem.measurements || []); - models[makeId(subsystem)] = { - type: "example.subsystem", - name: subsystem.name, - composition: measurements.map(makeId) - }; - measurements.forEach(addMeasurement); - } - - (dictionary.subsystems || []).forEach(addSubsystem); - - return models; - } - - // Begin generating models once the dictionary is available - modelPromise = adapter.dictionary().then(buildTaxonomy); - - return { - getModels: function (ids) { - // Return models for the dictionary only when they - // are relevant to the request. - return ids.some(isRelevant) ? modelPromise : empty; - } - }; - } - - return ExampleTelemetryModelProvider; - } -); -tutorials/telemetry/src/ExampleTelemetryModelProvider.js - - This script implements a provider for modelService; the modelService is a composite service, meaning that multiple such services can exist side by side. (For example, there is another provider for modelService that reads domain 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) 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 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 whole dictionary.) -Some notable points to call out here: - -Every subsystem and every measurement from the dictionary has an identifier field declared. We use this as part of the domain object identifier, but we also prefix it with example_tlm:. This accomplishes a few things: -We can easily tell whether an identifier is expected to be in the dictionary or not. -We avoid naming collisions with other model providers. -Finally, Open MCT Web uses the colon prefix as a hint that this domain object will not be in the persistence store. -A couple of new types are introduced here (in the type field of the domain object models we create); we will need to define these as extensions as well in order for them to display correctly. -The composition field of each subsystem contained the Open MCT Web identifiers of all the measurements in that subsystem. This composition field will be used by Open MCT Web to determine what domain objects contain other domain objects (e.g. to populate the tree.) -The telemetry field of each measurement will be used by Open MCT Web to understand how to request and interpret telemetry data for this object. The key is the machine-readable identifier for this measurement within the telemetry system; the ranges provide metadata about the values for this data. (A separate field, domains, provides metadata about timestamps or other 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 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 fix this, we will need another script which will add these subsystems to the root-level object we added in Step 1. - - - -/*global define*/ - -define( - function () { - "use strict"; - - var TAXONOMY_ID = "example:sc", - PREFIX = "example_tlm:"; - - function ExampleTelemetryInitializer(adapter, objectService) { - // Generate a domain object identifier for a dictionary element - function makeId(element) { - return PREFIX + element.identifier; - } - - // When the dictionary is available, add all subsystems - // to the composition of My Spacecraft - function initializeTaxonomy(dictionary) { - // Get the top-level container for dictionary objects - // from a group of domain objects. - function getTaxonomyObject(domainObjects) { - return domainObjects[TAXONOMY_ID]; - } - - // Populate - function populateModel(taxonomyObject) { - return taxonomyObject.useCapability( - "mutation", - function (model) { - model.name = - dictionary.name; - model.composition = - dictionary.subsystems.map(makeId); - } - ); - } - - // Look up My Spacecraft, and populate it accordingly. - objectService.getObjects([TAXONOMY_ID]) - .then(getTaxonomyObject) - .then(populateModel); - } - - adapter.dictionary().then(initializeTaxonomy); - } - - return ExampleTelemetryInitializer; - } -); -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 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 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" + + 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 }, - { - "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" ] - } - ] - } -} -tutorials/telemetry/bundle.json - - A summary of what we’ve added here: - -New type definitions have been added to represent Subsystems and Measurements, respectively. -Measurements have a telemetry field; this is similar to the telemetry 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 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 ensure that Subsystems and Measurements will be recognized as having composition and telemetry capabilities, respectively. -The adapter to the WebSocket server has been added as a service with the symbolic name example.adapter; it is depended-upon elsewhere within this plugin. -A constant, EXAMPLE_WS_URL, is defined, and depended-upon by example.server. Setting priority to fallback means this constant will be overridden if defined anywhere else, allowing configuration bundles to specify different URLs for the WebSocket connection. -The initializer script is registered using the runs category of extension, to ensure that this executes (and populates the contents of the top-level My 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 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 available in the constructor call. - - Now if we run Open MCT Web (assuming our example telemetry server is also running) and expand our top-level node completely, we see the contents of our dictionary: - - - - 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 ourselves the ability to retrieve this data from the server. In this step, we 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; - } - }; + 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); } - - return ExampleTelemetryServerAdapter; - } -); - - - -tutorials/telemetry/src/ExampleTelemetryServerAdapter.js - - When the history method is called, a new request is issued to the server for historical telemetry, unless a request for the same historical telemetry is still pending. Similarly, when historical telemetry arrives for a given identifier, the pending promise is resolved. - - This history method will be used by a telemetryService provider which we will implement: - -/*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); + + 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] + })); } - - // Retrieve telemetry for a specific measurement - function handleRequest(request) { - var key = request.key; - return adapter.history(key).then(addToPackage); + }; + + 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] + })); } - - packaged[SOURCE] = {}; - return $q.all(relevantReqs.map(handleRequest)) - .then(function () { return packaged; }); - }, - subscribe: function (callback, requests) { - return function () {}; + }); + } + + // Listen for requests + ws.on('message', function (message) { + var parts = message.split(' '), + handler = handlers[parts[0]]; + if (handler) { + handler.apply(handlers, parts.slice(1)); } - }; - } - - return ExampleTelemetryProvider; - } -); - - - -tutorials/telemetry/src/ExampleTelemetryProvider.js - - The requestTelemetry method of a telemetryService is expected to take an array of requests (each with source and key parameters, identifying the general source of data and the specific element within that source, respectively) and return a Promise for any telemetry data it knows of which satisfies those requests, packaged in a specific way. This packaging is as an object containing key-value pairs, where keys correspond to source properties of requests and values are key-value pairs, where keys correspond to key properties of requests and values are TelemetrySeries objects. (We will see our implementation below.) - To do this, we create a container for our telemetry source, and consult the adapter to get telemetry histories for any relevant requests, then package them as they come in. The $q.all method is used to return a single Promise that will resolve only when all histories have been packaged. Promise-chaining is used to ensure that the resolved value will be the fully-packaged data. - -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 telemetry capability. - - As such, the source and key properties we observe here will come from the type definition and domain object model, respectively, as we specified them during Step 2. (Or, they might come from somewhere else entirely, if we have other telemetry-providing domain objects in our system; that is something we check for using the source property.) - - Finally, note that we also have a subscribe method, to satisfy the interface of telemetryService, but this subscribe method currently does nothing. - This script uses an ExampleTelemetrySeries class, which looks like: - - -/*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; - } -); -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" - } - ] - } - } - ], - "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" ] - } - ] - } -} -tutorials/telemetry/bundle.json - - Now, if we navigate to one of our numeric measurements, we should see a plot of its historical telemetry: - - - - 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 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; - } -); -tutorials/telemetry/src/ExampleTelemetryServerAdapter.js - - Here, we have added subscribe and unsubscribe methods which issue the corresponding requests to the server. Seperately, we introduce the ability to listen for data messages as they come in: These will contain the data associated 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); + }); + + // Stop sending telemetry updates for this connection when closed + ws.on('close', function () { + listeners = listeners.filter(function (listener) { + return listener !== notifySubscribers; }); }); - - 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); - }; - } - }; + + // 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__ - return ExampleTelemetryProvider; +For purposes of this tutorial, how this server has been implemented is +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 +excellent starting point.) + +What does matter for this tutorial is the interfaces that are exposed. Once a +WebSocket connection has been established to this server, it accepts plain text +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” + * `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” + * `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” + * `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” + * `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” + * `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 +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” +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 +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" + } + ] + } + ] } -); +__tutorial-server/dictionary.json__ +It should be noted that neither the interface for the example server nor the +dictionary format are expected by Open MCT Web; rather, these are intended to +stand in for some existing source of telemetry data to which we wish to adapt +Open MCT Web. +We can run this example server by: -tutorials/telemetry/src/ExampleTelemetryProvider.js + cd tutorial-server + npm install ws + node app.js - A quick summary of these changes: +To verify that this is running and try out its interface, we can use a tool +like [https://www.npmjs.com/package/wscat](): -First, we maintain current subscribers (callbacks) in an object containing key-value pairs, where keys are request key properties, and values are callback arrays. -We listen to new data coming in from the server adapter, and invoke any relevant callbacks when this happens. We package the data in the same manner that historical telemetry is packaged (even though in this case we are providing single-element series objects.) -Finally, in our subscribe method we add callbacks to the lists of active 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 multiple subscribe requests to the server, because we happen to know that the server can handle this.) + 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"}]}]}} - Running Open MCT Web again, we can still plot our historical telemetry - but now we also see that it updates in real-time as more data comes in from the server. +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 +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 +will retrieve from the server.) + + { + "name": "Example Telemetry Adapter", + "extensions": { + "types": [ + { + "name": "Spacecraft", + "key": "example.spacecraft", + "glyph": "o" + } + ], + "roots": [ + { + "id": "example:sc", + "priority": "preferred", + "model": { + "type": "example.spacecraft", + "name": "My Spacecraft", + "composition": [] + } + } + ] + } + } +__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 +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" + ] +__bundles.json__ + +...we will be able to reload Open MCT Web and see that it is present: + +![](images/telemetry-1.png) + +Now, we have somewhere in the UI to put the contents of our telemetry +dictionary. + +### Step 2. Expose the Telemetry Dictionary + +In order to expose the telemetry dictionary, we first need to read it from the +server. Our first step will be to add a service that will handle interactions +with the server; this will not be used by Open MCT Web directly, but will be +used by subsequent components we add. + + /*global define,WebSocket*/ + + define( + [], + function () { + "use strict"; + + function ExampleTelemetryServerAdapter($q, wsUrl) { + var ws = new WebSocket(wsUrl), + 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; + } + }; + + // Request dictionary once connection is established + ws.onopen = function () { + ws.send("dictionary"); + }; + + return { + dictionary: function () { + return dictionary.promise; + } + }; + } + + return ExampleTelemetryServerAdapter; + } + ); +__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 +close-handling for this WebSocket connection have been omitted for brevity. + +Once the dictionary has been loaded, we will want to represent its contents +as domain objects. Specifically, we want subsystems to appear as objects +under My Spacecraft, and measurements to appear as objects within those +subsystems. This means that we need to convert the data from the dictionary +into domain object models, and expose these to Open MCT Web via a +`modelService`. + + /*global define*/ + + define( + function () { + "use strict"; + + var PREFIX = "example_tlm:", + FORMAT_MAPPINGS = { + float: "number", + integer: "number", + string: "string" + }; + + function ExampleTelemetryModelProvider(adapter, $q) { + var modelPromise, empty = $q.when({}); + + // Check if this model is in our dictionary (by prefix) + function isRelevant(id) { + return id.indexOf(PREFIX) === 0; + } + + // Build a domain object identifier by adding a prefix + function makeId(element) { + return PREFIX + element.identifier; + } + + // Create domain object models from this dictionary + function buildTaxonomy(dictionary) { + var models = {}; + + // Create & store a domain object model for a measurement + function addMeasurement(measurement) { + var format = FORMAT_MAPPINGS[measurement.type]; + models[makeId(measurement)] = { + type: "example.measurement", + name: measurement.name, + telemetry: { + key: measurement.identifier, + ranges: [{ + key: "value", + name: "Value", + units: measurement.units, + format: format + }] + } + }; + } + + // Create & store a domain object model for a subsystem + function addSubsystem(subsystem) { + var measurements = + (subsystem.measurements || []); + models[makeId(subsystem)] = { + type: "example.subsystem", + name: subsystem.name, + composition: measurements.map(makeId) + }; + measurements.forEach(addMeasurement); + } + + (dictionary.subsystems || []).forEach(addSubsystem); + + return models; + } + + // Begin generating models once the dictionary is available + modelPromise = adapter.dictionary().then(buildTaxonomy); + + return { + getModels: function (ids) { + // Return models for the dictionary only when they + // are relevant to the request. + return ids.some(isRelevant) ? modelPromise : empty; + } + }; + } + + return ExampleTelemetryModelProvider; + } + ); +__tutorials/telemetry/src/ExampleTelemetryModelProvider.js__ + +This script implements a `provider` for `modelService`; the `modelService` is a +composite service, meaning that multiple such services can exist side by side. +(For example, there is another `provider` for `modelService` that reads domain +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]()) +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 +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 +whole dictionary.) + +Some notable points to call out here: + +* Every subsystem and every measurement from the dictionary has an `identifier` +field declared. We use this as part of the domain object identifier, but we +also prefix it with `example_tlm`:. This accomplishes a few things: + * We can easily tell whether an identifier is expected to be in the + dictionary or not. + * We avoid naming collisions with other model providers. + * Finally, Open MCT Web uses the colon prefix as a hint that this domain + object will not be in the persistence store. +* A couple of new types are introduced here (in the `type` field of the domain +object models we create); we will need to define these as extensions as well in +order for them to display correctly. +* The `composition` field of each subsystem contained the Open MCT Web +identifiers of all the measurements in that subsystem. This `composition` field +will be used by Open MCT Web to determine what domain objects contain other +domain objects (e.g. to populate the tree.) +* The `telemetry` field of each measurement will be used by Open MCT Web to +understand how to request and interpret telemetry data for this object. The +`key` is the machine-readable identifier for this measurement within the +telemetry system; the `ranges` provide metadata about the values for this data. +(A separate field, `domains`, provides metadata about timestamps or other +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 +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 +fix this, we will need another script which will add these subsystems to the +root-level object we added in Step 1. + + /*global define*/ + + define( + function () { + "use strict"; + + var TAXONOMY_ID = "example:sc", + PREFIX = "example_tlm:"; + + function ExampleTelemetryInitializer(adapter, objectService) { + // Generate a domain object identifier for a dictionary element + function makeId(element) { + return PREFIX + element.identifier; + } + + // When the dictionary is available, add all subsystems + // to the composition of My Spacecraft + function initializeTaxonomy(dictionary) { + // Get the top-level container for dictionary objects + // from a group of domain objects. + function getTaxonomyObject(domainObjects) { + return domainObjects[TAXONOMY_ID]; + } + + // Populate + function populateModel(taxonomyObject) { + return taxonomyObject.useCapability( + "mutation", + function (model) { + model.name = + dictionary.name; + model.composition = + dictionary.subsystems.map(makeId); + } + ); + } + + // Look up My Spacecraft, and populate it accordingly. + objectService.getObjects([TAXONOMY_ID]) + .then(getTaxonomyObject) + .then(populateModel); + } + + adapter.dictionary().then(initializeTaxonomy); + } + + return ExampleTelemetryInitializer; + } + ); +__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 +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 +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": [] + } + } + ], + + "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: + +* New type definitions have been added to represent Subsystems and Measurements, +respectively. + * Measurements have a `telemetry` field; this is similar to the `telemetry` + 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 + `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 + ensure that Subsystems and Measurements will be recognized as having + `composition` and `telemetry` capabilities, respectively. +* The adapter to the WebSocket server has been added as a service with the +symbolic name `example.adapter`; it is depended-upon elsewhere within this +plugin. +* A constant, `EXAMPLE_WS_URL`, is defined, and depended-upon by +`example.server`. Setting `priority` to `fallback` means this constant will be +overridden if defined anywhere else, allowing configuration bundles to specify +different URLs for the WebSocket connection. +* The initializer script is registered using the `runs` category of extension, +to ensure that this executes (and populates the contents of the top-level My +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 + 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 + available in the constructor call. + +Now if we run Open MCT Web (assuming our example telemetry server is also +running) and expand our top-level node completely, we see the contents of our +dictionary: + +[](images/telemetry-2.png) + +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 +ourselves the ability to retrieve this data from the server. In this step, we +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; + } + ); + +__tutorials/telemetry/src/ExampleTelemetryServerAdapter.js__ + +When the `history` method is called, a new request is issued to the server for +historical telemetry, _unless_ a request for the same historical telemetry is +still pending. Similarly, when historical telemetry arrives for a given +identifier, the pending promise is resolved. + +This `history` method will be used by a `telemetryService` provider which we +will implement: + + /*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 () {}; + } + }; + } + + return ExampleTelemetryProvider; + } + ); +__tutorials/telemetry/src/ExampleTelemetryProvider.js__ + +The `requestTelemetry` method of a `telemetryService` is expected to take an +array of requests (each with `source` and `key` parameters, identifying the +general source of data and the specific element within that source, respectively) and +return a Promise for any telemetry data it knows of which satisfies those +requests, packaged in a specific way. This packaging is as an object containing +key-value pairs, where keys correspond to `source` properties of requests and +values are key-value pairs, where keys correspond to `key` properties of requests +and values are `TelemetrySeries` objects. (We will see our implementation +below.) + +To do this, we create a container for our telemetry source, and consult the +adapter to get telemetry histories for any relevant requests, then package +them as they come in. The `$q.all` method is used to return a single Promise +that will resolve only when all histories have been packaged. Promise-chaining +is used to ensure that the resolved value will be the fully-packaged data. + +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 +`telemetry` capability. + +As such, the `source` and `key` properties we observe here will come from the +type definition and domain object model, respectively, as we specified them +during Step 2. (Or, they might come from somewhere else entirely, if we have +other telemetry-providing domain objects in our system; that is something we +check for using the `source` property.) + +Finally, note that we also have a subscribe method, to satisfy the interface of +telemetryService, but this subscribe method currently does nothing. + +This script uses an `ExampleTelemetrySeries` class, which looks like: + + /*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; + } + ); +__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" + } + ] + } + } + ], + "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" ] + + } + ] + } + } +__tutorials/telemetry/bundle.json__ + +Now, if we navigate to one of our numeric measurements, we should see a plot of +its historical telemetry: + +![Telemetry](images/telemetry-3.png) + +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 +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; + } + ); +__tutorials/telemetry/src/ExampleTelemetryServerAdapter.js__ + +Here, we have added `subscribe` and `unsubscribe` methods which issue the +corresponding requests to the server. Seperately, we introduce the ability to +listen for `data` messages as they come in: These will contain the data associated +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); + + }; + } + }; + } + + return ExampleTelemetryProvider; + } + ); +__tutorials/telemetry/src/ExampleTelemetryProvider.js__ + +A quick summary of these changes: + +First, we maintain current subscribers (callbacks) in an object containing +key-value pairs, where keys are request key properties, and values are callback +arrays. +We listen to new data coming in from the server adapter, and invoke any +relevant callbacks when this happens. We package the data in the same manner +that historical telemetry is packaged (even though in this case we are +providing single-element series objects.) +Finally, in our `subscribe` method we add callbacks to the lists of active +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 +multiple subscribe requests to the server, because we happen to know that the +server can handle this.) + +Running Open MCT Web again, we can still plot our historical telemetry - but +now we also see that it updates in real-time as more data comes in from the +server. From fa487e026ec1d1b1de633730498ab4b6310ac589 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Mon, 12 Oct 2015 20:09:02 -0700 Subject: [PATCH 11/24] Fixed code formatting error --- docs/src/index.html | 3 ++- docs/src/tutorials/index.md | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/src/index.html b/docs/src/index.html index e84b405234..e80a6138b2 100644 --- a/docs/src/index.html +++ b/docs/src/index.html @@ -29,8 +29,9 @@ Sections: diff --git a/docs/src/tutorials/index.md b/docs/src/tutorials/index.md index 88bedd19cd..6ec8e8d3b3 100644 --- a/docs/src/tutorials/index.md +++ b/docs/src/tutorials/index.md @@ -22,7 +22,7 @@ In this section, we will cover the steps necessary to get a minimal Open MCT Web developer environment up and running. Once we have this, we will be able to proceed with writing plugins as described in this tutorial. -## Prerequisites +### Prerequisites This tutorial assumes you have the following software installed. Version numbers record what was used in writing this tutorial; the same steps should work with @@ -37,7 +37,7 @@ Open MCT Web can be run without any of these tools, provided suitable alternatives are taken; see the [Open MCT Web Developer Guide](../guide/index.md) for a more general overview of how to run and deploy a Open MCT Web application. -## Check out Open MCT Web Sources +### Check out Open MCT Web Sources First step is to check out Open MCT Web from the source repository. @@ -55,7 +55,7 @@ At this point, it will also be useful to branch off of Open MCT Web v0.6.2 git branch open-v0.6.2 git checkout -## Configuring Persistence +### Configuring Persistence 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 @@ -69,7 +69,7 @@ To change this configuration, edit bundles.json (at the top level of the Open MCT Web repository) and replace platform/persistence/elastic with example/persistence. -### Before +#### Before [ "platform/framework", @@ -95,7 +95,7 @@ example/persistence. ] __bundles.json__ -### After +#### After [ "platform/framework", From e43a788a6d922fd57bc0650b39a8ebfee2b9382e Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Mon, 12 Oct 2015 20:11:31 -0700 Subject: [PATCH 12/24] Fixed code formatting error --- docs/src/tutorials/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/tutorials/index.md b/docs/src/tutorials/index.md index 6ec8e8d3b3..9a6d58fff2 100644 --- a/docs/src/tutorials/index.md +++ b/docs/src/tutorials/index.md @@ -180,6 +180,7 @@ to this plugin as tutorials/todo as well.) We will start with an “empty bundle } } + __tutorials/todo/bundle.json__ We will also include this in our list of active bundles. From 87f48aac354b835c2f09451919aaf2d13f46e544 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Mon, 12 Oct 2015 20:12:18 -0700 Subject: [PATCH 13/24] Fixed code formatting error --- docs/src/tutorials/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/tutorials/index.md b/docs/src/tutorials/index.md index 9a6d58fff2..d83431ddea 100644 --- a/docs/src/tutorials/index.md +++ b/docs/src/tutorials/index.md @@ -170,8 +170,8 @@ 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” -- one which exposes no extensions - which looks like: +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", From 64fae21d1634c45ebb1926d706a8d70ec02d4061 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Mon, 12 Oct 2015 20:15:50 -0700 Subject: [PATCH 14/24] Fixed code formatting error --- docs/src/tutorials/index.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/src/tutorials/index.md b/docs/src/tutorials/index.md index d83431ddea..eebb989440 100644 --- a/docs/src/tutorials/index.md +++ b/docs/src/tutorials/index.md @@ -16,6 +16,13 @@ October 6, 2015 | 2.2 | Conversion to markdown | Andrew Henry # Introduction +## This document +This document contains a number of code examples in formatted code blocks. In +many cases these code blocks are repeated in order to highlight code that has +been added or removed as part of the tutorial. In these cases, any lines added +will be indicated with a '+' at the start of the line. Any lines removed will +be indicated with a '-'. + ## Setting Up Open MCT Web In this section, we will cover the steps necessary to get a minimal Open MCT Web From 53a3a2f459b3e632da25b4c5d337372395de6102 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Tue, 13 Oct 2015 10:25:20 -0700 Subject: [PATCH 15/24] Additional editing --- docs/src/tutorials/index.md | 64 ++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/docs/src/tutorials/index.md b/docs/src/tutorials/index.md index eebb989440..9eb935b9e4 100644 --- a/docs/src/tutorials/index.md +++ b/docs/src/tutorials/index.md @@ -250,8 +250,9 @@ 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)...which shows that -our plugin has loaded.` +`Resolving extensions for bundle tutorials/todo(To-do Plugin)` + +...which shows that our plugin has loaded. ### Step 2. Add a Domain Object Type @@ -337,16 +338,16 @@ __tutorials/todo/res/templates/todo.html__ 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. -After that, we have a list of tasks. The scope variable model is the model of -the domain object being viewed; this contains all of the persistent state +* 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. +* After that, we have a list of tasks. The scope variable `model` is the model +of the domain object being viewed; this contains all of the persistent state associated with that object. This model is effectively just a JSON document, so we can choose what goes into it (so long as we take care not to collide with platform-defined properties; see the Open MCT Web Developer Guide.) Here, we -assume that all tasks will be stored in a property tasks, and that each will be -an object containing a description (the readable summary of the task) and a -boolean completed flag. +assume that all tasks will be stored in a property `tasks`, and that each will be +an object containing a `description` (the readable summary of the task) and a +boolean `completed` flag. To expose this view in Open MCT Web, we need to declare it in our bundle definition: @@ -377,21 +378,22 @@ definition: } __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 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 that we create, but -not for other domain objects (such as Folders.) +* 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 glyph and name properties describe the icon and human-readable name for this -view to display in the UI where needed (if multiple views are available for -To-do Lists, the user will be able to choose one.) +* 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 +that we create, but not for other domain objects (such as Folders.) -Finally, the templateUrl points to the Angular template we wrote; this path is -relative to the bundle’s res folder. +* The `glyph` and `name` properties describe the icon and human-readable name +for this view to display in the UI where needed (if multiple views are available +for To-do Lists, the user will be able to choose one.) + +* Finally, the `templateUrl` points to the Angular template we wrote; this path is +relative to the bundle’s `res` folder. 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 @@ -500,17 +502,17 @@ __tutorials/todo/src/controllers/TodoController.js__ 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 +* `setVisibility` changes which tasks are meant to be visible. The first argument is a boolean, which, if true, means we want to show everything; the second 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 its -persistence capability. See the Open MCT Web Developer Guide for more -information on these capabilities. +* `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 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 +* `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 the use of the double-not !! to coerce the completed flag to a boolean, for @@ -601,12 +603,14 @@ In this extension definition we have: * A `key`, which again is a machine-readable identifier. This is the name that templates will reference. * An `implementation`, which refers to an AMD module. The path is relative to the -src directory within the bundle. +`src` directory within the bundle. * The `depends` property declares the dependencies of this controller. Here, we want Angular to inject `$scope`, the current Angular scope (which, going back to our controller, is expected as our first argument.) -If we reload the browser now, our To-do List looks much the same, but now we are able to filter down the visible list, and the changes we make will stick around if we go to My Items and come back. +If we reload the browser now, our To-do List looks much the same, but now we are +able to filter down the visible list, and the changes we make will stick around +if we go to My Items and come back. ### Step 5. Support Editing @@ -703,8 +707,8 @@ __tutorials/todo/bundle.json__ 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 -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 +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` method. For more information on forms and tool bars in Open MCT Web, see the Open MCT Web Developer Guide. From 5b617295e94cd352711465f0f4431600c17549db Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 13 Oct 2015 12:08:58 -0700 Subject: [PATCH 16/24] Did review of tutorials --- .../images/{add-tasl.png => add-task.png} | Bin docs/src/tutorials/index.md | 126 +++++++++--------- 2 files changed, 65 insertions(+), 61 deletions(-) rename docs/src/tutorials/images/{add-tasl.png => add-task.png} (100%) diff --git a/docs/src/tutorials/images/add-tasl.png b/docs/src/tutorials/images/add-task.png similarity index 100% rename from docs/src/tutorials/images/add-tasl.png rename to docs/src/tutorials/images/add-task.png diff --git a/docs/src/tutorials/index.md b/docs/src/tutorials/index.md index 9eb935b9e4..3211b2dd49 100644 --- a/docs/src/tutorials/index.md +++ b/docs/src/tutorials/index.md @@ -720,7 +720,7 @@ 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 () { + define(function () { + // Form to display when adding new tasks + var NEW_TASK_FORM = { + name: "Add a Task", @@ -734,7 +734,7 @@ To support selection, we will need to make some changes to our controller: + }] + }; - function TodoController($scope, dialogService) { + + function TodoController($scope, dialogService) { var showAll = true, showCompleted; @@ -745,21 +745,23 @@ To support selection, we will need to make some changes to our controller: return persistence && persistence.persist(); } - // Remove a task - function removeTaskAtIndex(taskIndex) { - $scope.domainObject.useCapability('mutation', function (model) { - model.tasks.splice(taskIndex, 1); - }); - 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(); - } + + // 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) { @@ -782,25 +784,25 @@ To support selection, we will need to make some changes to our controller: }; // 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(); - } - }); - }; + + 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); - } - }); - } + + // Expose a view-level selection proxy + + $scope.selection.proxy({ + + addTask: function () { + + dialogService.getUserInput(NEW_TASK_FORM, {}) + + .then(addNewTask); + + } + + }); + + } } return TodoController; @@ -996,10 +998,10 @@ states in the controller: showCompleted = completed; }; - // Check if current visibility settings match - $scope.checkVisibility = function (all, completed) { - return showAll ? all : (completed === showCompleted); - }; + + // 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) { @@ -1024,14 +1026,15 @@ states in the controller: removeTaskAtIndex(taskIndex); $scope.selection.deselect(); }, - taskIndex: taskIndex + + taskIndex: taskIndex }); }; - // Expose a check for current selection state - $scope.isSelected = function (taskIndex) { - return ($scope.selection.get() || {}).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({ @@ -1503,15 +1506,15 @@ __tutorials/bargraph/res/templates/bargraph.html__ Summarizing these changes: * Utilize the exposed `low`, `middle`, and `high` values to populate our labels -along the vertical axis. Additionally, use the toPercent function to position +along the vertical axis. Additionally, use the `toPercent` function to position these from the bottom. * Replace our three hard-coded bars with a repeater that looks at the `telemetryObjects` exposed by the controller and adds one bar each. -* Position the dashed tick-line using the middle value and the `toPercent` -function, lining it up with its `label` to the left. +* Position the dashed tick-line using the `middle` value and the `toPercent` +function, lining it up with its label to the left. * At the bottom, repeat a set of labels for the telemetry-providing domain objects, with matching alignment to the bars above. We use an existing -representation, label, to make this easier. +representation, `label`, to make this easier. Finally, we expose our controller from our bundle definition. Note that the depends declaration includes both `$scope` as well as the `telemetryHandler` @@ -1583,10 +1586,10 @@ telemetry-providing domain object, as percentages. + 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)); - } + + $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 () { @@ -1816,7 +1819,7 @@ A summary of these changes: * First, read `low`, `middle`, and `high` from the view configuration instead of 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 +* 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. * The tool bar will treat properties in a view proxy as getter-setters if @@ -2218,10 +2221,10 @@ __bundles.json__ ...we will be able to reload Open MCT Web and see that it is present: -![](images/telemetry-1.png) +![Telemetry](images/telemetry-1.png) Now, we have somewhere in the UI to put the contents of our telemetry -dictionary. +dictionary. ### Step 2. Expose the Telemetry Dictionary @@ -2590,7 +2593,8 @@ Now if we run Open MCT Web (assuming our example telemetry server is also running) and expand our top-level node completely, we see the contents of our dictionary: -[](images/telemetry-2.png) +![Telemetry 2](images/telemetry-2.png) + Note that “My Spacecraft” has changed its name to “Example Spacecraft”, which is the name it had in the dictionary. @@ -2742,8 +2746,8 @@ during Step 2. (Or, they might come from somewhere else entirely, if we have other telemetry-providing domain objects in our system; that is something we check for using the `source` property.) -Finally, note that we also have a subscribe method, to satisfy the interface of -telemetryService, but this subscribe method currently does nothing. +Finally, note that we also have a `subscribe` method, to satisfy the interface of +`telemetryService`, but this `subscribe` method currently does nothing. This script uses an `ExampleTelemetrySeries` class, which looks like: @@ -2775,7 +2779,7 @@ __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: +Finally, we expose this `telemetryService` provider declaratively: { "name": "Example Telemetry Adapter", @@ -3029,14 +3033,14 @@ __tutorials/telemetry/src/ExampleTelemetryProvider.js__ A quick summary of these changes: -First, we maintain current subscribers (callbacks) in an object containing +* First, we maintain current subscribers (callbacks) in an object containing key-value pairs, where keys are request key properties, and values are callback arrays. -We listen to new data coming in from the server adapter, and invoke any +* We listen to new data coming in from the server adapter, and invoke any relevant callbacks when this happens. We package the data in the same manner that historical telemetry is packaged (even though in this case we are providing single-element series objects.) -Finally, in our `subscribe` method we add callbacks to the lists of active +* Finally, in our `subscribe` method we add callbacks to the lists of active 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, From 8c2a29e895eb1177783666bc87e3c29422a2ba7c Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Wed, 14 Oct 2015 17:11:08 -0700 Subject: [PATCH 17/24] Modified file copy prcess to prevent encoding of png's as utf8 --- docs/gendocs.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/gendocs.js b/docs/gendocs.js index 2fcda7214e..329a9b607f 100644 --- a/docs/gendocs.js +++ b/docs/gendocs.js @@ -179,13 +179,17 @@ GLOBAL.window = GLOBAL.window || GLOBAL; // nomnoml expects window to be define glob(options['in'] + "/**/*.@(html|css|png)", {}, function (err, files) { files.forEach(function (file) { var destination = file.replace(options['in'], options.out), - destPath = path.dirname(destination); - + destPath = path.dirname(destination), + streamOptions = {}; + if (file.match(/png$/)){ + streamOptions.encoding = 'binary'; + } else { + streamOptions.encoding = 'utf8'; + } + mkdirp(destPath, function (err) { - fs.createReadStream(file, { encoding: 'utf8' }) - .pipe(fs.createWriteStream(destination, { - encoding: 'utf8' - })); + fs.createReadStream(file, streamOptions) + .pipe(fs.createWriteStream(destination, streamOptions)); }); }); }); From 4f18663c71c92e422af82fd412d3044a6a350b65 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Wed, 14 Oct 2015 17:14:00 -0700 Subject: [PATCH 18/24] Fixed title in tutorials --- docs/src/tutorials/index.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/src/tutorials/index.md b/docs/src/tutorials/index.md index 3211b2dd49..5c6ac98691 100644 --- a/docs/src/tutorials/index.md +++ b/docs/src/tutorials/index.md @@ -1,18 +1,18 @@ -Open MCT Web Tutorials +# Open MCT Web Tutorials Victor Woeltjen victor.woeltjen@nasa.gov -October 6, 2015 +October 14, 2015 Document Version 2.2 -Date | Version | Summary of Changes | Author ---------------- | ------- | --------------------------------- | --------------- -May 12, 2015 | 0 | Initial Draft | Victor Woeltjen -June 4, 2015 | 1.0 | Name changes | Victor Woeltjen -July 28, 2015 | 2.0 | Telemetry adapter tutorial | Victor Woeltjen -July 31, 2015 | 2.1 | Clarify telemetry adapter details | Victor Woeltjen -October 6, 2015 | 2.2 | Conversion to markdown | Andrew Henry +Date | Version | Summary of Changes | Author +---------------- | ------- | --------------------------------- | --------------- +May 12, 2015 | 0 | Initial Draft | Victor Woeltjen +June 4, 2015 | 1.0 | Name changes | Victor Woeltjen +July 28, 2015 | 2.0 | Telemetry adapter tutorial | Victor Woeltjen +July 31, 2015 | 2.1 | Clarify telemetry adapter details | Victor Woeltjen +October 14, 2015 | 2.2 | Conversion to markdown | Andrew Henry # Introduction From d3ff0a258e9a78b8598bed6c98710e229730c57e Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Wed, 14 Oct 2015 19:18:27 -0700 Subject: [PATCH 19/24] Added links to architecture document from services --- docs/src/guide/index.md | 62 +++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/docs/src/guide/index.md b/docs/src/guide/index.md index d06e962b44..c1a48797ab 100644 --- a/docs/src/guide/index.md +++ b/docs/src/guide/index.md @@ -1561,19 +1561,21 @@ extension mechanism is insufficient to achieve a desired result. ### Action Service -The `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#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. ### Capability Service -The capabilityServic e provides constructors for capabilities which will be -exposed for a given domain object. +The [Capability Service](../architecture/platform#capability-service) (`capabilityService`) +provides constructors for capabilities which will be exposed for a given domain +object. -Th e 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 @@ -1620,20 +1622,20 @@ option may have the following properties: when clicked. * `description`: Description to show in tooltip on hover. -## Domain Object Service +### Domain Object Service -The objectService provides domain object instances. It has the following -interface: +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. -## Gesture Service +### Gesture Service -The `gestureService` is used to attach gestures (see extension category - gestures ) to representations. It has the following interface: +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 @@ -1641,18 +1643,20 @@ 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 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. -## Persistence Service +### Persistence Service -The persistenceService provides the ability to load/store JavaScript objects +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: @@ -1678,16 +1682,18 @@ the update fails. persistence space , identified by the specified key . Returns a promise which will be rejected if deletion fails. -## Policy Service +### Policy Service -The policyService may be used to determine whether or not certain behaviors are +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](#PolicyCategories) for categories supported by the platform; plugins - may define and utilize policies of additional categories, as well. + 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 @@ -1700,9 +1706,10 @@ should be allowed. Returns a boolean. Its arguments are interpreted as: 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 `telemetryService` is used to acquire telemetry data. See the section on +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. @@ -1722,19 +1729,20 @@ 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 `typeService` exposes domain object types. It has the following interface: +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, as an array of `Type` instances. * `getType(key)`: Returns the `Type` instance identified by the provided key, or undefined if no such type exists. -## View Service +### View Service -The `viewService` exposes definitions for views of domain objects. It has the -following interface: +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`. From efb7611f6ed43d2c2b72827680023534c93b1ccd Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Thu, 15 Oct 2015 12:35:38 -0700 Subject: [PATCH 20/24] Added Table of Contents generation --- docs/gendocs.js | 17 +++ docs/src/architecture/Framework.md | 12 ++ docs/src/architecture/Platform.md | 25 ++++ docs/src/architecture/index.md | 10 ++ docs/src/guide/index.md | 198 ++++++++++++++++++++++++----- docs/src/tutorials/index.md | 74 ++++++++--- package.json | 6 +- 7 files changed, 293 insertions(+), 49 deletions(-) diff --git a/docs/gendocs.js b/docs/gendocs.js index 329a9b607f..71d8ac6bf7 100644 --- a/docs/gendocs.js +++ b/docs/gendocs.js @@ -117,6 +117,22 @@ GLOBAL.window = GLOBAL.window || GLOBAL; // nomnoml expects window to be define }; return transform; } + + function strip() { + var patternsToStrip = [ + "^ + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Overview](#overview) + - [Application Initialization](#application-initialization) + - [Architectural Paradigm](#architectural-paradigm) + - [Extension Categories](#extension-categories) + - [Composite Services](#composite-services) + + + # Overview The framework layer's most basic responsibility is allowing individual diff --git a/docs/src/architecture/Platform.md b/docs/src/architecture/Platform.md index a59a6ebf9c..cd788c3cd8 100644 --- a/docs/src/architecture/Platform.md +++ b/docs/src/architecture/Platform.md @@ -1,3 +1,28 @@ + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Overview](#overview) + - [Platform Architecture](#platform-architecture) + - [Application Start-up](#application-start-up) +- [Presentation Layer](#presentation-layer) + - [Angular built-ins](#angular-built-ins) + - [Domain object representation](#domain-object-representation) +- [Information Model](#information-model) + - [Capabilities and Services](#capabilities-and-services) +- [Service Infrastructure](#service-infrastructure) + - [Object Service](#object-service) + - [Model Service](#model-service) + - [Capability Service](#capability-service) + - [Telemetry Service](#telemetry-service) + - [Persistence Service](#persistence-service) + - [Action Service](#action-service) + - [View Service](#view-service) + - [Policy Service](#policy-service) + - [Type Service](#type-service) + + + # Overview The Open MCT Web platform utilizes the [framework layer](Framework.md) diff --git a/docs/src/architecture/index.md b/docs/src/architecture/index.md index fd9e0961e1..cbf73592db 100644 --- a/docs/src/architecture/index.md +++ b/docs/src/architecture/index.md @@ -1,3 +1,13 @@ + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Introduction](#introduction) +- [Overview](#overview) + - [Software Architecture](#software-architecture) + + + # Introduction The purpose of this document is to familiarize developers with the diff --git a/docs/src/guide/index.md b/docs/src/guide/index.md index c1a48797ab..2261511280 100644 --- a/docs/src/guide/index.md +++ b/docs/src/guide/index.md @@ -13,11 +13,149 @@ May 12, 2015 | 0.1 | | Victor Woeltjen June 4, 2015 | 1.0 | Name Changes | Victor Woeltjen October 4, 2015 | 1.1 | Conversion to MarkDown | Andrew Henry +# Contents + + + + + +- [Introduction](#introduction) + - [What is Open MCT Web](#what-is-open-mct-web) + - [Client-Server Relationship](#client-server-relationship) + - [Developing with Open MCT Web](#developing-with-open-mct-web) + - [Technologies](#technologies) + - [Forking](#forking) +- [Overview](#overview) + - [Framework Overview](#framework-overview) + - [Tiers](#tiers) + - [Platform Overview](#platform-overview) + - [Web Services](#web-services) + - [Glossary](#glossary) +- [Framework](#framework) + - [Bundles](#bundles) + - [Configuring Active Bundles](#configuring-active-bundles) + - [Bundle Definition](#bundle-definition) + - [Bundle Directory Structure](#bundle-directory-structure) + - [Extensions](#extensions) + - [General Extensions](#general-extensions) + - [Extension Definitions](#extension-definitions) + - [Partial Construction](#partial-construction) + - [Priority](#priority) + - [Angular Built-ins](#angular-built-ins) + - [Angular Directives](#angular-directives) + - [Angular Controllers](#angular-controllers) + - [Angular Services](#angular-services) + - [Angular Constants](#angular-constants) + - [Angular Runs](#angular-runs) + - [Angular Routes](#angular-routes) + - [Composite Services](#composite-services) +- [Core API](#core-api) + - [Domain Objects](#domain-objects) + - [Domain Object Actions](#domain-object-actions) + - [Action Contexts](#action-contexts) + - [Telemetry](#telemetry) + - [Telemetry Requests](#telemetry-requests) + - [Telemetry Responses](#telemetry-responses) + - [Telemetry Series](#telemetry-series) + - [Telemetry Metadata](#telemetry-metadata) + - [Types](#types) + - [Type Features](#type-features) + - [Type Properties](#type-properties) +- [Extension Categories](#extension-categories) + - [Actions Category](#actions-category) + - [Capabilities Category](#capabilities-category) + - [Controls Category](#controls-category) + - [Gestures Category](#gestures-category) + - [Indicators Category](#indicators-category) + - [Standard Indicators](#standard-indicators) + - [Custom Indicators](#custom-indicators) + - [Licenses Category](#licenses-category) + - [Policies Category](#policies-category) + - [Representations Category](#representations-category) + - [Representation Scope](#representation-scope) + - [Representers Category](#representers-category) + - [Roots Category](#roots-category) + - [Stylesheets Category](#stylesheets-category) + - [Templates Category](#templates-category) + - [Types Category](#types-category) + - [Versions Category](#versions-category) + - [Views Category](#views-category) + - [View Scope](#view-scope) + - [Selection State](#selection-state) +- [Directives](#directives) + - [Before Unload](#before-unload) + - [Chart](#chart) + - [Container](#container) + - [Control](#control) + - [Drag](#drag) + - [Form](#form) + - [Form Structure](#form-structure) + - [Form Controls](#form-controls) + - [Include](#include) + - [Representation](#representation) + - [Resize](#resize) + - [Scroll](#scroll) + - [Toolbar](#toolbar) + - [Toolbar Structure](#toolbar-structure) +- [Services](#services) + - [Composite Type Services](#composite-type-services) + - [Action Service](#action-service) + - [Capability Service](#capability-service) + - [Dialog Service](#dialog-service) + - [Dialog Structure](#dialog-structure) + - [Domain Object Service](#domain-object-service) + - [Gesture Service](#gesture-service) + - [Model Service](#model-service) + - [Persistence Service](#persistence-service) + - [Policy Service](#policy-service) + - [Telemetry Service](#telemetry-service) + - [Type Service](#type-service) + - [View Service](#view-service) + - [Other Services](#other-services) + - [Drag and Drop](#drag-and-drop) + - [Navigation](#navigation) + - [Now](#now) + - [Telemetry Formatter](#telemetry-formatter) + - [Telemetry Handler](#telemetry-handler) + - [Telemetry Handle](#telemetry-handle) +- [Models](#models) + - [General Metadata](#general-metadata) + - [Extension-specific Properties](#extension-specific-properties) + - [Capability-specific Properties](#capability-specific-properties) + - [View Configurations](#view-configurations) + - [Modifying Models](#modifying-models) +- [Capabilities](#capabilities) + - [Action Capability](#action-capability) + - [Composition Capability](#composition-capability) + - [Delegation Capability](#delegation-capability) + - [Editor Capability](#editor-capability) + - [Mutation Capability](#mutation-capability) + - [Mutator Function](#mutator-function) + - [Persistence Capability](#persistence-capability) + - [Relationship Capability](#relationship-capability) + - [Telemetry Capability](#telemetry-capability) + - [Type Capability](#type-capability) + - [View Capability](#view-capability) +- [Actions](#actions) + - [Action Categories](#action-categories) + - [Platform Actions](#platform-actions) +- [Policies](#policies) + - [Policy Categories](#policy-categories) +- [Build-Test-Deploy](#build-test-deploy) + - [Command-line Build](#command-line-build) + - [Test Suite](#test-suite) + - [Code Coverage](#code-coverage) + - [Deployment](#deployment) + - [Configuration](#configuration) + - [Configuration Constants](#configuration-constants) + + + # Introduction The purpose of this guide is to familiarize software developers with the Open MCT Web platform. -## What is Open MCT Web? +## 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 @@ -676,7 +814,7 @@ If the provided capability has no invoke method, the return value here functions as `getCapability` including returning `undefined` if the capability is not exposed. -## Actions +## Domain Object Actions An `Action` is behavior that can be performed upon/using a `DomainObject`. An Action has the following interface: @@ -820,7 +958,7 @@ 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 +## Actions Category An action is a thing that can be done to or using a domain object, typically as initiated by the user. @@ -853,7 +991,7 @@ Categories supported by the platform include: * `glyph`: A single character which will be rendered in Open MCT Web's custom font set as an icon for this action. -## Capabilities +## Capabilities Category Capabilities are exposed by domain objects (e.g. via the `getCapability` method) but most commonly originate as extensions of this category. @@ -876,7 +1014,7 @@ 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 +## Controls Category Controls provide options for the `mct-control` directive. @@ -911,7 +1049,7 @@ of an individual row definition. * `field`: Name of the field in `ngModel` which will hold the value for this control. -## Gestures +## Gestures Category A _gesture_ is a user action which can be taken upon a representation of a domain object. @@ -935,7 +1073,7 @@ 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 +## 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. @@ -971,7 +1109,7 @@ 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 +## 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. @@ -985,7 +1123,7 @@ Licenses may have the following properties, all of which are strings: * `copyright`: Copyright text to display for this component. * `link`: URL to full license text. -## Policies +## 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 @@ -1012,7 +1150,7 @@ 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 +## 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 @@ -1062,7 +1200,7 @@ representation state. * Any capabilities requested by the uses property of the representation definition. -## Representers +## Representers Category The `representers` extension category is used to add additional behavior to the `mct-representation` directive. This extension category is intended primarily @@ -1086,7 +1224,7 @@ 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. -## Roots +## 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. @@ -1097,7 +1235,7 @@ 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. -## Stylesheets +## Stylesheets Category The stylesheets extension category is used to add CSS files to style the application. Extension definitions for this category should include one @@ -1110,7 +1248,7 @@ include. This path is relative to the bundle's resources folder (by default, To control the order of CSS files, use priority (see the section on Extension Definitions above.) -## Templates +## Templates Category The `templates` extension category is used to expose Angular templates under symbolic identifiers. These can then be utilized using the `mct-include` @@ -1131,7 +1269,7 @@ in the bottom right, for instance.) Templates do not have implementations. -## Types +## Types Category The types extension category describes types of domain objects which may appear within Open MCT Web. @@ -1168,7 +1306,7 @@ property is described by an object containing the following properties: Types do not have implementations. -## Versions +## 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: @@ -1182,7 +1320,7 @@ To control the ordering of line items within the About dialog, use `priority`. This extension category does not have implementations. -## Views +## 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 @@ -1536,7 +1674,7 @@ 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 +## Composite Type Services This section describes the composite services exposed by Open MCT Web, specifically focusing on their interface and contract. @@ -1924,7 +2062,7 @@ 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 +## 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. @@ -1946,7 +2084,7 @@ 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 +## Composition Capability The `composition` capability provides access to domain objects that are contained by this domain object. While the `composition` property of a domain @@ -1959,7 +2097,7 @@ This capability has the following interface: * `invoke()`: Returns a `Promise` for an array of `DomainObject` instances. -## Delegation +## Delegation Capability The delegation capability is used to communicate the intent of a domain object to delegate responsibilities, which would normally handled by other @@ -1980,7 +2118,7 @@ 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 +## 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 @@ -1988,7 +2126,7 @@ _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 +## 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 @@ -2005,7 +2143,7 @@ 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 +### 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: @@ -2019,7 +2157,7 @@ for this domain object. (including any changes made in place by the mutator function) will be used as the new domain object model. -## Persistence +## 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 @@ -2034,7 +2172,7 @@ completed. * `getSpace()`: Return the string which identifies the persistence space which stores this domain object. -## Relationship +## 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 @@ -2051,7 +2189,7 @@ 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 +## Telemetry Capability The telemetry capability provides a means for accessing telemetry data associated with a domain object. It has the following interface: @@ -2074,11 +2212,11 @@ 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 +## Type Capability The `type` capability exposes information about the domain object's type. It has the same interface as `Type`; see Core API. -## View +## View Capability The `view` capability exposes views which are applicable to a given domain object. It has the following interface: @@ -2145,7 +2283,7 @@ contained. The candidate argument is the view's extension definition; the context argument is the `DomainObject` to be viewed. -# Build, Test, Deploy +# Build-Test-Deploy Open MCT Web is designed to support a broad variety of build and deployment options. The sources can be deployed in the same directory structure used during development. A few utilities are included to support development processes. diff --git a/docs/src/tutorials/index.md b/docs/src/tutorials/index.md index 5c6ac98691..c459066969 100644 --- a/docs/src/tutorials/index.md +++ b/docs/src/tutorials/index.md @@ -14,6 +14,46 @@ July 28, 2015 | 2.0 | Telemetry adapter tutorial | Victor Woeltjen July 31, 2015 | 2.1 | Clarify telemetry adapter details | Victor Woeltjen October 14, 2015 | 2.2 | Conversion to markdown | Andrew Henry +# Contents + + + + + +- [Introduction](#introduction) + - [This document](#this-document) + - [Setting Up Open MCT Web](#setting-up-open-mct-web) + - [Prerequisites](#prerequisites) + - [Check out Open MCT Web Sources](#check-out-open-mct-web-sources) + - [Configuring Persistence](#configuring-persistence) + - [Bundle Before](#bundle-before) + - [Bundle After](#bundle-after) + - [Run a Web Server](#run-a-web-server) + - [Viewing in Browser](#viewing-in-browser) +- [Tutorials](#tutorials) + - [To-do List](#to-do-list) + - [Step 1-Create the Plugin](#step-1-create-the-plugin) + - [Before](#before) + - [After](#after) + - [Step 2-Add a Domain Object Type](#step-2-add-a-domain-object-type) + - [Step 3-Add a View](#step-3-add-a-view) + - [Step 4-Add a Controller](#step-4-add-a-controller) + - [Step 5-Support Editing](#step-5-support-editing) + - [Step 6-Customizing Look and Feel](#step-6-customizing-look-and-feel) + - [Bar Graph](#bar-graph) + - [Step 1-Define the View](#step-1-define-the-view) + - [Step 2-Add a Controller](#step-2-add-a-controller) + - [Step 3-Using Telemetry Data](#step-3-using-telemetry-data) + - [Step 4-View Configuration](#step-4-view-configuration) + - [Telemetry Adapter](#telemetry-adapter) + - [Step 0-Expose Your Telemetry](#step-0-expose-your-telemetry) + - [Step 1-Add a Top-level Object](#step-1-add-a-top-level-object) + - [Step 2-Expose the Telemetry Dictionary](#step-2-expose-the-telemetry-dictionary) + - [Step 3-Historical Telemetry](#step-3-historical-telemetry) + - [Step 4-Real-time Telemetry](#step-4-real-time-telemetry) + + + # Introduction ## This document @@ -76,7 +116,7 @@ To change this configuration, edit bundles.json (at the top level of the Open MCT Web repository) and replace platform/persistence/elastic with example/persistence. -#### Before +#### Bundle Before [ "platform/framework", @@ -102,7 +142,7 @@ example/persistence. ] __bundles.json__ -#### After +#### Bundle After [ "platform/framework", @@ -167,7 +207,7 @@ 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/](). -### Step 1. Create the Plugin +### Step 1-Create the Plugin The first step to adding a new feature to Open MCT Web is to create the plugin which will expose that feature. A plugin in Open MCT Web is represented by what @@ -254,7 +294,7 @@ should see: ...which shows that our plugin has loaded. -### Step 2. Add a Domain Object Type +### Step 2-Add a Domain Object Type Features in a Open MCT Web application are most commonly expressed as domain objects and/or views thereof. A domain object is some thing that is relevant to @@ -311,7 +351,7 @@ 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 +### 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 @@ -447,7 +487,7 @@ 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 their initial state. -### Step 4. Add a Controller +### Step 4-Add a Controller We need to do some scripting to add dynamic behavior to that view. In particular, we want to: @@ -613,7 +653,7 @@ able to filter down the visible list, and the changes we make will stick around if we go to My Items and come back. -### Step 5. Support Editing +### Step 5-Support Editing We now have a somewhat-functional view of our To-Do List, but we’re still missing some important functionality: Adding and removing tasks! @@ -938,7 +978,7 @@ button appear, which we can then click on to remove that task: 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. -### Step 6. Customizing Look and Feel +### Step 6-Customizing Look and Feel In this section, our goal is to: @@ -1227,7 +1267,7 @@ It is recommended that the reader completes (or is familiar with) the To-Do List tutorial before completing this tutorial, as certain concepts discussed there will be addressed in more brevity here. -### Step 1. Define the View +### Step 1-Define the View 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`. @@ -1405,7 +1445,7 @@ _Sine Wave Generator_) as well as for _Telemetry Panel_ objects: This means that our remaining work will be to populate and position these elements based on the actual contents of the domain object. -### Step 2. Add a Controller +### Step 2-Add a Controller Our next step will be to begin dynamically populating this template’s contents. Specifically, our goals for this step will be to: @@ -1556,7 +1596,7 @@ this Telemetry Panel containing four Sine Wave Generators. ![Bar Plot](images/bar-plot-2.png) -### Step 3. Using Telemetry Data +### Step 3-Using Telemetry Data Now that our bar graph is labeled correctly, it’s time to start putting data into the view. @@ -1660,7 +1700,7 @@ When we reload Open MCT Web, our bar graph view now looks like: ![Bar Plot](images/bar-plot-3.png) -### Step 4. View Configuration +### Step 4-View Configuration 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 @@ -1849,7 +1889,7 @@ A summary of the steps we will take: * Support subscription/unsubscription to real-time streaming data. * Support historical retrieval of telemetry data. -### Step 0. Expose Your Telemetry +### Step 0-Expose Your Telemetry As a precondition to integrating telemetry data into Open MCT Web, this information needs to be available over web-based interfaces. In practice, @@ -2129,7 +2169,7 @@ like [https://www.npmjs.com/package/wscat](): 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 +### 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 @@ -2226,7 +2266,7 @@ __bundles.json__ Now, we have somewhere in the UI to put the contents of our telemetry dictionary. -### Step 2. Expose the Telemetry Dictionary +### Step 2-Expose the Telemetry Dictionary In order to expose the telemetry dictionary, we first need to read it from the server. Our first step will be to add a service that will handle interactions @@ -2599,7 +2639,7 @@ dictionary: Note that “My Spacecraft” has changed its name to “Example Spacecraft”, which is the name it had in the dictionary. -### Step 3. Historical Telemetry +### 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 @@ -2870,7 +2910,7 @@ 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 +### Step 4-Real-time 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 diff --git a/package.json b/package.json index a1ac01f437..9ddfdefa69 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "split": "^1.0.0", "mkdirp": "^0.5.1", "nomnoml": "^0.0.3", - "canvas": "^1.2.7" + "canvas": "^1.2.7", + "doctoc": "0.15.0" }, "scripts": { "start": "node app.js", @@ -30,8 +31,9 @@ "jshint": "jshint platform example || exit 0", "watch": "karma start", "jsdoc": "jsdoc -c jsdoc.json -r -d target/docs/api", + "doctoc": "doctoc docs/src", "otherdoc": "node docs/gendocs.js --in docs/src --out target/docs", - "docs": "npm run jsdoc ; npm run otherdoc" + "docs": "npm run jsdoc ; npm run doctoc; npm run otherdoc" }, "repository": { "type": "git", From 688718cad0070acc5d0452cef7a3e384971dc9f6 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 15 Oct 2015 13:10:03 -0700 Subject: [PATCH 21/24] Fixed jslint errors --- docs/gendocs.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/gendocs.js b/docs/gendocs.js index 71d8ac6bf7..8f375aec0f 100644 --- a/docs/gendocs.js +++ b/docs/gendocs.js @@ -120,9 +120,8 @@ GLOBAL.window = GLOBAL.window || GLOBAL; // nomnoml expects window to be define function strip() { var patternsToStrip = [ - "^ - -**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - -- [Overview](#overview) - - [Application Initialization](#application-initialization) - - [Architectural Paradigm](#architectural-paradigm) - - [Extension Categories](#extension-categories) - - [Composite Services](#composite-services) - - - # Overview The framework layer's most basic responsibility is allowing individual diff --git a/docs/src/architecture/Platform.md b/docs/src/architecture/Platform.md index cd788c3cd8..a59a6ebf9c 100644 --- a/docs/src/architecture/Platform.md +++ b/docs/src/architecture/Platform.md @@ -1,28 +1,3 @@ - - -**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - -- [Overview](#overview) - - [Platform Architecture](#platform-architecture) - - [Application Start-up](#application-start-up) -- [Presentation Layer](#presentation-layer) - - [Angular built-ins](#angular-built-ins) - - [Domain object representation](#domain-object-representation) -- [Information Model](#information-model) - - [Capabilities and Services](#capabilities-and-services) -- [Service Infrastructure](#service-infrastructure) - - [Object Service](#object-service) - - [Model Service](#model-service) - - [Capability Service](#capability-service) - - [Telemetry Service](#telemetry-service) - - [Persistence Service](#persistence-service) - - [Action Service](#action-service) - - [View Service](#view-service) - - [Policy Service](#policy-service) - - [Type Service](#type-service) - - - # Overview The Open MCT Web platform utilizes the [framework layer](Framework.md) diff --git a/docs/src/architecture/index.md b/docs/src/architecture/index.md index cbf73592db..fd9e0961e1 100644 --- a/docs/src/architecture/index.md +++ b/docs/src/architecture/index.md @@ -1,13 +1,3 @@ - - -**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - -- [Introduction](#introduction) -- [Overview](#overview) - - [Software Architecture](#software-architecture) - - - # Introduction The purpose of this document is to familiarize developers with the diff --git a/docs/src/guide/index.md b/docs/src/guide/index.md index 2261511280..7b35dd66cc 100644 --- a/docs/src/guide/index.md +++ b/docs/src/guide/index.md @@ -13,144 +13,6 @@ May 12, 2015 | 0.1 | | Victor Woeltjen June 4, 2015 | 1.0 | Name Changes | Victor Woeltjen October 4, 2015 | 1.1 | Conversion to MarkDown | Andrew Henry -# Contents - - - - - -- [Introduction](#introduction) - - [What is Open MCT Web](#what-is-open-mct-web) - - [Client-Server Relationship](#client-server-relationship) - - [Developing with Open MCT Web](#developing-with-open-mct-web) - - [Technologies](#technologies) - - [Forking](#forking) -- [Overview](#overview) - - [Framework Overview](#framework-overview) - - [Tiers](#tiers) - - [Platform Overview](#platform-overview) - - [Web Services](#web-services) - - [Glossary](#glossary) -- [Framework](#framework) - - [Bundles](#bundles) - - [Configuring Active Bundles](#configuring-active-bundles) - - [Bundle Definition](#bundle-definition) - - [Bundle Directory Structure](#bundle-directory-structure) - - [Extensions](#extensions) - - [General Extensions](#general-extensions) - - [Extension Definitions](#extension-definitions) - - [Partial Construction](#partial-construction) - - [Priority](#priority) - - [Angular Built-ins](#angular-built-ins) - - [Angular Directives](#angular-directives) - - [Angular Controllers](#angular-controllers) - - [Angular Services](#angular-services) - - [Angular Constants](#angular-constants) - - [Angular Runs](#angular-runs) - - [Angular Routes](#angular-routes) - - [Composite Services](#composite-services) -- [Core API](#core-api) - - [Domain Objects](#domain-objects) - - [Domain Object Actions](#domain-object-actions) - - [Action Contexts](#action-contexts) - - [Telemetry](#telemetry) - - [Telemetry Requests](#telemetry-requests) - - [Telemetry Responses](#telemetry-responses) - - [Telemetry Series](#telemetry-series) - - [Telemetry Metadata](#telemetry-metadata) - - [Types](#types) - - [Type Features](#type-features) - - [Type Properties](#type-properties) -- [Extension Categories](#extension-categories) - - [Actions Category](#actions-category) - - [Capabilities Category](#capabilities-category) - - [Controls Category](#controls-category) - - [Gestures Category](#gestures-category) - - [Indicators Category](#indicators-category) - - [Standard Indicators](#standard-indicators) - - [Custom Indicators](#custom-indicators) - - [Licenses Category](#licenses-category) - - [Policies Category](#policies-category) - - [Representations Category](#representations-category) - - [Representation Scope](#representation-scope) - - [Representers Category](#representers-category) - - [Roots Category](#roots-category) - - [Stylesheets Category](#stylesheets-category) - - [Templates Category](#templates-category) - - [Types Category](#types-category) - - [Versions Category](#versions-category) - - [Views Category](#views-category) - - [View Scope](#view-scope) - - [Selection State](#selection-state) -- [Directives](#directives) - - [Before Unload](#before-unload) - - [Chart](#chart) - - [Container](#container) - - [Control](#control) - - [Drag](#drag) - - [Form](#form) - - [Form Structure](#form-structure) - - [Form Controls](#form-controls) - - [Include](#include) - - [Representation](#representation) - - [Resize](#resize) - - [Scroll](#scroll) - - [Toolbar](#toolbar) - - [Toolbar Structure](#toolbar-structure) -- [Services](#services) - - [Composite Type Services](#composite-type-services) - - [Action Service](#action-service) - - [Capability Service](#capability-service) - - [Dialog Service](#dialog-service) - - [Dialog Structure](#dialog-structure) - - [Domain Object Service](#domain-object-service) - - [Gesture Service](#gesture-service) - - [Model Service](#model-service) - - [Persistence Service](#persistence-service) - - [Policy Service](#policy-service) - - [Telemetry Service](#telemetry-service) - - [Type Service](#type-service) - - [View Service](#view-service) - - [Other Services](#other-services) - - [Drag and Drop](#drag-and-drop) - - [Navigation](#navigation) - - [Now](#now) - - [Telemetry Formatter](#telemetry-formatter) - - [Telemetry Handler](#telemetry-handler) - - [Telemetry Handle](#telemetry-handle) -- [Models](#models) - - [General Metadata](#general-metadata) - - [Extension-specific Properties](#extension-specific-properties) - - [Capability-specific Properties](#capability-specific-properties) - - [View Configurations](#view-configurations) - - [Modifying Models](#modifying-models) -- [Capabilities](#capabilities) - - [Action Capability](#action-capability) - - [Composition Capability](#composition-capability) - - [Delegation Capability](#delegation-capability) - - [Editor Capability](#editor-capability) - - [Mutation Capability](#mutation-capability) - - [Mutator Function](#mutator-function) - - [Persistence Capability](#persistence-capability) - - [Relationship Capability](#relationship-capability) - - [Telemetry Capability](#telemetry-capability) - - [Type Capability](#type-capability) - - [View Capability](#view-capability) -- [Actions](#actions) - - [Action Categories](#action-categories) - - [Platform Actions](#platform-actions) -- [Policies](#policies) - - [Policy Categories](#policy-categories) -- [Build-Test-Deploy](#build-test-deploy) - - [Command-line Build](#command-line-build) - - [Test Suite](#test-suite) - - [Code Coverage](#code-coverage) - - [Deployment](#deployment) - - [Configuration](#configuration) - - [Configuration Constants](#configuration-constants) - - - # Introduction The purpose of this guide is to familiarize software developers with the Open MCT Web platform. diff --git a/docs/src/tutorials/index.md b/docs/src/tutorials/index.md index 720c80c828..d07466abac 100644 --- a/docs/src/tutorials/index.md +++ b/docs/src/tutorials/index.md @@ -14,46 +14,6 @@ July 28, 2015 | 2.0 | Telemetry adapter tutorial | Victor Woeltjen July 31, 2015 | 2.1 | Clarify telemetry adapter details | Victor Woeltjen October 14, 2015 | 2.2 | Conversion to markdown | Andrew Henry -# Contents - - - - - -- [Introduction](#introduction) - - [This document](#this-document) - - [Setting Up Open MCT Web](#setting-up-open-mct-web) - - [Prerequisites](#prerequisites) - - [Check out Open MCT Web Sources](#check-out-open-mct-web-sources) - - [Configuring Persistence](#configuring-persistence) - - [Bundle Before](#bundle-before) - - [Bundle After](#bundle-after) - - [Run a Web Server](#run-a-web-server) - - [Viewing in Browser](#viewing-in-browser) -- [Tutorials](#tutorials) - - [To-do List](#to-do-list) - - [Step 1-Create the Plugin](#step-1-create-the-plugin) - - [Before](#before) - - [After](#after) - - [Step 2-Add a Domain Object Type](#step-2-add-a-domain-object-type) - - [Step 3-Add a View](#step-3-add-a-view) - - [Step 4-Add a Controller](#step-4-add-a-controller) - - [Step 5-Support Editing](#step-5-support-editing) - - [Step 6-Customizing Look and Feel](#step-6-customizing-look-and-feel) - - [Bar Graph](#bar-graph) - - [Step 1-Define the View](#step-1-define-the-view) - - [Step 2-Add a Controller](#step-2-add-a-controller) - - [Step 3-Using Telemetry Data](#step-3-using-telemetry-data) - - [Step 4-View Configuration](#step-4-view-configuration) - - [Telemetry Adapter](#telemetry-adapter) - - [Step 0-Expose Your Telemetry](#step-0-expose-your-telemetry) - - [Step 1-Add a Top-level Object](#step-1-add-a-top-level-object) - - [Step 2-Expose the Telemetry Dictionary](#step-2-expose-the-telemetry-dictionary) - - [Step 3-Historical Telemetry](#step-3-historical-telemetry) - - [Step 4-Real-time Telemetry](#step-4-real-time-telemetry) - - - # Introduction ## This document