openmct/platform/commonUI/general/src/controllers/TimeRangeController.js
2018-05-14 15:46:17 -07:00

312 lines
11 KiB
JavaScript

/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, 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 () {
var TICK_SPACING_PX = 150;
/* format number as percent; 0.0-1.0 to "0%"-"100%" */
function toPercent(p) {
return (100 * p) + "%";
}
function clamp(value, low, high) {
return Math.max(low, Math.min(high, value));
}
function copyBounds(bounds) {
return {
start: bounds.start,
end: bounds.end
};
}
/**
* Controller used by the `time-controller` template.
* @memberof platform/commonUI/general
* @constructor
* @param $scope the Angular scope for this controller
* @param {FormatService} formatService the service to user to format
* domain values
* @param {string} defaultFormat the format to request when no
* format has been otherwise specified
* @param {Function} now a function to return current system time
*/
function TimeRangeController($scope, $timeout, formatService, defaultFormat, now) {
this.$scope = $scope;
this.formatService = formatService;
this.defaultFormat = defaultFormat;
this.now = now;
this.tickCount = 2;
this.innerMinimumSpan = 1000; // 1 second
this.outerMinimumSpan = 1000; // 1 second
this.initialDragValue = undefined;
this.formatter = formatService.getFormat(defaultFormat);
this.formStartChanged = false;
this.formEndChanged = false;
this.$timeout = $timeout;
this.$scope.ticks = [];
this.updateViewFromModel(this.$scope.ngModel);
this.updateFormModel();
[
'updateViewFromModel',
'updateSpanWidth',
'updateOuterStart',
'updateOuterEnd',
'updateFormat',
'validateStart',
'validateEnd',
'onFormStartChange',
'onFormEndChange'
].forEach(function (boundFn) {
this[boundFn] = this[boundFn].bind(this);
}, this);
this.$scope.$watchCollection("ngModel", this.updateViewFromModel);
this.$scope.$watch("spanWidth", this.updateSpanWidth);
this.$scope.$watch("ngModel.outer.start", this.updateOuterStart);
this.$scope.$watch("ngModel.outer.end", this.updateOuterEnd);
this.$scope.$watch("parameters.format", this.updateFormat);
this.$scope.$watch("formModel.start", this.onFormStartChange);
this.$scope.$watch("formModel.end", this.onFormEndChange);
}
TimeRangeController.prototype.formatTimestamp = function (ts) {
return this.formatter.format(ts);
};
TimeRangeController.prototype.updateTicks = function () {
var i, p, ts, start, end, span;
end = this.$scope.ngModel.outer.end;
start = this.$scope.ngModel.outer.start;
span = end - start;
this.$scope.ticks = [];
for (i = 0; i < this.tickCount; i += 1) {
p = i / (this.tickCount - 1);
ts = p * span + start;
this.$scope.ticks.push(this.formatTimestamp(ts));
}
};
TimeRangeController.prototype.updateSpanWidth = function (w) {
this.tickCount = Math.max(Math.floor(w / TICK_SPACING_PX), 2);
this.updateTicks();
};
TimeRangeController.prototype.updateViewForInnerSpanFromModel = function (
ngModel
) {
var span = ngModel.outer.end - ngModel.outer.start;
// Expose readable dates for the knobs
this.$scope.startInnerText = this.formatTimestamp(ngModel.inner.start);
this.$scope.endInnerText = this.formatTimestamp(ngModel.inner.end);
// And positions for the knobs
this.$scope.startInnerPct =
toPercent((ngModel.inner.start - ngModel.outer.start) / span);
this.$scope.endInnerPct =
toPercent((ngModel.outer.end - ngModel.inner.end) / span);
};
TimeRangeController.prototype.defaultBounds = function () {
var t = this.now();
return {
start: t - 24 * 3600 * 1000, // One day
end: t
};
};
TimeRangeController.prototype.updateViewFromModel = function (ngModel) {
ngModel = ngModel || {};
ngModel.outer = ngModel.outer || this.defaultBounds();
ngModel.inner = ngModel.inner || copyBounds(ngModel.outer);
// Stick it back is scope (in case we just set defaults)
this.$scope.ngModel = ngModel;
this.updateViewForInnerSpanFromModel(ngModel);
this.updateTicks();
};
TimeRangeController.prototype.startLeftDrag = function () {
this.initialDragValue = this.$scope.ngModel.inner.start;
};
TimeRangeController.prototype.startRightDrag = function () {
this.initialDragValue = this.$scope.ngModel.inner.end;
};
TimeRangeController.prototype.startMiddleDrag = function () {
this.initialDragValue = {
start: this.$scope.ngModel.inner.start,
end: this.$scope.ngModel.inner.end
};
};
TimeRangeController.prototype.toMillis = function (pixels) {
var span =
this.$scope.ngModel.outer.end - this.$scope.ngModel.outer.start;
return (pixels / this.$scope.spanWidth) * span;
};
TimeRangeController.prototype.leftDrag = function (pixels) {
var delta = this.toMillis(pixels);
this.$scope.ngModel.inner.start = clamp(
this.initialDragValue + delta,
this.$scope.ngModel.outer.start,
this.$scope.ngModel.inner.end - this.innerMinimumSpan
);
this.updateViewFromModel(this.$scope.ngModel);
};
TimeRangeController.prototype.rightDrag = function (pixels) {
var delta = this.toMillis(pixels);
this.$scope.ngModel.inner.end = clamp(
this.initialDragValue + delta,
this.$scope.ngModel.inner.start + this.innerMinimumSpan,
this.$scope.ngModel.outer.end
);
this.updateViewFromModel(this.$scope.ngModel);
};
TimeRangeController.prototype.middleDrag = function (pixels) {
var delta = this.toMillis(pixels),
edge = delta < 0 ? 'start' : 'end',
opposite = delta < 0 ? 'end' : 'start';
// Adjust the position of the edge in the direction of drag
this.$scope.ngModel.inner[edge] = clamp(
this.initialDragValue[edge] + delta,
this.$scope.ngModel.outer.start,
this.$scope.ngModel.outer.end
);
// Adjust opposite knob to maintain span
this.$scope.ngModel.inner[opposite] =
this.$scope.ngModel.inner[edge] +
this.initialDragValue[opposite] -
this.initialDragValue[edge];
this.updateViewFromModel(this.$scope.ngModel);
};
TimeRangeController.prototype.updateFormModel = function () {
this.$scope.formModel = {
start: ((this.$scope.ngModel || {}).outer || {}).start,
end: ((this.$scope.ngModel || {}).outer || {}).end
};
};
TimeRangeController.prototype.updateOuterStart = function () {
var ngModel = this.$scope.ngModel;
ngModel.inner.start =
Math.max(ngModel.outer.start, ngModel.inner.start);
ngModel.inner.end = Math.max(
ngModel.inner.start + this.innerMinimumSpan,
ngModel.inner.end
);
this.updateFormModel();
this.updateViewForInnerSpanFromModel(ngModel);
this.updateTicks();
};
TimeRangeController.prototype.updateOuterEnd = function () {
var ngModel = this.$scope.ngModel;
ngModel.inner.end =
Math.min(ngModel.outer.end, ngModel.inner.end);
ngModel.inner.start = Math.min(
ngModel.inner.end - this.innerMinimumSpan,
ngModel.inner.start
);
this.updateFormModel();
this.updateViewForInnerSpanFromModel(ngModel);
this.updateTicks();
};
TimeRangeController.prototype.updateFormat = function (key) {
this.formatter = this.formatService.getFormat(key || this.defaultFormat);
this.updateViewForInnerSpanFromModel(this.$scope.ngModel);
this.updateTicks();
};
TimeRangeController.prototype.updateBoundsFromForm = function () {
var self = this;
//Allow Angular to trigger watches and determine whether values have changed.
this.$timeout(function () {
if (self.formStartChanged) {
self.$scope.ngModel.outer.start =
self.$scope.ngModel.inner.start =
self.$scope.formModel.start;
self.formStartChanged = false;
}
if (self.formEndChanged) {
self.$scope.ngModel.outer.end =
self.$scope.ngModel.inner.end =
self.$scope.formModel.end;
self.formEndChanged = false;
}
});
};
TimeRangeController.prototype.onFormStartChange = function (
newValue,
oldValue
) {
if (!this.formStartChanged && newValue !== oldValue) {
this.formStartChanged = true;
}
};
TimeRangeController.prototype.onFormEndChange = function (
newValue,
oldValue
) {
if (!this.formEndChanged && newValue !== oldValue) {
this.formEndChanged = true;
}
};
TimeRangeController.prototype.validateStart = function (startValue) {
return startValue <=
this.$scope.formModel.end - this.outerMinimumSpan;
};
TimeRangeController.prototype.validateEnd = function (endValue) {
return endValue >=
this.$scope.formModel.start + this.outerMinimumSpan;
};
return TimeRangeController;
});