[Style] Switch to prototype

Switch TimeRangeController to prototype style, and update tests
and template to utilize new style.
This commit is contained in:
Pete Richards 2016-04-13 13:23:33 -07:00
parent d48871f204
commit 6d58f23c0c
3 changed files with 287 additions and 266 deletions

View File

@ -19,15 +19,18 @@
this source code distribution or the Licensing information page available this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information. at runtime from the About dialog for additional information.
--> -->
<div ng-controller="TimeRangeController"> <div ng-controller="TimeRangeController as trCtrl">
<form class="l-time-range-inputs-holder" <form class="l-time-range-inputs-holder"
ng-submit="updateBoundsFromForm()"> ng-submit="trCtrl.updateBoundsFromForm()">
<span class="l-time-range-inputs-elem ui-symbol type-icon">&#x43;</span> <span class="l-time-range-inputs-elem ui-symbol type-icon">&#x43;</span>
<span class="l-time-range-input"> <span class="l-time-range-input">
<mct-control key="'datetime-field'" <mct-control key="'datetime-field'"
structure="{ format: parameters.format, validate: validateStart }" structure="{
format: parameters.format,
validate: trCtrl.validateStart
}"
ng-model="formModel" ng-model="formModel"
ng-blur="updateBoundsFromForm()" ng-blur="trCtrl.updateBoundsFromForm()"
field="'start'" field="'start'"
class="time-range-start"> class="time-range-start">
</mct-control> </mct-control>
@ -37,9 +40,12 @@
<span class="l-time-range-input" ng-controller="ToggleController as t2"> <span class="l-time-range-input" ng-controller="ToggleController as t2">
<mct-control key="'datetime-field'" <mct-control key="'datetime-field'"
structure="{ format: parameters.format, validate: validateEnd }" structure="{
format: parameters.format,
validate: trCtrl.validateEnd
}"
ng-model="formModel" ng-model="formModel"
ng-blur="updateBoundsFromForm()" ng-blur="trCtrl.updateBoundsFromForm()"
field="'end'" field="'end'"
class="time-range-end"> class="time-range-end">
</mct-control>&nbsp; </mct-control>&nbsp;
@ -53,22 +59,25 @@
<div class="slider" <div class="slider"
mct-resize="spanWidth = bounds.width"> mct-resize="spanWidth = bounds.width">
<div class="knob knob-l" <div class="knob knob-l"
mct-drag-down="startLeftDrag()" mct-drag-down="trCtrl.startLeftDrag()"
mct-drag="leftDrag(delta[0])" mct-drag="trCtrl.leftDrag(delta[0])"
ng-style="{ left: startInnerPct }"> ng-style="{ left: startInnerPct }">
<div class="range-value">{{startInnerText}}</div> <div class="range-value">{{startInnerText}}</div>
</div> </div>
<div class="knob knob-r" <div class="knob knob-r"
mct-drag-down="startRightDrag()" mct-drag-down="trCtrl.startRightDrag()"
mct-drag="rightDrag(delta[0])" mct-drag="trCtrl.rightDrag(delta[0])"
ng-style="{ right: endInnerPct }"> ng-style="{ right: endInnerPct }">
<div class="range-value">{{endInnerText}}</div> <div class="range-value">{{endInnerText}}</div>
</div> </div>
<div class="slot range-holder"> <div class="slot range-holder">
<div class="range" <div class="range"
mct-drag-down="startMiddleDrag()" mct-drag-down="trCtrl.startMiddleDrag()"
mct-drag="middleDrag(delta[0])" mct-drag="trCtrl.middleDrag(delta[0])"
ng-style="{ left: startInnerPct, right: endInnerPct}"> ng-style="{
left: startInnerPct,
right: endInnerPct
}">
<div class="toi-line"></div> <div class="toi-line"></div>
</div> </div>
</div> </div>

View File

