diff --git a/src/adapter/bundle.js b/src/adapter/bundle.js index c80db823bc..7051f96f42 100644 --- a/src/adapter/bundle.js +++ b/src/adapter/bundle.js @@ -32,6 +32,7 @@ define([ './policies/AdapterCompositionPolicy', './policies/AdaptedViewPolicy', './runs/AlternateCompositionInitializer', + './runs/TimeSettingsURLHandler', 'text!./templates/adapted-view-template.html' ], function ( legacyRegistry, @@ -45,6 +46,7 @@ define([ AdapterCompositionPolicy, AdaptedViewPolicy, AlternateCompositionInitializer, + TimeSettingsURLHandler, adaptedViewTemplate ) { legacyRegistry.register('src/adapter', { @@ -121,6 +123,16 @@ define([ { implementation: AlternateCompositionInitializer, depends: ["openmct"] + }, + { + implementation: function (openmct, $location, $rootScope) { + return new TimeSettingsURLHandler( + openmct.time, + $location, + $rootScope + ); + }, + depends: ["openmct", "$location", "$rootScope"] } ], views: [ diff --git a/src/adapter/runs/TimeSettingsURLHandler.js b/src/adapter/runs/TimeSettingsURLHandler.js new file mode 100644 index 0000000000..beb123885b --- /dev/null +++ b/src/adapter/runs/TimeSettingsURLHandler.js @@ -0,0 +1,117 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2017, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +define([], function () { + // Parameter names in query string + var SEARCH = { + MODE: 'tc.mode', + TIME_SYSTEM: 'tc.timeSystem', + START_BOUND: 'tc.startBound', + END_BOUND: 'tc.endBound', + START_DELTA: 'tc.startDelta', + END_DELTA: 'tc.endDelta' + }; + var TIME_EVENTS = ['bounds', 'timeSystem', 'clock', 'clockOffsets']; + + /** + * Communicates settings from the URL to the time API, + * and vice versa. + */ + function TimeSettingsURLHandler(time, $location, $rootScope) { + this.time = time; + this.$location = $location; + + $rootScope.$on('$locationChangeSuccess', this.updateTime.bind(this)); + + TIME_EVENTS.forEach(function (event) { + this.time.on(event, this.updateQueryParams.bind(this)); + }, this); + + this.updateTime(); // Initialize + } + + TimeSettingsURLHandler.prototype.updateQueryParams = function () { + var clock = this.time.clock(); + var fixed = !clock; + var mode = fixed ? 'fixed' : clock.key; + var timeSystem = this.time.timeSystem().key; + + this.$location.search(SEARCH.MODE, mode); + this.$location.search(SEARCH.TIME_SYSTEM, timeSystem); + + if (fixed) { + var bounds = this.time.bounds(); + this.$location.search(SEARCH.START_BOUND, bounds.start); + this.$location.search(SEARCH.END_BOUND, bounds.end); + this.$location.search(SEARCH.START_DELTA, null); + this.$location.search(SEARCH.END_DELTA, null); + } else { + var deltas = this.time.clockOffsets(); + this.$location.search(SEARCH.START_BOUND, null); + this.$location.search(SEARCH.END_BOUND, null); + this.$location.search(SEARCH.START_DELTA, -deltas.start); + this.$location.search(SEARCH.END_DELTA, deltas.end); + } + }; + + TimeSettingsURLHandler.prototype.updateTime = function () { + var searchParams = this.$location.search(); + var mode = searchParams[SEARCH.MODE]; + var timeSystem = searchParams[SEARCH.TIME_SYSTEM]; + var clockOffsets = { + start: -searchParams[SEARCH.START_DELTA], + end: +searchParams[SEARCH.END_DELTA] + }; + var bounds = { + start: +searchParams[SEARCH.START_BOUND], + end: +searchParams[SEARCH.END_BOUND] + }; + var fixed = (mode === 'fixed'); + var clock = fixed ? undefined : mode; + var hasDeltas = + !isNaN(parseInt(searchParams[SEARCH.START_DELTA], 0xA)) && + !isNaN(parseInt(searchParams[SEARCH.END_DELTA], 0xA)); + var hasBounds = + !isNaN(parseInt(searchParams[SEARCH.START_BOUND], 0xA)) && + !isNaN(parseInt(searchParams[SEARCH.END_BOUND], 0xA)); + + if (fixed && timeSystem && hasBounds) { + this.time.timeSystem(timeSystem, bounds); + this.time.stopClock(); + } + + if (!fixed && clock && hasDeltas) { + this.time.clock(clock, clockOffsets); + this.time.timeSystem(timeSystem); + } + + if (hasDeltas && !fixed) { + this.time.clockOffsets(clockOffsets); + } + + if (hasBounds && fixed) { + this.time.bounds(bounds); + } + }; + + return TimeSettingsURLHandler; +}); diff --git a/src/adapter/runs/TimeSettingsURLHandlerSpec.js b/src/adapter/runs/TimeSettingsURLHandlerSpec.js new file mode 100644 index 0000000000..d8eabd5633 --- /dev/null +++ b/src/adapter/runs/TimeSettingsURLHandlerSpec.js @@ -0,0 +1,185 @@ +/***************************************************************************** + * Open MCT, Copyright (c) 2014-2017, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT 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 includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ + +define(['./TimeSettingsURLHandler'], function (TimeSettingsURLHandler) { + describe("TimeSettingsURLHandler", function () { + var time; + var $location; + var $rootScope; + var search; + var handler; + + beforeEach(function () { + time = jasmine.createSpyObj('time', [ + 'on', + 'bounds', + 'clockOffsets', + 'timeSystem', + 'clock', + 'stopClock' + ]); + $location = jasmine.createSpyObj('$location', [ + 'search' + ]); + $rootScope = jasmine.createSpyObj('$rootScope', [ + '$on' + ]); + + time.timeSystem.andReturn({ key: 'test-time-system' }); + + search = {}; + $location.search.andCallFake(function (key, value) { + if (arguments.length === 0) { + return search; + } + if (value === null) { + delete search[key]; + } else { + search[key] = String(value); + } + return this; + }); + + handler = new TimeSettingsURLHandler( + time, + $location, + $rootScope + ); + }); + + ['bounds', 'timeSystem', 'clock', 'clockOffsets'].forEach(function (event) { + it("listens for " + event + " time events", function () { + expect(time.on) + .toHaveBeenCalledWith(event, jasmine.any(Function)); + }); + + describe("when " + event + " time event occurs with no clock", function () { + var expected; + + beforeEach(function () { + expected = { + 'tc.mode': 'fixed', + 'tc.timeSystem': 'test-time-system', + 'tc.startBound': '123', + 'tc.endBound': '456' + }; + time.clock.andReturn(undefined); + time.bounds.andReturn({ start: 123, end: 456 }); + + time.on.calls.forEach(function (call) { + if (call.args[0] === event) { + call.args[1](); + } + }); + }); + + it("updates query parameters for fixed mode", function () { + expect(search).toEqual(expected); + }); + }); + + describe("when " + event + " time event occurs with a clock", function () { + var expected; + + beforeEach(function () { + expected = { + 'tc.mode': 'clocky', + 'tc.timeSystem': 'test-time-system', + 'tc.startDelta': '123', + 'tc.endDelta': '456' + }; + time.clock.andReturn({ key: 'clocky' }); + time.clockOffsets.andReturn({ start: -123, end: 456 }); + + time.on.calls.forEach(function (call) { + if (call.args[0] === event) { + call.args[1](); + } + }); + }); + + it("updates query parameters for realtime mode", function () { + expect(search).toEqual(expected); + }); + }); + }); + + it("listens for location changes", function () { + expect($rootScope.$on) + .toHaveBeenCalledWith('$locationChangeSuccess', jasmine.any(Function)); + }); + + [false, true].forEach(function (fixed) { + var name = fixed ? "fixed-time" : "real-time"; + var suffix = fixed ? 'Bound' : 'Delta'; + describe("when " + name + " location changes occur", function () { + beforeEach(function () { + search['tc.mode'] = fixed ? 'fixed' : 'clocky'; + search['tc.timeSystem'] = 'some-time-system'; + search['tc.start' + suffix] = '12321'; + search['tc.end' + suffix] = '32123'; + $rootScope.$on.mostRecentCall.args[1](); + }); + + if (fixed) { + var bounds = { start: 12321, end: 32123 }; + it("stops the clock", function () { + expect(time.stopClock).toHaveBeenCalled(); + }); + + it("sets the bounds", function () { + expect(time.bounds).toHaveBeenCalledWith(bounds); + }); + + it("sets the time system with bounds", function () { + expect(time.timeSystem).toHaveBeenCalledWith( + search['tc.timeSystem'], + bounds + ); + }); + } else { + var clockOffsets = { start: -12321, end: 32123 }; + + it("sets the clock", function () { + expect(time.stopClock).not.toHaveBeenCalled(); + expect(time.clock).toHaveBeenCalledWith( + search['tc.mode'], + clockOffsets + ); + }); + + it("sets clock offsets", function () { + expect(time.clockOffsets) + .toHaveBeenCalledWith(clockOffsets); + }); + + it("sets the time system without bounds", function () { + expect(time.timeSystem).toHaveBeenCalledWith( + search['tc.timeSystem'] + ); + }); + } + }); + }); + + }); +});