diff --git a/bundles.json b/bundles.json index 6ebe71b189..f87f035720 100644 --- a/bundles.json +++ b/bundles.json @@ -29,6 +29,7 @@ "platform/policy", "platform/entanglement", "platform/search", + "platform/status", "example/imagery", "example/eventGenerator", diff --git a/platform/status/README.md b/platform/status/README.md new file mode 100644 index 0000000000..4d8cb7e6fa --- /dev/null +++ b/platform/status/README.md @@ -0,0 +1,2 @@ +Facilitates tracking states associated with specific domain +objects. diff --git a/platform/status/bundle.json b/platform/status/bundle.json new file mode 100644 index 0000000000..4c117a9d75 --- /dev/null +++ b/platform/status/bundle.json @@ -0,0 +1,23 @@ +{ + "extensions": { + "representers": [ + { + "implementation": "StatusRepresenter.js" + } + ], + "capabilities": [ + { + "key": "status", + "implementation": "StatusCapability.js", + "depends": [ "statusService" ] + } + ], + "services": [ + { + "key": "statusService", + "implementation": "StatusService.js", + "depends": [ "topic" ] + } + ] + } +} diff --git a/platform/status/src/StatusCapability.js b/platform/status/src/StatusCapability.js new file mode 100644 index 0000000000..2751c198a9 --- /dev/null +++ b/platform/status/src/StatusCapability.js @@ -0,0 +1,91 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define*/ + +define( + [], + function () { + 'use strict'; + + /** + * The `status` capability can be used to attach information + * about the state of a domain object, expressed as simple + * string flags. + * + * Representations of domain objects will also receive CSS + * classes which reflect their current status. + * (@see platform/status.StatusRepresenter) + * + * @param {platform/status.StatusService} statusService + * the service which will track domain object status + * within the application. + * @param {DomainObject} the domain object whose status will + * be tracked. + * @constructor + * @memberof platform/status + */ + function StatusCapability(statusService, domainObject) { + this.statusService = statusService; + this.domainObject = domainObject; + } + + /** + * Get all status flags currently set for this domain object. + * @returns {string[]} all current status flags. + */ + StatusCapability.prototype.get = function () { + return this.statusService.getStatus(this.domainObject.getId()); + }; + + /** + * Set a status flag on this domain object. + * @param {string} status the status to set + * @param {boolean} state true if the domain object should + * possess this status, false if it should not + */ + StatusCapability.prototype.set = function (status, state) { + return this.statusService.setStatus( + this.domainObject.getId(), + status, + state + ); + }; + + /** + * Listen for changes in this domain object's status. + * @param {Function} callback function to invoke on changes; + * called with the new status of the domain object, as an + * array of strings + * @returns {Function} a function which can be used to stop + * listening to status changes for this domain object. + */ + StatusCapability.prototype.listen = function (callback) { + return this.statusService.listen( + this.domainObject.getId(), + callback + ); + }; + + return StatusCapability; + + } +); diff --git a/platform/status/src/StatusConstants.js b/platform/status/src/StatusConstants.js new file mode 100644 index 0000000000..75b1bca2e1 --- /dev/null +++ b/platform/status/src/StatusConstants.js @@ -0,0 +1,26 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define*/ +define({ + CSS_CLASS_PREFIX: 's-status-', + TOPIC_PREFIX: 'status:' +}); diff --git a/platform/status/src/StatusRepresenter.js b/platform/status/src/StatusRepresenter.js new file mode 100644 index 0000000000..0808688c02 --- /dev/null +++ b/platform/status/src/StatusRepresenter.js @@ -0,0 +1,96 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define*/ + +define( + ['./StatusConstants'], + function (StatusConstants) { + 'use strict'; + + var STATUS_CLASS_PREFIX = StatusConstants.CSS_CLASS_PREFIX; + + /** + * Adds/removes CSS classes to `mct-representation`s to reflect the + * current status of represented domain objects, as reported by + * their `status` capability. + * + * Statuses are prefixed with `s-status-` to build CSS class names. + * As such, when a domain object has the status "pending", its + * representations will have the CSS class `s-status-pending`. + * + * @param {angular.Scope} scope the representation's scope object + * @param element the representation's jqLite-wrapped DOM element + * @implements {Representer} + * @constructor + * @memberof platform/status + */ + function StatusRepresenter(scope, element) { + this.element = element; + this.lastClasses = []; + } + + /** + * Remove any status-related classes from this representation. + * @private + */ + StatusRepresenter.prototype.clearClasses = function () { + var element = this.element; + this.lastClasses.forEach(function (c) { + element.removeClass(c); + }); + }; + + StatusRepresenter.prototype.represent = function (representation, domainObject) { + var self = this, + statusCapability = domainObject.getCapability('status'); + + function updateStatus(flags) { + var newClasses = flags.map(function (flag) { + return STATUS_CLASS_PREFIX + flag; + }); + + self.clearClasses(); + + newClasses.forEach(function (c) { + self.element.addClass(c); + }); + + self.lastClasses = newClasses; + } + + updateStatus(statusCapability.get()); + this.unlisten = statusCapability.listen(updateStatus); + }; + + StatusRepresenter.prototype.destroy = function () { + this.clearClasses(); + if (this.unlisten) { + this.unlisten(); + this.unlisten = undefined; + } + }; + + + return StatusRepresenter; + + } +); diff --git a/platform/status/src/StatusService.js b/platform/status/src/StatusService.js new file mode 100644 index 0000000000..6fff6f49d8 --- /dev/null +++ b/platform/status/src/StatusService.js @@ -0,0 +1,92 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define*/ + +define( + ['./StatusConstants'], + function (StatusConstants) { + 'use strict'; + + var STATUS_PREFIX = StatusConstants.TOPIC_PREFIX; + + /** + * The `statusService` maintains information about the current + * status of specific domain objects within the system. Status + * is represented as string flags which are present when a + * domain object possesses that status, and false when it does + * not. + * + * @param {platform/core.Topic} topic the `topic` service, used + * to create/use named listeners. + * @constructor + * @memberof platform/status + */ + function StatusService(topic) { + this.statusTable = {}; + this.topic = topic; + } + + /** + * Get all status flags currently set for a domain object. + * @param {string} id the identifier of the domain object + * @returns {string[]} an array containing all status flags currently + * applicable to the object with this identifier + */ + StatusService.prototype.getStatus = function (id) { + return this.statusTable[id] || []; + }; + + /** + * Set a status flag for a domain object. + * @param {string} id the identifier of the domain object + * @param {string} status the status to set + * @param {boolean} state true if the domain object should + * possess this status, false if it should not + */ + StatusService.prototype.setStatus = function (id, status, state) { + this.statusTable[id] = this.statusTable[id] || []; + this.statusTable[id] = this.statusTable[id].filter(function (s) { + return s !== status; + }); + if (state) { + this.statusTable[id].push(status); + } + this.topic(STATUS_PREFIX + id).notify(this.statusTable[id]); + }; + + /** + * Listen for changes in a domain object's status. + * @param {string} id the identifier of the domain object + * @param {Function} callback function to invoke on changes; + * called with the new status of the domain object, as an + * array of strings + * @returns {Function} a function which can be used to stop + * listening to status changes for this domain object. + */ + StatusService.prototype.listen = function (id, callback) { + return this.topic(STATUS_PREFIX + id).listen(callback); + }; + + return StatusService; + + } +); diff --git a/platform/status/test/StatusCapabilitySpec.js b/platform/status/test/StatusCapabilitySpec.js new file mode 100644 index 0000000000..481abf6a53 --- /dev/null +++ b/platform/status/test/StatusCapabilitySpec.js @@ -0,0 +1,84 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/ + +define( + ["../src/StatusCapability"], + function (StatusCapability) { + "use strict"; + + describe("The status capability", function () { + var mockStatusService, + mockDomainObject, + mockUnlisten, + testId, + testStatusFlags, + capability; + + beforeEach(function () { + testId = "some-id"; + testStatusFlags = [ 'a', 'b', 'c' ]; + + mockStatusService = jasmine.createSpyObj( + 'statusService', + [ 'listen', 'setStatus', 'getStatus' ] + ); + mockDomainObject = jasmine.createSpyObj( + 'domainObject', + [ 'getId', 'getCapability', 'getModel' ] + ); + mockUnlisten = jasmine.createSpy('unlisten'); + + mockStatusService.listen.andReturn(mockUnlisten); + mockStatusService.getStatus.andReturn(testStatusFlags); + mockDomainObject.getId.andReturn(testId); + + capability = new StatusCapability( + mockStatusService, + mockDomainObject + ); + }); + + it("sets status with the statusService", function () { + var testStatus = "some-test-status"; + capability.set(testStatus, true); + expect(mockStatusService.setStatus) + .toHaveBeenCalledWith(testId, testStatus, true); + capability.set(testStatus, false); + expect(mockStatusService.setStatus) + .toHaveBeenCalledWith(testId, testStatus, false); + }); + + it("gets status from the statusService", function () { + expect(capability.get()).toBe(testStatusFlags); + }); + + it("listens to changes from the statusService", function () { + var mockCallback = jasmine.createSpy(); + expect(capability.listen(mockCallback)) + .toBe(mockUnlisten); + expect(mockStatusService.listen) + .toHaveBeenCalledWith(testId, mockCallback); + }); + }); + } +); diff --git a/platform/status/test/StatusRepresenterSpec.js b/platform/status/test/StatusRepresenterSpec.js new file mode 100644 index 0000000000..e9191587a7 --- /dev/null +++ b/platform/status/test/StatusRepresenterSpec.js @@ -0,0 +1,118 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/ + +define( + ["../src/StatusRepresenter", "../src/StatusConstants"], + function (StatusRepresenter, StatusConstants) { + "use strict"; + + describe("The status representer", function () { + var mockScope, + mockElement, + testRepresentation, + mockDomainObject, + mockStatusCapability, + mockUnlisten, + elementClasses, + testStatusFlags, + representer; + + function verifyClasses() { + expect(Object.keys(elementClasses).sort()) + .toEqual(testStatusFlags.map(function (s) { + return StatusConstants.CSS_CLASS_PREFIX + s; + }).sort()); + } + + function updateStatus(newFlags) { + testStatusFlags = newFlags; + mockStatusCapability.get.andReturn(newFlags); + mockStatusCapability.listen.mostRecentCall + .args[0](newFlags); + } + + beforeEach(function () { + testStatusFlags = [ 'x', 'y', 'z' ]; + + mockScope = {}; + mockElement = jasmine.createSpyObj('element', [ + 'addClass', + 'removeClass' + ]); + testRepresentation = { key: "someKey" }; + mockDomainObject = jasmine.createSpyObj( + 'domainObject', + [ 'getModel', 'getId', 'getCapability' ] + ); + mockStatusCapability = jasmine.createSpyObj( + 'status', + [ 'get', 'set', 'listen' ] + ); + mockUnlisten = jasmine.createSpy(); + + elementClasses = {}; + + mockElement.addClass.andCallFake(function (c) { + elementClasses[c] = true; + }); + mockElement.removeClass.andCallFake(function (c) { + delete elementClasses[c]; + }); + + mockStatusCapability.get.andReturn(testStatusFlags); + mockStatusCapability.listen.andReturn(mockUnlisten); + + mockDomainObject.getCapability.andCallFake(function (c) { + return c === 'status' && mockStatusCapability; + }); + + representer = new StatusRepresenter(mockScope, mockElement); + representer.represent(testRepresentation, mockDomainObject); + }); + + it("listens for status changes", function () { + expect(mockStatusCapability.listen) + .toHaveBeenCalledWith(jasmine.any(Function)); + }); + + it("initially sets classes to reflect status", verifyClasses); + + it("changes classes on status change callbacks", function () { + updateStatus(['a', 'x', '123']); + verifyClasses(); + }); + + it("stops listening when destroyed", function () { + expect(mockUnlisten).not.toHaveBeenCalled(); + representer.destroy(); + expect(mockUnlisten).toHaveBeenCalled(); + }); + + it("removes status classes when destroyed", function () { + expect(elementClasses).not.toEqual({}); + representer.destroy(); + expect(elementClasses).toEqual({}); + }); + }); + } +); diff --git a/platform/status/test/StatusServiceSpec.js b/platform/status/test/StatusServiceSpec.js new file mode 100644 index 0000000000..1f85cd70a1 --- /dev/null +++ b/platform/status/test/StatusServiceSpec.js @@ -0,0 +1,94 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/ + +define( + ["../src/StatusService"], + function (StatusService) { + "use strict"; + + describe("The status service", function () { + var mockTopic, + mockTopicInstance, + mockUnlisten, + mockCallback, + testId, + testStatus, + statusService; + + beforeEach(function () { + testId = "some-domain-object-identifier"; + testStatus = "test-status"; + + mockTopic = jasmine.createSpy('topic'); + mockTopicInstance = jasmine.createSpyObj( + 'topicInstance', + [ 'notify', 'listen' ] + ); + mockUnlisten = jasmine.createSpy('unlisten'); + mockCallback = jasmine.createSpy('callback'); + + mockTopic.andReturn(mockTopicInstance); + mockTopicInstance.listen.andReturn(mockUnlisten); + + statusService = new StatusService(mockTopic); + }); + + it("initially contains no flags for an object", function () { + expect(statusService.getStatus(testId)).toEqual([]); + }); + + it("stores and clears status flags", function () { + statusService.setStatus(testId, testStatus, true); + expect(statusService.getStatus(testId)).toEqual([testStatus]); + statusService.setStatus(testId, testStatus, false); + expect(statusService.getStatus(testId)).toEqual([]); + }); + + it("uses topic to listen for changes", function () { + expect(statusService.listen(testId, mockCallback)) + .toEqual(mockUnlisten); + expect(mockTopic) + .toHaveBeenCalledWith(jasmine.any(String)); + // Just care that the topic was somehow unique to the object + expect(mockTopic.mostRecentCall.args[0].indexOf(testId)) + .not.toEqual(-1); + }); + + it("notifies listeners of changes", function () { + statusService.setStatus(testId, testStatus, true); + expect(mockTopicInstance.notify) + .toHaveBeenCalledWith([ testStatus ]); + statusService.setStatus(testId, testStatus, false); + expect(mockTopicInstance.notify) + .toHaveBeenCalledWith([ ]); + + expect(mockTopic) + .toHaveBeenCalledWith(jasmine.any(String)); + // Just care that the topic was somehow unique to the object + expect(mockTopic.mostRecentCall.args[0].indexOf(testId)) + .not.toEqual(-1); + }); + + }); + } +); diff --git a/platform/status/test/suite.json b/platform/status/test/suite.json new file mode 100644 index 0000000000..8b0cf2fd26 --- /dev/null +++ b/platform/status/test/suite.json @@ -0,0 +1,5 @@ +[ + "StatusCapability", + "StatusRepresenter", + "StatusService" +]