@ -19,247 +19,259 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
/*global define,Promise*/ /*global define*/
define( define([
['moment'],
function (moment) {
"use strict";
var TICK_SPACING_PX = 150; ], function () {
"use strict";
var TICK_SPACING_PX = 150;
/** /* format number as percent; 0.0-1.0 to "0%"-"100%" */
* Controller used by the `time-controller` template. function toPercent(p) {
* @memberof platform/commonUI/general return (100 * p) + "%";
* @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, formatService, defaultFormat, now) {
var tickCount = 2,
innerMinimumSpan = 1000, // 1 second
outerMinimumSpan = 1000, // 1 second
initialDragValue,
formatter = formatService.getFormat(defaultFormat);
function formatTimestamp(ts) {
return formatter.format(ts);
}
// From 0.0-1.0 to "0%"-"100%"
function toPercent(p) {
return (100 * p) + "%";
}
function updateTicks() {
var i, p, ts, start, end, span;
end = $scope.ngModel.outer.end;
start = $scope.ngModel.outer.start;
span = end - start;
$scope.ticks = [];
for (i = 0; i < tickCount; i += 1) {
p = i / (tickCount - 1);
ts = p * span + start;
$scope.ticks.push(formatTimestamp(ts));
}
}
function updateSpanWidth(w) {
tickCount = Math.max(Math.floor(w / TICK_SPACING_PX), 2);
updateTicks();
}
function updateViewForInnerSpanFromModel(ngModel) {
var span = ngModel.outer.end - ngModel.outer.start;
// Expose readable dates for the knobs
$scope.startInnerText = formatTimestamp(ngModel.inner.start);
$scope.endInnerText = formatTimestamp(ngModel.inner.end);
// And positions for the knobs
$scope.startInnerPct =
toPercent((ngModel.inner.start - ngModel.outer.start) / span);
$scope.endInnerPct =
toPercent((ngModel.outer.end - ngModel.inner.end) / span);
}
function defaultBounds() {
var t = now();
return {
start: t - 24 * 3600 * 1000, // One day
end: t
};
}
function copyBounds(bounds) {
return { start: bounds.start, end: bounds.end };
}
function updateViewFromModel(ngModel) {
ngModel = ngModel || {};
ngModel.outer = ngModel.outer || defaultBounds();
ngModel.inner = ngModel.inner || copyBounds(ngModel.outer);
// Stick it back is scope (in case we just set defaults)
$scope.ngModel = ngModel;
updateViewForInnerSpanFromModel(ngModel);
updateTicks();
}
function startLeftDrag() {
initialDragValue = $scope.ngModel.inner.start;
}
function startRightDrag() {
initialDragValue = $scope.ngModel.inner.end;
}
function startMiddleDrag() {
initialDragValue = {
start: $scope.ngModel.inner.start,
end: $scope.ngModel.inner.end
};
}
function toMillis(pixels) {
var span =
$scope.ngModel.outer.end - $scope.ngModel.outer.start;
return (pixels / $scope.spanWidth) * span;
}
function clamp(value, low, high) {
return Math.max(low, Math.min(high, value));
}
function leftDrag(pixels) {
var delta = toMillis(pixels);
$scope.ngModel.inner.start = clamp(
initialDragValue + delta,
$scope.ngModel.outer.start,
$scope.ngModel.inner.end - innerMinimumSpan
);
updateViewFromModel($scope.ngModel);
}
function rightDrag(pixels) {
var delta = toMillis(pixels);
$scope.ngModel.inner.end = clamp(
initialDragValue + delta,
$scope.ngModel.inner.start + innerMinimumSpan,
$scope.ngModel.outer.end
);
updateViewFromModel($scope.ngModel);
}
function middleDrag(pixels) {
var delta = toMillis(pixels),
edge = delta < 0 ? 'start' : 'end',
opposite = delta < 0 ? 'end' : 'start';
// Adjust the position of the edge in the direction of drag
$scope.ngModel.inner[edge] = clamp(
initialDragValue[edge] + delta,
$scope.ngModel.outer.start,
$scope.ngModel.outer.end
);
// Adjust opposite knob to maintain span
$scope.ngModel.inner[opposite] = $scope.ngModel.inner[edge] +
initialDragValue[opposite] - initialDragValue[edge];
updateViewFromModel($scope.ngModel);
}
function updateFormModel() {
$scope.formModel = {
start: (($scope.ngModel || {}).outer || {}).start,
end: (($scope.ngModel || {}).outer || {}).end
};
}
function updateOuterStart(t) {
var ngModel = $scope.ngModel;
ngModel.inner.start =
Math.max(ngModel.outer.start, ngModel.inner.start);
ngModel.inner.end = Math.max(
ngModel.inner.start + innerMinimumSpan,
ngModel.inner.end
);
updateFormModel();
updateViewForInnerSpanFromModel(ngModel);
updateTicks();
}
function updateOuterEnd(t) {
var ngModel = $scope.ngModel;
ngModel.inner.end =
Math.min(ngModel.outer.end, ngModel.inner.end);
ngModel.inner.start = Math.min(
ngModel.inner.end - innerMinimumSpan,
ngModel.inner.start
);
updateFormModel();
updateViewForInnerSpanFromModel(ngModel);
updateTicks();
}
function updateFormat(key) {
formatter = formatService.getFormat(key || defaultFormat);
updateViewForInnerSpanFromModel($scope.ngModel);
updateTicks();
}
function updateBoundsFromForm() {
var start = $scope.formModel.start,
end = $scope.formModel.end;
if (end >= start + outerMinimumSpan) {
$scope.ngModel = $scope.ngModel || {};
$scope.ngModel.outer = { start: start, end: end };
}
}
function validateStart(startValue) {
return startValue <= $scope.formModel.end - outerMinimumSpan;
}
function validateEnd(endValue) {
return endValue >= $scope.formModel.start + outerMinimumSpan;
}
$scope.startLeftDrag = startLeftDrag;
$scope.startRightDrag = startRightDrag;
$scope.startMiddleDrag = startMiddleDrag;
$scope.leftDrag = leftDrag;
$scope.rightDrag = rightDrag;
$scope.middleDrag = middleDrag;
$scope.updateBoundsFromForm = updateBoundsFromForm;
$scope.validateStart = validateStart;
$scope.validateEnd = validateEnd;
$scope.ticks = [];
// Initialize scope to defaults
updateViewFromModel($scope.ngModel);
updateFormModel();
$scope.$watchCollection("ngModel", updateViewFromModel);
$scope.$watch("spanWidth", updateSpanWidth);
$scope.$watch("ngModel.outer.start", updateOuterStart);
$scope.$watch("ngModel.outer.end", updateOuterEnd);
$scope.$watch("parameters.format", updateFormat);
}
return TimeRangeController;
} }
);
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, 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.$scope.ticks = [];
this.updateViewFromModel(this.$scope.ngModel);
this.updateFormModel();
[
'updateViewFromModel',
'updateSpanWidth',
'updateOuterStart',
'updateOuterEnd',
'updateFormat',
'validateStart',
'validateEnd'
].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);
}
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 start = this.$scope.formModel.start,
end = this.$scope.formModel.end;
if (end >= start + this.outerMinimumSpan) {
this.$scope.ngModel = this.$scope.ngModel || {};
this.$scope.ngModel.outer = { start: start, end: end };
}
};
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;
});

