diff --git a/API.md b/API.md index c5f27843c2..a56a0de187 100644 --- a/API.md +++ b/API.md @@ -52,6 +52,8 @@ - [The URL Status Indicator](#the-url-status-indicator) - [Creating a Simple Indicator](#creating-a-simple-indicator) - [Custom Indicators](#custom-indicators) + - [Priority API](#priority-api) + - [Priority Types](#priority-types) @@ -247,16 +249,24 @@ To do so, use the `addRoot` method of the object API. eg. ```javascript openmct.objects.addRoot({ - namespace: "example.namespace", - key: "my-key" - }); + namespace: "example.namespace", + key: "my-key" +}, +openmct.priority.HIGH); ``` -The `addRoot` function takes a single [object identifier](#domain-objects-and-identifiers) -as an argument. +The `addRoot` function takes a two arguments, the first can be an [object identifier](#domain-objects-and-identifiers) for a root level object, or an array of identifiers for root +level objects, or a function that returns a promise for an identifier or an array of root level objects, the second is a [priority](#priority-api) or numeric value. -Root objects are loaded just like any other objects, i.e. via an object -provider. +When using the `getAll` method of the object API, they will be returned in order of priority. + +eg. +```javascript +openmct.objects.addRoot(identifier, openmct.priority.LOW); // low = -1000, will appear last in composition or tree +openmct.objects.addRoot(otherIdentifier, openmct.priority.HIGH); // high = 1000, will appear first in composition or tree +``` + +Root objects are loaded just like any other objects, i.e. via an object provider. ## Object Providers @@ -1051,3 +1061,25 @@ A completely custom indicator can be added by simply providing a DOM element to element: domNode }); ``` + +## Priority API + +Open MCT provides some built-in priority values that can be used in the application for view providers, indicators, root object order, and more. + +### Priority Types + +Currently, the Open MCT Priority API provides (type: numeric value): +- HIGH: 1000 +- Default: 0 +- LOW: -1000 + +View provider Example: + +``` javascript + class ViewProvider { + ... + priority() { + return openmct.priority.HIGH; + } +} +``` \ No newline at end of file diff --git a/src/api/objects/ObjectAPI.js b/src/api/objects/ObjectAPI.js index 56fcde50e7..4444f085b2 100644 --- a/src/api/objects/ObjectAPI.js +++ b/src/api/objects/ObjectAPI.js @@ -41,7 +41,7 @@ function ObjectAPI(typeRegistry, openmct) { this.typeRegistry = typeRegistry; this.eventEmitter = new EventEmitter(); this.providers = {}; - this.rootRegistry = new RootRegistry(); + this.rootRegistry = new RootRegistry(openmct); this.inMemorySearchProvider = new InMemorySearchProvider(openmct); this.rootProvider = new RootObjectProvider(this.rootRegistry); @@ -367,14 +367,17 @@ ObjectAPI.prototype.endTransaction = function () { /** * Add a root-level object. - * @param {module:openmct.ObjectAPI~Identifier|function} an array of - * identifiers for root level objects, or a function that returns a + * @param {module:openmct.ObjectAPI~Identifier|array|function} identifier an identifier or + * an array of identifiers for root level objects, or a function that returns a * promise for an identifier or an array of root level objects. + * @param {module:openmct.PriorityAPI~priority|Number} priority a number representing + * this item(s) position in the root object's composition (example: order in object tree). + * For arrays, they are treated as blocks. * @method addRoot * @memberof module:openmct.ObjectAPI# */ -ObjectAPI.prototype.addRoot = function (key) { - this.rootRegistry.addRoot(key); +ObjectAPI.prototype.addRoot = function (identifier, priority) { + this.rootRegistry.addRoot(identifier, priority); }; /** diff --git a/src/api/objects/RootRegistry.js b/src/api/objects/RootRegistry.js index a2406c14f1..bfb682f9dc 100644 --- a/src/api/objects/RootRegistry.js +++ b/src/api/objects/RootRegistry.js @@ -20,39 +20,43 @@ * at runtime from the About dialog for additional information. *****************************************************************************/ -define([ - 'lodash' -], function ( - _ -) { +import utils from './object-utils'; - function RootRegistry() { - this.providers = []; +export default class RootRegistry { + + constructor(openmct) { + this._rootItems = []; + this._openmct = openmct; } - RootRegistry.prototype.getRoots = function () { - const promises = this.providers.map(function (provider) { - return provider(); - }); + getRoots() { + const sortedItems = this._rootItems.sort((a, b) => b.priority - a.priority); + const promises = sortedItems.map((rootItem) => rootItem.provider()); - return Promise.all(promises) - .then(_.flatten); - }; - - function isKey(key) { - return _.isObject(key) && _.has(key, 'key') && _.has(key, 'namespace'); + return Promise.all(promises).then(rootItems => rootItems.flat()); } - RootRegistry.prototype.addRoot = function (key) { - if (isKey(key) || (Array.isArray(key) && key.every(isKey))) { - this.providers.push(function () { - return key; - }); - } else if (typeof key === "function") { - this.providers.push(key); + addRoot(rootItem, priority) { + + if (!this._isValid(rootItem)) { + return; } - }; - return RootRegistry; + this._rootItems.push({ + priority: priority || this._openmct.priority.DEFAULT, + provider: typeof rootItem === 'function' ? rootItem : () => rootItem + }); + } -}); + _isValid(rootItem) { + if (utils.isIdentifier(rootItem) || typeof rootItem === 'function') { + return true; + } + + if (Array.isArray(rootItem)) { + return rootItem.every(utils.isIdentifier); + } + + return false; + } +} diff --git a/src/api/objects/object-utils.js b/src/api/objects/object-utils.js index 41e556974e..a3464aebaa 100644 --- a/src/api/objects/object-utils.js +++ b/src/api/objects/object-utils.js @@ -172,6 +172,7 @@ define([ } return { + isIdentifier: isIdentifier, toOldFormat: toOldFormat, toNewFormat: toNewFormat, makeKeyString: makeKeyString, diff --git a/src/api/objects/test/RootRegistrySpec.js b/src/api/objects/test/RootRegistrySpec.js index b10e955e9c..8cc0c77146 100644 --- a/src/api/objects/test/RootRegistrySpec.js +++ b/src/api/objects/test/RootRegistrySpec.js @@ -19,83 +19,113 @@ * this source code distribution or the Licensing information page available * at runtime from the About dialog for additional information. *****************************************************************************/ -define([ - '../RootRegistry' -], function ( - RootRegistry -) { - describe('RootRegistry', function () { - let idA; - let idB; - let idC; - let registry; - beforeEach(function () { - idA = { - key: 'keyA', - namespace: 'something' - }; - idB = { - key: 'keyB', - namespace: 'something' - }; - idC = { - key: 'keyC', - namespace: 'something' - }; - registry = new RootRegistry(); - }); +import { createOpenMct, resetApplicationState } from '../../../utils/testing'; - it('can register a root by key', function () { - registry.addRoot(idA); +describe('RootRegistry', () => { + let openmct; + let idA; + let idB; + let idC; + let idD; - return registry.getRoots() - .then(function (roots) { - expect(roots).toEqual([idA]); - }); - }); + beforeEach((done) => { + openmct = createOpenMct(); + idA = { + key: 'keyA', + namespace: 'something' + }; + idB = { + key: 'keyB', + namespace: 'something' + }; + idC = { + key: 'keyC', + namespace: 'something' + }; + idD = { + key: 'keyD', + namespace: 'something' + }; - it('can register multiple roots by key', function () { - registry.addRoot([idA, idB]); + openmct.on('start', done); + openmct.startHeadless(); + }); - return registry.getRoots() - .then(function (roots) { - expect(roots).toEqual([idA, idB]); - }); - }); + afterEach(async () => { + await resetApplicationState(openmct); + }); - it('can register an asynchronous root ', function () { - registry.addRoot(function () { - return Promise.resolve(idA); + it('can register a root by identifier', () => { + openmct.objects.addRoot(idA); + + return openmct.objects.getRoot() + .then((rootObject) => { + expect(rootObject.composition).toEqual([idA]); }); + }); - return registry.getRoots() - .then(function (roots) { - expect(roots).toEqual([idA]); - }); - }); + it('can register multiple roots by identifier', () => { + openmct.objects.addRoot([idA, idB]); - it('can register multiple asynchronous roots', function () { - registry.addRoot(function () { - return Promise.resolve([idA, idB]); + return openmct.objects.getRoot() + .then((rootObject) => { + expect(rootObject.composition).toEqual([idA, idB]); }); + }); - return registry.getRoots() - .then(function (roots) { - expect(roots).toEqual([idA, idB]); - }); - }); + it('can register an asynchronous root ', () => { + openmct.objects.addRoot(() => Promise.resolve(idA)); - it('can combine different types of registration', function () { - registry.addRoot([idA, idB]); - registry.addRoot(function () { - return Promise.resolve([idC]); + return openmct.objects.getRoot() + .then((rootObject) => { + expect(rootObject.composition).toEqual([idA]); }); + }); - return registry.getRoots() - .then(function (roots) { - expect(roots).toEqual([idA, idB, idC]); - }); - }); + it('can register multiple asynchronous roots', () => { + openmct.objects.addRoot(() => Promise.resolve([idA, idB])); + + return openmct.objects.getRoot() + .then((rootObject) => { + expect(rootObject.composition).toEqual([idA, idB]); + }); + }); + + it('can combine different types of registration', () => { + openmct.objects.addRoot([idA, idB]); + openmct.objects.addRoot(() => Promise.resolve([idC])); + + return openmct.objects.getRoot() + .then((rootObject) => { + expect(rootObject.composition).toEqual([idA, idB, idC]); + }); + }); + + it('supports priority ordering for identifiers', () => { + openmct.objects.addRoot(idA, openmct.priority.LOW); + openmct.objects.addRoot(idB, openmct.priority.HIGH); + openmct.objects.addRoot(idC); // DEFAULT + + return openmct.objects.getRoot() + .then((rootObject) => { + expect(rootObject.composition[0]).toEqual(idB); + expect(rootObject.composition[1]).toEqual(idC); + expect(rootObject.composition[2]).toEqual(idA); + }); + }); + + it('supports priority ordering for different types of registration', () => { + openmct.objects.addRoot(() => Promise.resolve([idC]), openmct.priority.LOW); + openmct.objects.addRoot(idB, openmct.priority.HIGH); + openmct.objects.addRoot([idA, idD]); // default + + return openmct.objects.getRoot() + .then((rootObject) => { + expect(rootObject.composition[0]).toEqual(idB); + expect(rootObject.composition[1]).toEqual(idA); + expect(rootObject.composition[2]).toEqual(idD); + expect(rootObject.composition[3]).toEqual(idC); + }); }); }); diff --git a/src/plugins/myItems/plugin.js b/src/plugins/myItems/plugin.js index 2b95fa8791..cce0f5113a 100644 --- a/src/plugins/myItems/plugin.js +++ b/src/plugins/myItems/plugin.js @@ -25,11 +25,15 @@ import myItemsInterceptor from "./myItemsInterceptor"; const MY_ITEMS_DEFAULT_NAME = 'My Items'; -export default function MyItemsPlugin(name = MY_ITEMS_DEFAULT_NAME, namespace = '') { +export default function MyItemsPlugin(name = MY_ITEMS_DEFAULT_NAME, namespace = '', priority = undefined) { return function install(openmct) { const identifier = createMyItemsIdentifier(namespace); + if (priority === undefined) { + priority = openmct.priority.LOW; + } + openmct.objects.addGetInterceptor(myItemsInterceptor(openmct, identifier, name)); - openmct.objects.addRoot(identifier); + openmct.objects.addRoot(identifier, priority); }; }