diff --git a/platform/commonUI/general/res/templates/controls/time-controller.html b/platform/commonUI/general/res/templates/controls/time-controller.html
index ff142ea189..300e56c381 100644
--- a/platform/commonUI/general/res/templates/controls/time-controller.html
+++ b/platform/commonUI/general/res/templates/controls/time-controller.html
@@ -19,84 +19,90 @@
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
-
\ No newline at end of file
+
+
diff --git a/platform/commonUI/general/src/controllers/TimeRangeController.js b/platform/commonUI/general/src/controllers/TimeRangeController.js
index 55bddd712f..d4fb21be08 100644
--- a/platform/commonUI/general/src/controllers/TimeRangeController.js
+++ b/platform/commonUI/general/src/controllers/TimeRangeController.js
@@ -26,9 +26,8 @@ define(
function (moment) {
"use strict";
- var
- DATE_FORMAT = "YYYY-MM-DD HH:mm:ss",
- TICK_SPACING_PX = 150;
+ var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss",
+ TICK_SPACING_PX = 150;
/**
* @memberof platform/commonUI/general
@@ -44,6 +43,15 @@ define(
return moment.utc(ts).format(DATE_FORMAT);
}
+ function parseTimestamp(text) {
+ var m = moment.utc(text, DATE_FORMAT);
+ if (m.isValid()) {
+ return m.valueOf();
+ } else {
+ throw new Error("Could not parse " + text);
+ }
+ }
+
// From 0.0-1.0 to "0%"-"1%"
function toPercent(p) {
return (100 * p) + "%";
@@ -93,6 +101,25 @@ define(
return { start: bounds.start, end: bounds.end };
}
+ function updateBoundsTextForProperty(ngModel, property) {
+ try {
+ if (!$scope.boundsModel[property] ||
+ parseTimestamp($scope.boundsModel[property]) !==
+ ngModel.outer[property]) {
+ $scope.boundsModel[property] =
+ formatTimestamp(ngModel.outer[property]);
+ }
+ } catch (e) {
+ // User-entered text is invalid, so leave it be
+ // until they fix it.
+ }
+ }
+
+ function updateBoundsText(ngModel) {
+ updateBoundsTextForProperty(ngModel, 'start');
+ updateBoundsTextForProperty(ngModel, 'end');
+ }
+
function updateViewFromModel(ngModel) {
var t = now();
@@ -101,8 +128,7 @@ define(
ngModel.inner = ngModel.inner || copyBounds(ngModel.outer);
// First, dates for the date pickers for outer bounds
- $scope.startOuterDate = new Date(ngModel.outer.start);
- $scope.endOuterDate = new Date(ngModel.outer.end);
+ updateBoundsText(ngModel);
// Then various updates for the inner span
updateViewForInnerSpanFromModel(ngModel);
@@ -178,6 +204,8 @@ define(
function updateOuterStart(t) {
var ngModel = $scope.ngModel;
+ ngModel.outer.start = t;
+
ngModel.outer.end = Math.max(
ngModel.outer.start + outerMinimumSpan,
ngModel.outer.end
@@ -190,14 +218,15 @@ define(
ngModel.inner.end
);
- $scope.startOuterText = formatTimestamp(t);
-
updateViewForInnerSpanFromModel(ngModel);
+ updateTicks();
}
function updateOuterEnd(t) {
var ngModel = $scope.ngModel;
+ ngModel.outer.end = t;
+
ngModel.outer.start = Math.min(
ngModel.outer.end - outerMinimumSpan,
ngModel.outer.start
@@ -210,9 +239,40 @@ define(
ngModel.inner.start
);
- $scope.endOuterText = formatTimestamp(t);
-
updateViewForInnerSpanFromModel(ngModel);
+ updateTicks();
+ }
+
+ function updateStartFromText(value) {
+ try {
+ updateOuterStart(parseTimestamp(value));
+ updateBoundsTextForProperty($scope.ngModel, 'end');
+ $scope.boundsModel.startValid = true;
+ } catch (e) {
+ $scope.boundsModel.startValid = false;
+ return;
+ }
+ }
+
+ function updateEndFromText(value) {
+ try {
+ updateOuterEnd(parseTimestamp(value));
+ updateBoundsTextForProperty($scope.ngModel, 'start');
+ $scope.boundsModel.endValid = true;
+ } catch (e) {
+ $scope.boundsModel.endValid = false;
+ return;
+ }
+ }
+
+ function updateStartFromPicker(value) {
+ updateOuterStart(value);
+ updateBoundsText($scope.ngModel);
+ }
+
+ function updateEndFromPicker(value) {
+ updateOuterEnd(value);
+ updateBoundsText($scope.ngModel);
}
$scope.startLeftDrag = startLeftDrag;
@@ -224,14 +284,17 @@ define(
$scope.state = false;
$scope.ticks = [];
+ $scope.boundsModel = {};
// Initialize scope to defaults
updateViewFromModel($scope.ngModel);
$scope.$watchCollection("ngModel", updateViewFromModel);
$scope.$watch("spanWidth", updateSpanWidth);
- $scope.$watch("ngModel.outer.start", updateOuterStart);
- $scope.$watch("ngModel.outer.end", updateOuterEnd);
+ $scope.$watch("ngModel.outer.start", updateStartFromPicker);
+ $scope.$watch("ngModel.outer.end", updateEndFromPicker);
+ $scope.$watch("boundsModel.start", updateStartFromText);
+ $scope.$watch("boundsModel.end", updateEndFromText);
}
return TimeConductorController;
diff --git a/platform/commonUI/general/test/controllers/TimeRangeControllerSpec.js b/platform/commonUI/general/test/controllers/TimeRangeControllerSpec.js
index 9d7a6a9f52..91d3ecb9db 100644
--- a/platform/commonUI/general/test/controllers/TimeRangeControllerSpec.js
+++ b/platform/commonUI/general/test/controllers/TimeRangeControllerSpec.js
@@ -22,8 +22,8 @@
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
define(
- ["../../src/controllers/TimeRangeController"],
- function (TimeRangeController) {
+ ["../../src/controllers/TimeRangeController", "moment"],
+ function (TimeRangeController, moment) {
"use strict";
var SEC = 1000,
@@ -166,8 +166,72 @@ define(
expect(mockScope.ngModel.inner.end)
.toBeGreaterThan(mockScope.ngModel.inner.start);
});
+
+ describe("by typing", function () {
+ it("updates models", function () {
+ var newStart = "1977-05-25 17:30:00",
+ newEnd = "2015-12-18 03:30:00";
+
+ mockScope.boundsModel.start = newStart;
+ fireWatch("boundsModel.start", newStart);
+ expect(mockScope.ngModel.outer.start)
+ .toEqual(moment.utc(newStart).valueOf());
+ expect(mockScope.boundsModel.startValid)
+ .toBeTruthy();
+
+ mockScope.boundsModel.end = newEnd;
+ fireWatch("boundsModel.end", newEnd);
+ expect(mockScope.ngModel.outer.end)
+ .toEqual(moment.utc(newEnd).valueOf());
+ expect(mockScope.boundsModel.endValid)
+ .toBeTruthy();
+ });
+
+ it("displays error state", function () {
+ var newStart = "Not a date",
+ newEnd = "Definitely not a date",
+ oldStart = mockScope.ngModel.outer.start,
+ oldEnd = mockScope.ngModel.outer.end;
+
+ mockScope.boundsModel.start = newStart;
+ fireWatch("boundsModel.start", newStart);
+ expect(mockScope.ngModel.outer.start)
+ .toEqual(oldStart);
+ expect(mockScope.boundsModel.startValid)
+ .toBeFalsy();
+
+ mockScope.boundsModel.end = newEnd;
+ fireWatch("boundsModel.end", newEnd);
+ expect(mockScope.ngModel.outer.end)
+ .toEqual(oldEnd);
+ expect(mockScope.boundsModel.endValid)
+ .toBeFalsy();
+ });
+
+ it("does not modify user input", function () {
+ // Don't want the controller "fixing" bad or
+ // irregularly-formatted input out from under
+ // the user's fingertips.
+ var newStart = "Not a date",
+ newEnd = "2015-3-3 01:02:04",
+ oldStart = mockScope.ngModel.outer.start,
+ oldEnd = mockScope.ngModel.outer.end;
+
+ mockScope.boundsModel.start = newStart;
+ fireWatch("boundsModel.start", newStart);
+ expect(mockScope.boundsModel.start)
+ .toEqual(newStart);
+
+ mockScope.boundsModel.end = newEnd;
+ fireWatch("boundsModel.end", newEnd);
+ expect(mockScope.boundsModel.end)
+ .toEqual(newEnd);
+ });
+ });
});
+
+
});
}
);