Merge pull request #830 from nasa/time-conductor-ux

Time Conductor updates query on blur, mobile support
This commit is contained in:
Victor Woeltjen
2016-04-14 10:04:17 -07:00
5 changed files with 567 additions and 299 deletions

View File

@ -1,54 +1,42 @@
@mixin toiLineHovEffects() { @mixin toiLineHovEffects() {
//@include pulse(.25s);
&:before, &:before,
&:after { &:after {
background-color: $timeControllerToiLineColorHov; background-color: $timeControllerToiLineColorHov;
} }
} }
mct-include.l-time-controller { .l-time-controller {
$minW: 500px; $minW: 500px;
$knobHOffset: 0px; $knobHOffset: 0px;
$knobM: ($sliderKnobW + $knobHOffset) * -1; $knobM: ($sliderKnobW + $knobHOffset) * -1;
$rangeValPad: $interiorMargin; $rangeValPad: $interiorMargin;
$rangeValOffset: $sliderKnobW; $rangeValOffset: $sliderKnobW;
//$knobCr: $sliderKnobW;
$timeRangeSliderLROffset: 130px + $sliderKnobW + $rangeValOffset; $timeRangeSliderLROffset: 130px + $sliderKnobW + $rangeValOffset;
$r1H: nth($ueTimeControlH,1); $r1H: nth($ueTimeControlH,1);
$r2H: nth($ueTimeControlH,2); $r2H: nth($ueTimeControlH,2);
$r3H: nth($ueTimeControlH,3); $r3H: nth($ueTimeControlH,3);
//@include absPosDefault();
//@include test();
display: block; display: block;
//top: auto;
height: $r1H + $r2H + $r3H + ($interiorMargin * 2); height: $r1H + $r2H + $r3H + ($interiorMargin * 2);
min-width: $minW; min-width: $minW;
font-size: 0.8rem; font-size: 0.8rem;
.l-time-range-inputs-holder,
.l-time-range-slider {
//font-size: 0.8em;
}
.l-time-range-inputs-holder, .l-time-range-inputs-holder,
.l-time-range-slider-holder, .l-time-range-slider-holder,
.l-time-range-ticks-holder .l-time-range-ticks-holder
{ {
//@include test();
@include absPosDefault(0, visible); @include absPosDefault(0, visible);
box-sizing: border-box; box-sizing: border-box;
top: auto; top: auto;
} }
.l-time-range-slider, .l-time-range-slider,
.l-time-range-ticks { .l-time-range-ticks {
//@include test(red, 0.1);
@include absPosDefault(0, visible); @include absPosDefault(0, visible);
left: $timeRangeSliderLROffset; right: $timeRangeSliderLROffset; left: $timeRangeSliderLROffset; right: $timeRangeSliderLROffset;
} }
.l-time-range-inputs-holder { .l-time-range-inputs-holder {
//@include test(red);
height: $r1H; bottom: $r2H + $r3H + ($interiorMarginSm * 2); height: $r1H; bottom: $r2H + $r3H + ($interiorMarginSm * 2);
padding-top: $interiorMargin; padding-top: $interiorMargin;
border-top: 1px solid $colorInteriorBorder; border-top: 1px solid $colorInteriorBorder;
@ -70,7 +58,6 @@ mct-include.l-time-controller {
} }
.l-time-range-slider-holder { .l-time-range-slider-holder {
//@include test(green);
height: $r2H; bottom: $r3H + ($interiorMarginSm * 1); height: $r2H; bottom: $r3H + ($interiorMarginSm * 1);
.range-holder { .range-holder {
box-shadow: none; box-shadow: none;
@ -82,7 +69,6 @@ mct-include.l-time-controller {
$myW: 8px; $myW: 8px;
@include transform(translateX(50%)); @include transform(translateX(50%));
position: absolute; position: absolute;
//@include test();
top: 0; right: 0; bottom: 0px; left: auto; top: 0; right: 0; bottom: 0px; left: auto;
width: $myW; width: $myW;
height: auto; height: auto;
@ -97,7 +83,6 @@ mct-include.l-time-controller {
// Vert line // Vert line
top: 0; right: auto; bottom: -10px; left: floor($myW/2) - 1; top: 0; right: auto; bottom: -10px; left: floor($myW/2) - 1;
width: 2px; width: 2px;
//top: 0; right: 3px; bottom: 0; left: 3px;
} }
&:after { &:after {
// Circle element // Circle element
@ -114,7 +99,6 @@ mct-include.l-time-controller {
} }
} }
&:not(:active) { &:not(:active) {
//@include test(#ff00cc);
.knob, .knob,
.range { .range {
@include transition-property(left, right); @include transition-property(left, right);
@ -155,7 +139,6 @@ mct-include.l-time-controller {
.knob { .knob {
z-index: 2; z-index: 2;
.range-value { .range-value {
//@include test($sliderColorRange);
@include trans-prop-nice-fade(.25s); @include trans-prop-nice-fade(.25s);
padding: 0 $rangeValOffset; padding: 0 $rangeValOffset;
position: absolute; position: absolute;
@ -167,7 +150,6 @@ mct-include.l-time-controller {
color: $sliderColorKnobHov; color: $sliderColorKnobHov;
} }
&.knob-l { &.knob-l {
//border-bottom-left-radius: $knobCr; // MOVED TO _CONTROLS.SCSS
margin-left: $knobM; margin-left: $knobM;
.range-value { .range-value {
text-align: right; text-align: right;
@ -175,7 +157,6 @@ mct-include.l-time-controller {
} }
} }
&.knob-r { &.knob-r {
//border-bottom-right-radius: $knobCr;
margin-right: $knobM; margin-right: $knobM;
.range-value { .range-value {
left: $rangeValOffset; left: $rangeValOffset;
@ -185,15 +166,189 @@ mct-include.l-time-controller {
} }
} }
} }
.l-time-domain-selector {
position: absolute;
right: 0px;
bottom: 46px;
}
} }
//.slot.range-holder {
// background-color: $sliderColorRangeHolder;
//}
.s-time-range-val { .s-time-range-val {
//@include test();
border-radius: $controlCr; border-radius: $controlCr;
background-color: $colorInputBg; background-color: $colorInputBg;
padding: 1px 1px 0 $interiorMargin; padding: 1px 1px 0 $interiorMargin;
} }
@include phoneandtablet {
.l-time-controller, .l-time-range-inputs-holder {
min-width: 0px;
}
.l-time-controller {
.l-time-domain-selector {
select {
height: 25px;
margin-bottom: 0px;
}
}
.l-time-range-slider-holder, .l-time-range-ticks-holder {
display: none;
}
.time-range-start, .time-range-end, {
width: 100%;
}
.l-time-range-inputs-holder {
.l-time-range-input {
display: block;
.s-btn {
padding-right: 18px;
white-space: nowrap;
input {
width: 100%;
}
}
}
.l-time-range-inputs-elem {
}
}
}
}
@include phone {
.l-time-controller {
height: 48px;
.l-time-range-inputs-holder {
bottom: 24px;
}
.l-time-domain-selector {
width: 33%;
bottom: -9px;
}
.l-time-range-inputs-holder {
.l-time-range-input {
margin-bottom: 5px;
.s-btn {
width: 66%;
}
}
.l-time-range-inputs-elem {
&.ui-symbol {
display: none;
}
&.lbl {
width: 33%;
right: 0px;
top: 5px;
display: block;
height: 25px;
margin: 0;
line-height: 25px;
position: absolute;
}
}
}
}
}
@include tablet {
.l-time-controller {
height: 17px;
.l-time-range-inputs-holder {
bottom: -7px;
left: -5px;
}
.l-time-domain-selector {
width: 23%;
right: -4px;
bottom: -10px;
}
.l-time-range-inputs-holder {
.l-time-range-input {
float: left;
.s-btn {
width: 100%;
padding-left: 4px;
}
}
}
}
}
@include tabletLandscape {
.l-time-controller {
height: 17px;
.l-time-range-inputs-holder {
bottom: -7px;
}
.l-time-domain-selector {
width: 23%;
right: auto;
bottom: -10px;
left: 391px;
}
.l-time-range-inputs-holder {
.l-time-range-inputs-elem {
&.ui-symbol, &.lbl {
display: block;
float: left;
line-height: 25px;
}
}
}
}
.pane-tree-hidden .l-time-controller {
.l-time-domain-selector {
left: 667px;
}
.l-time-range-inputs-holder {
padding-left: 277px;
}
}
}
@include tabletPortrait {
.l-time-controller {
height: 17px;
.l-time-range-inputs-holder {
bottom: -7px;
left: -5px;
}
.l-time-domain-selector {
width: 23%;
right: -4px;
bottom: -10px;
}
.l-time-range-inputs-holder {
.l-time-range-input {
width: 38%;
float: left;
}
.l-time-range-inputs-elem {
&.ui-symbol, &.lbl {
display: none;
}
}
}
}
}

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,252 @@ 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.formStartChanged = false;
this.formEndChanged = false;
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);
} }
// 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 () {
if (this.formStartChanged) {
this.$scope.ngModel.outer.start =
this.$scope.ngModel.inner.start =
this.$scope.formModel.start;
this.formStartChanged = false;
} }
if (this.formEndChanged) {
function updateFormat(key) { this.$scope.ngModel.outer.end =
formatter = formatService.getFormat(key || defaultFormat); this.$scope.ngModel.inner.end =
updateViewForInnerSpanFromModel($scope.ngModel); this.$scope.formModel.end;
updateTicks(); this.formEndChanged = false;
} }
};
function updateBoundsFromForm() { TimeRangeController.prototype.onFormStartChange = function (
var start = $scope.formModel.start, newValue,
end = $scope.formModel.end; oldValue
if (end >= start + outerMinimumSpan) { ) {
$scope.ngModel = $scope.ngModel || {}; if (!this.formStartChanged && newValue !== oldValue) {
$scope.ngModel.outer = { start: start, end: end }; this.formStartChanged = true;
} }
};
TimeRangeController.prototype.onFormEndChange = function (
newValue,
oldValue
) {
if (!this.formEndChanged && newValue !== oldValue) {
this.formEndChanged = true;
} }
};
function validateStart(startValue) { TimeRangeController.prototype.validateStart = function (startValue) {
return startValue <= $scope.formModel.end - outerMinimumSpan; return startValue <=
} this.$scope.formModel.end - this.outerMinimumSpan;
};
function validateEnd(endValue) { TimeRangeController.prototype.validateEnd = function (endValue) {
return endValue >= $scope.formModel.start + outerMinimumSpan; return endValue >=
} this.$scope.formModel.start + this.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);
}); });
@ -119,25 +119,87 @@ define(
start: DAY * 10000, start: DAY * 10000,
end: DAY * 11000 end: DAY * 11000
}; };
// These watches may not exist, but Angular would fire });
// them if they did.
it('updates all changed bounds when requested', function () {
fireWatchCollection("formModel", mockScope.formModel); fireWatchCollection("formModel", mockScope.formModel);
fireWatch("formModel.start", mockScope.formModel.start); fireWatch("formModel.start", mockScope.formModel.start);
fireWatch("formModel.end", mockScope.formModel.end); fireWatch("formModel.end", mockScope.formModel.end);
});
it("does not immediately make changes to the model", function () {
expect(mockScope.ngModel.outer.start) expect(mockScope.ngModel.outer.start)
.not.toEqual(mockScope.formModel.start); .not.toEqual(mockScope.formModel.start);
expect(mockScope.ngModel.inner.start)
.not.toEqual(mockScope.formModel.start);
expect(mockScope.ngModel.outer.end) expect(mockScope.ngModel.outer.end)
.not.toEqual(mockScope.formModel.end); .not.toEqual(mockScope.formModel.end);
expect(mockScope.ngModel.inner.end)
.not.toEqual(mockScope.formModel.end);
controller.updateBoundsFromForm();
expect(mockScope.ngModel.outer.start)
.toEqual(mockScope.formModel.start);
expect(mockScope.ngModel.inner.start)
.toEqual(mockScope.formModel.start);
expect(mockScope.ngModel.outer.end)
.toEqual(mockScope.formModel.end);
expect(mockScope.ngModel.inner.end)
.toEqual(mockScope.formModel.end);
});
it('updates changed start bound when requested', function () {
fireWatchCollection("formModel", mockScope.formModel);
fireWatch("formModel.start", mockScope.formModel.start);
expect(mockScope.ngModel.outer.start)
.not.toEqual(mockScope.formModel.start);
expect(mockScope.ngModel.inner.start)
.not.toEqual(mockScope.formModel.start);
expect(mockScope.ngModel.outer.end)
.not.toEqual(mockScope.formModel.end);
expect(mockScope.ngModel.inner.end)
.not.toEqual(mockScope.formModel.end);
controller.updateBoundsFromForm();
expect(mockScope.ngModel.outer.start)
.toEqual(mockScope.formModel.start);
expect(mockScope.ngModel.inner.start)
.toEqual(mockScope.formModel.start);
expect(mockScope.ngModel.outer.end)
.not.toEqual(mockScope.formModel.end);
expect(mockScope.ngModel.inner.end)
.not.toEqual(mockScope.formModel.end);
}); });
it("updates model bounds on request", function () { it('updates changed end bound when requested', function () {
mockScope.updateBoundsFromForm(); fireWatchCollection("formModel", mockScope.formModel);
fireWatch("formModel.end", mockScope.formModel.end);
expect(mockScope.ngModel.outer.start) expect(mockScope.ngModel.outer.start)
.toEqual(mockScope.formModel.start); .not.toEqual(mockScope.formModel.start);
expect(mockScope.ngModel.inner.start)
.not.toEqual(mockScope.formModel.start);
expect(mockScope.ngModel.outer.end) expect(mockScope.ngModel.outer.end)
.not.toEqual(mockScope.formModel.end);
expect(mockScope.ngModel.inner.end)
.not.toEqual(mockScope.formModel.end);
controller.updateBoundsFromForm();
expect(mockScope.ngModel.outer.start)
.not.toEqual(mockScope.formModel.start);
expect(mockScope.ngModel.inner.start)
.not.toEqual(mockScope.formModel.start);
expect(mockScope.ngModel.outer.end)
.toEqual(mockScope.formModel.end);
expect(mockScope.ngModel.inner.end)
.toEqual(mockScope.formModel.end); .toEqual(mockScope.formModel.end);
}); });
}); });
@ -160,27 +222,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 +250,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);
}); });

View File

@ -6,6 +6,6 @@
ng-model='ngModel' ng-model='ngModel'
field="'domain'" field="'domain'"
options="ngModel.options" options="ngModel.options"
style="position: absolute; right: 0px; bottom: 46px;" class="l-time-domain-selector"
> >
</mct-control> </mct-control>