[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,15 +19,30 @@
* 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) { ], function () {
"use strict"; "use strict";
var TICK_SPACING_PX = 150; 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. * Controller used by the `time-controller` template.
@ -41,225 +56,222 @@ define(
* @param {Function} now a function to return current system time * @param {Function} now a function to return current system time
*/ */
function TimeRangeController($scope, formatService, defaultFormat, now) { function TimeRangeController($scope, formatService, defaultFormat, now) {
var tickCount = 2, this.$scope = $scope;
innerMinimumSpan = 1000, // 1 second this.formatService = formatService;
outerMinimumSpan = 1000, // 1 second this.defaultFormat = defaultFormat;
initialDragValue, this.now = now;
formatter = formatService.getFormat(defaultFormat);
function formatTimestamp(ts) { this.tickCount = 2;
return formatter.format(ts); 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);
} }
// From 0.0-1.0 to "0%"-"100%" TimeRangeController.prototype.formatTimestamp = function (ts) {
function toPercent(p) { return this.formatter.format(ts);
return (100 * p) + "%"; };
}
function updateTicks() { TimeRangeController.prototype.updateTicks = function () {
var i, p, ts, start, end, span; var i, p, ts, start, end, span;
end = $scope.ngModel.outer.end; end = this.$scope.ngModel.outer.end;
start = $scope.ngModel.outer.start; start = this.$scope.ngModel.outer.start;
span = end - start; span = end - start;
$scope.ticks = []; this.$scope.ticks = [];
for (i = 0; i < tickCount; i += 1) { for (i = 0; i < this.tickCount; i += 1) {
p = i / (tickCount - 1); p = i / (this.tickCount - 1);
ts = p * span + start; ts = p * span + start;
$scope.ticks.push(formatTimestamp(ts)); this.$scope.ticks.push(this.formatTimestamp(ts));
}
} }
};
function updateSpanWidth(w) { TimeRangeController.prototype.updateSpanWidth = function (w) {
tickCount = Math.max(Math.floor(w / TICK_SPACING_PX), 2); this.tickCount = Math.max(Math.floor(w / TICK_SPACING_PX), 2);
updateTicks(); this.updateTicks();
} };
function updateViewForInnerSpanFromModel(ngModel) { TimeRangeController.prototype.updateViewForInnerSpanFromModel = function (
ngModel
) {
var span = ngModel.outer.end - ngModel.outer.start; var span = ngModel.outer.end - ngModel.outer.start;
// Expose readable dates for the knobs // Expose readable dates for the knobs
$scope.startInnerText = formatTimestamp(ngModel.inner.start); this.$scope.startInnerText = this.formatTimestamp(ngModel.inner.start);
$scope.endInnerText = formatTimestamp(ngModel.inner.end); this.$scope.endInnerText = this.formatTimestamp(ngModel.inner.end);
// And positions for the knobs // And positions for the knobs
$scope.startInnerPct = this.$scope.startInnerPct =
toPercent((ngModel.inner.start - ngModel.outer.start) / span); toPercent((ngModel.inner.start - ngModel.outer.start) / span);
$scope.endInnerPct = this.$scope.endInnerPct =
toPercent((ngModel.outer.end - ngModel.inner.end) / span); toPercent((ngModel.outer.end - ngModel.inner.end) / span);
} };
function defaultBounds() { TimeRangeController.prototype.defaultBounds = function () {
var t = now(); var t = this.now();
return { return {
start: t - 24 * 3600 * 1000, // One day start: t - 24 * 3600 * 1000, // One day
end: t end: t
}; };
} };
function copyBounds(bounds) {
return { start: bounds.start, end: bounds.end };
}
function updateViewFromModel(ngModel) { TimeRangeController.prototype.updateViewFromModel = function (ngModel) {
ngModel = ngModel || {}; ngModel = ngModel || {};
ngModel.outer = ngModel.outer || defaultBounds(); ngModel.outer = ngModel.outer || this.defaultBounds();
ngModel.inner = ngModel.inner || copyBounds(ngModel.outer); ngModel.inner = ngModel.inner || copyBounds(ngModel.outer);
// Stick it back is scope (in case we just set defaults) // Stick it back is scope (in case we just set defaults)
$scope.ngModel = ngModel; this.$scope.ngModel = ngModel;
updateViewForInnerSpanFromModel(ngModel); this.updateViewForInnerSpanFromModel(ngModel);
updateTicks(); this.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) { 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 = var span =
$scope.ngModel.outer.end - $scope.ngModel.outer.start; this.$scope.ngModel.outer.end - this.$scope.ngModel.outer.start;
return (pixels / $scope.spanWidth) * span; return (pixels / this.$scope.spanWidth) * span;
} };
function clamp(value, low, high) { TimeRangeController.prototype.leftDrag = function (pixels) {
return Math.max(low, Math.min(high, value)); var delta = this.toMillis(pixels);
} this.$scope.ngModel.inner.start = clamp(
this.initialDragValue + delta,
function leftDrag(pixels) { this.$scope.ngModel.outer.start,
var delta = toMillis(pixels); this.$scope.ngModel.inner.end - this.innerMinimumSpan
$scope.ngModel.inner.start = clamp(
initialDragValue + delta,
$scope.ngModel.outer.start,
$scope.ngModel.inner.end - innerMinimumSpan
); );
updateViewFromModel($scope.ngModel); this.updateViewFromModel(this.$scope.ngModel);
} };
function rightDrag(pixels) { TimeRangeController.prototype.rightDrag = function (pixels) {
var delta = toMillis(pixels); var delta = this.toMillis(pixels);
$scope.ngModel.inner.end = clamp( this.$scope.ngModel.inner.end = clamp(
initialDragValue + delta, this.initialDragValue + delta,
$scope.ngModel.inner.start + innerMinimumSpan, this.$scope.ngModel.inner.start + this.innerMinimumSpan,
$scope.ngModel.outer.end this.$scope.ngModel.outer.end
); );
updateViewFromModel($scope.ngModel); this.updateViewFromModel(this.$scope.ngModel);
} };
function middleDrag(pixels) { TimeRangeController.prototype.middleDrag = function (pixels) {
var delta = toMillis(pixels), var delta = this.toMillis(pixels),
edge = delta < 0 ? 'start' : 'end', edge = delta < 0 ? 'start' : 'end',
opposite = delta < 0 ? 'end' : 'start'; opposite = delta < 0 ? 'end' : 'start';
// Adjust the position of the edge in the direction of drag // Adjust the position of the edge in the direction of drag
$scope.ngModel.inner[edge] = clamp( this.$scope.ngModel.inner[edge] = clamp(
initialDragValue[edge] + delta, this.initialDragValue[edge] + delta,
$scope.ngModel.outer.start, this.$scope.ngModel.outer.start,
$scope.ngModel.outer.end this.$scope.ngModel.outer.end
); );
// Adjust opposite knob to maintain span // Adjust opposite knob to maintain span
$scope.ngModel.inner[opposite] = $scope.ngModel.inner[edge] + this.$scope.ngModel.inner[opposite] =
initialDragValue[opposite] - initialDragValue[edge]; this.$scope.ngModel.inner[edge] +
this.initialDragValue[opposite] -
this.initialDragValue[edge];
updateViewFromModel($scope.ngModel); this.updateViewFromModel(this.$scope.ngModel);
}
function updateFormModel() {
$scope.formModel = {
start: (($scope.ngModel || {}).outer || {}).start,
end: (($scope.ngModel || {}).outer || {}).end
}; };
}
function updateOuterStart(t) { TimeRangeController.prototype.updateFormModel = function () {
var ngModel = $scope.ngModel; 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 = ngModel.inner.start =
Math.max(ngModel.outer.start, ngModel.inner.start); Math.max(ngModel.outer.start, ngModel.inner.start);
ngModel.inner.end = Math.max( ngModel.inner.end = Math.max(
ngModel.inner.start + innerMinimumSpan, ngModel.inner.start + this.innerMinimumSpan,
ngModel.inner.end ngModel.inner.end
); );
updateFormModel(); this.updateFormModel();
updateViewForInnerSpanFromModel(ngModel); this.updateViewForInnerSpanFromModel(ngModel);
updateTicks(); this.updateTicks();
} };
function updateOuterEnd(t) { TimeRangeController.prototype.updateOuterEnd = function () {
var ngModel = $scope.ngModel; var ngModel = this.$scope.ngModel;
ngModel.inner.end = ngModel.inner.end =
Math.min(ngModel.outer.end, ngModel.inner.end); Math.min(ngModel.outer.end, ngModel.inner.end);
ngModel.inner.start = Math.min( ngModel.inner.start = Math.min(
ngModel.inner.end - innerMinimumSpan, ngModel.inner.end - this.innerMinimumSpan,
ngModel.inner.start ngModel.inner.start
); );
updateFormModel(); this.updateFormModel();
updateViewForInnerSpanFromModel(ngModel); this.updateViewForInnerSpanFromModel(ngModel);
updateTicks(); 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 };
} }
};
function updateFormat(key) { TimeRangeController.prototype.validateStart = function (startValue) {
formatter = formatService.getFormat(key || defaultFormat); return startValue <=
updateViewForInnerSpanFromModel($scope.ngModel); this.$scope.formModel.end - this.outerMinimumSpan;
updateTicks(); };
}
function updateBoundsFromForm() { TimeRangeController.prototype.validateEnd = function (endValue) {
var start = $scope.formModel.start, return endValue >=
end = $scope.formModel.end; this.$scope.formModel.start + this.outerMinimumSpan;
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; 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);
}); });