View File

@ -94,18 +94,18 @@ define(
it("exposes start time validator", function () { it("exposes start time validator", function () {
var testValue = 42000000; var testValue = 42000000;
mockScope.formModel = { end: testValue }; mockScope.formModel = { end: testValue };
expect(mockScope.validateStart(testValue + 1)) expect(controller.validateStart(testValue + 1))
.toBe(false); .toBe(false);
expect(mockScope.validateStart(testValue - 60 * 60 * 1000 - 1)) expect(controller.validateStart(testValue - 60 * 60 * 1000 - 1))
.toBe(true); .toBe(true);
}); });
it("exposes end time validator", function () { it("exposes end time validator", function () {
var testValue = 42000000; var testValue = 42000000;
mockScope.formModel = { start: testValue }; mockScope.formModel = { start: testValue };
expect(mockScope.validateEnd(testValue - 1)) expect(controller.validateEnd(testValue - 1))
.toBe(false); .toBe(false);
expect(mockScope.validateEnd(testValue + 60 * 60 * 1000 + 1)) expect(controller.validateEnd(testValue + 60 * 60 * 1000 + 1))
.toBe(true); .toBe(true);
}); });
@ -134,7 +134,7 @@ define(
}); });
it("updates model bounds on request", function () { it("updates model bounds on request", function () {
mockScope.updateBoundsFromForm(); controller.updateBoundsFromForm();
expect(mockScope.ngModel.outer.start) expect(mockScope.ngModel.outer.start)
.toEqual(mockScope.formModel.start); .toEqual(mockScope.formModel.start);
expect(mockScope.ngModel.outer.end) expect(mockScope.ngModel.outer.end)
@ -160,27 +160,27 @@ define(
}); });
it("updates the start time for left drags", function () { it("updates the start time for left drags", function () {
mockScope.startLeftDrag(); controller.startLeftDrag();
mockScope.leftDrag(250); controller.leftDrag(250);
expect(mockScope.ngModel.inner.start) expect(mockScope.ngModel.inner.start)
.toEqual(DAY * 1000 + HOUR * 9); .toEqual(DAY * 1000 + HOUR * 9);
}); });
it("updates the end time for right drags", function () { it("updates the end time for right drags", function () {
mockScope.startRightDrag(); controller.startRightDrag();
mockScope.rightDrag(-250); controller.rightDrag(-250);
expect(mockScope.ngModel.inner.end) expect(mockScope.ngModel.inner.end)
.toEqual(DAY * 1000 + HOUR * 15); .toEqual(DAY * 1000 + HOUR * 15);
}); });
it("updates both start and end for middle drags", function () { it("updates both start and end for middle drags", function () {
mockScope.startMiddleDrag(); controller.startMiddleDrag();
mockScope.middleDrag(-125); controller.middleDrag(-125);
expect(mockScope.ngModel.inner).toEqual({ expect(mockScope.ngModel.inner).toEqual({
start: DAY * 1000, start: DAY * 1000,
end: DAY * 1000 + HOUR * 18 end: DAY * 1000 + HOUR * 18
}); });
mockScope.middleDrag(250); controller.middleDrag(250);
expect(mockScope.ngModel.inner).toEqual({ expect(mockScope.ngModel.inner).toEqual({
start: DAY * 1000 + HOUR * 6, start: DAY * 1000 + HOUR * 6,
end: DAY * 1001 end: DAY * 1001
@ -188,8 +188,8 @@ define(
}); });
it("enforces a minimum inner span", function () { it("enforces a minimum inner span", function () {
mockScope.startRightDrag(); controller.startRightDrag();
mockScope.rightDrag(-9999999); controller.rightDrag(-9999999);
expect(mockScope.ngModel.inner.end) expect(mockScope.ngModel.inner.end)
.toBeGreaterThan(mockScope.ngModel.inner.start); .toBeGreaterThan(mockScope.ngModel.inner.start);
}); });