From 07818b0a6d69e521ee962a5e8c0351d08153ffc1 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Wed, 21 Oct 2015 14:35:18 -0700 Subject: [PATCH 1/6] [Time Controller] Show bounds in a text field Show bounds in a text field to allow user editing; supports manual editing of time controller bounds, nasa/openmctweb#181. --- .../templates/controls/time-controller.html | 152 +++++++++--------- .../src/controllers/TimeRangeController.js | 10 +- 2 files changed, 82 insertions(+), 80 deletions(-) diff --git a/platform/commonUI/general/res/templates/controls/time-controller.html b/platform/commonUI/general/res/templates/controls/time-controller.html index 61a4feedb8..7f1e8b8453 100644 --- a/platform/commonUI/general/res/templates/controls/time-controller.html +++ b/platform/commonUI/general/res/templates/controls/time-controller.html @@ -21,82 +21,84 @@ -->
-
- C - - - - {{startOuterText}} - - -
- - -
-
-
-
+
+ C + + + + + + + +
+ + +
+
+
+
- to + to - - - - {{endOuterText}} - - -
- - -
-
-
  -
-
+ + + + + + + +
+ + +
+
+
  +
+
-
-
-
-
-
{{startInnerText}}
-
-
-
{{endInnerText}}
-
-
-
-
-
-
-
-
-
+
+
+
+
+
{{startInnerText}}
+
+
+
{{endInnerText}}
+
+
+
+
+
+
+
+
+
-
-
-
- {{tick}} -
-
-
-
\ No newline at end of file +
+
+
+ {{tick}} +
+
+
+ diff --git a/platform/commonUI/general/src/controllers/TimeRangeController.js b/platform/commonUI/general/src/controllers/TimeRangeController.js index c979f03a47..92fc740034 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 @@ -99,8 +98,8 @@ 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); + $scope.boundsModel.start = formatTimestamp(ngModel.outer.start); + $scope.boundsModel.end = formatTimestamp(ngModel.outer.end); // Then various updates for the inner span updateViewForInnerSpanFromModel(ngModel); @@ -210,6 +209,7 @@ define( $scope.state = false; $scope.ticks = []; + $scope.boundsModel = {}; // Initialize scope to defaults updateViewFromModel($scope.ngModel); From 6d2b2fd81e0423faf7cfa0ea20bf55d30dd5e586 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Wed, 21 Oct 2015 14:46:12 -0700 Subject: [PATCH 2/6] [Time Controller] Parse user-entered timestamps nasa/openmctweb#181. --- .../src/controllers/TimeRangeController.js | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/platform/commonUI/general/src/controllers/TimeRangeController.js b/platform/commonUI/general/src/controllers/TimeRangeController.js index 44db087a65..b5ff59ab1a 100644 --- a/platform/commonUI/general/src/controllers/TimeRangeController.js +++ b/platform/commonUI/general/src/controllers/TimeRangeController.js @@ -43,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) + "%"; @@ -214,6 +223,22 @@ define( updateViewForInnerSpanFromModel(ngModel); } + function updateStartFromText(value) { + try { + updateOuterStart(parseTimestamp(value)); + } catch (e) { + return; + } + } + + function updateEndFromText(value) { + try { + updateOuterEnd(parseTimestamp(value)); + } catch (e) { + return; + } + } + $scope.startLeftDrag = startLeftDrag; $scope.startRightDrag = startRightDrag; $scope.startMiddleDrag = startMiddleDrag; @@ -232,6 +257,8 @@ define( $scope.$watch("spanWidth", updateSpanWidth); $scope.$watch("ngModel.outer.start", updateOuterStart); $scope.$watch("ngModel.outer.end", updateOuterEnd); + $scope.$watch("boundsModel.start", updateStartFromText); + $scope.$watch("boundsModel.end", updateEndFromText); } return TimeConductorController; From f88e8ebb51feed773e95206674cbb83da142e139 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Wed, 21 Oct 2015 15:08:44 -0700 Subject: [PATCH 3/6] [Time Controller] Update model state for text entry --- .../general/src/controllers/TimeRangeController.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/platform/commonUI/general/src/controllers/TimeRangeController.js b/platform/commonUI/general/src/controllers/TimeRangeController.js index b5ff59ab1a..a0c6b84dcf 100644 --- a/platform/commonUI/general/src/controllers/TimeRangeController.js +++ b/platform/commonUI/general/src/controllers/TimeRangeController.js @@ -186,6 +186,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 @@ -198,14 +200,14 @@ define( ngModel.inner.end ); - $scope.startOuterText = formatTimestamp(t); - updateViewForInnerSpanFromModel(ngModel); } function updateOuterEnd(t) { var ngModel = $scope.ngModel; + ngModel.outer.end = t; + ngModel.outer.start = Math.min( ngModel.outer.end - outerMinimumSpan, ngModel.outer.start @@ -218,8 +220,6 @@ define( ngModel.inner.start ); - $scope.endOuterText = formatTimestamp(t); - updateViewForInnerSpanFromModel(ngModel); } From 06bcd28558f5e9f6b3fd0e83c60a705a35702846 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Wed, 21 Oct 2015 15:22:00 -0700 Subject: [PATCH 4/6] [Time Controller] Keep inputs in sync Keep inputs in sync with displayed data in time controller, without overwriting user-entered text. nasa/openmctweb#181 --- .../src/controllers/TimeRangeController.js | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/platform/commonUI/general/src/controllers/TimeRangeController.js b/platform/commonUI/general/src/controllers/TimeRangeController.js index a0c6b84dcf..1b567def99 100644 --- a/platform/commonUI/general/src/controllers/TimeRangeController.js +++ b/platform/commonUI/general/src/controllers/TimeRangeController.js @@ -101,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(); @@ -109,8 +128,7 @@ define( ngModel.inner = ngModel.inner || copyBounds(ngModel.outer); // First, dates for the date pickers for outer bounds - $scope.boundsModel.start = formatTimestamp(ngModel.outer.start); - $scope.boundsModel.end = formatTimestamp(ngModel.outer.end); + updateBoundsText(ngModel); // Then various updates for the inner span updateViewForInnerSpanFromModel(ngModel); @@ -201,6 +219,7 @@ define( ); updateViewForInnerSpanFromModel(ngModel); + updateTicks(); } function updateOuterEnd(t) { @@ -221,11 +240,13 @@ define( ); updateViewForInnerSpanFromModel(ngModel); + updateTicks(); } function updateStartFromText(value) { try { updateOuterStart(parseTimestamp(value)); + updateBoundsTextForProperty($scope.ngModel, 'end'); } catch (e) { return; } @@ -234,11 +255,22 @@ define( function updateEndFromText(value) { try { updateOuterEnd(parseTimestamp(value)); + updateBoundsTextForProperty($scope.ngModel, 'start'); } catch (e) { return; } } + function updateStartFromPicker(value) { + updateOuterStart(value); + updateBoundsText($scope.ngModel); + } + + function updateEndFromPicker(value) { + updateOuterEnd(value); + updateBoundsText($scope.ngModel); + } + $scope.startLeftDrag = startLeftDrag; $scope.startRightDrag = startRightDrag; $scope.startMiddleDrag = startMiddleDrag; @@ -255,8 +287,8 @@ define( $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); } From 847c3560630430cba3ef1a8bf0339280222097ce Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Wed, 21 Oct 2015 15:26:42 -0700 Subject: [PATCH 5/6] [Time Controller] Change color when input is invalid nasa/openmctweb#181 --- .../res/templates/controls/time-controller.html | 10 ++++++++-- .../general/src/controllers/TimeRangeController.js | 4 ++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/platform/commonUI/general/res/templates/controls/time-controller.html b/platform/commonUI/general/res/templates/controls/time-controller.html index 8a9be523da..300e56c381 100644 --- a/platform/commonUI/general/res/templates/controls/time-controller.html +++ b/platform/commonUI/general/res/templates/controls/time-controller.html @@ -25,7 +25,10 @@ - + +
@@ -44,7 +47,10 @@ - + + diff --git a/platform/commonUI/general/src/controllers/TimeRangeController.js b/platform/commonUI/general/src/controllers/TimeRangeController.js index 1b567def99..d4fb21be08 100644 --- a/platform/commonUI/general/src/controllers/TimeRangeController.js +++ b/platform/commonUI/general/src/controllers/TimeRangeController.js @@ -247,7 +247,9 @@ define( try { updateOuterStart(parseTimestamp(value)); updateBoundsTextForProperty($scope.ngModel, 'end'); + $scope.boundsModel.startValid = true; } catch (e) { + $scope.boundsModel.startValid = false; return; } } @@ -256,7 +258,9 @@ define( try { updateOuterEnd(parseTimestamp(value)); updateBoundsTextForProperty($scope.ngModel, 'start'); + $scope.boundsModel.endValid = true; } catch (e) { + $scope.boundsModel.endValid = false; return; } } From dbebf085007be053a6f67d326b127cef16c93913 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Wed, 21 Oct 2015 15:38:58 -0700 Subject: [PATCH 6/6] [Time Controller] Add test cases ...to verify behavior on text entry of dates. --- .../controllers/TimeRangeControllerSpec.js | 68 ++++++++++++++++++- 1 file changed, 66 insertions(+), 2 deletions(-) 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); + }); + }); }); + + }); } );