',
@@ -44,9 +46,8 @@
link: function(scope, element, attrs, controllers) {
var ngModelCtrl = controllers[0];
var sgTimePaneCtrl = controllers[1];
- console.log(element);
var timePaneElement = element;
- sgTimePaneCtrl.configureNgModel(ngModelCtrl,sgTimePaneCtrl, timePaneElement);
+ sgTimePaneCtrl.configureNgModel(ngModelCtrl, sgTimePaneCtrl, timePaneElement);
}
};
}
@@ -110,15 +111,15 @@
}
}
- $scope.show5min=true;
+ $scope.show5min = true;
$scope.getToggleBtnLbl = function() {
return ($scope.is5min()) ? '>>' : '<<';
};
$scope.toggleManual5min = function() {
$scope.manual5min = !$scope.is5min();
};
- $scope.is5min=function(){
- if($scope.manual5min === true || $scope.manual5min === false) {
+ $scope.is5min = function() {
+ if ($scope.manual5min === true || $scope.manual5min === false) {
return $scope.manual5min;
}
else {
@@ -133,7 +134,8 @@
var self = this;
this.hourClickHandler = function(displayVal) {
- var updated = new Date(self.displayTime).setHours(Number(displayVal));
+ var updated = new Date(self.displayTime);
+ updated.setHours(Number(displayVal));
self.setNgModelValue(updated, 'hours');
};
$scope.hourClickHandler = this.hourClickHandler;
@@ -141,7 +143,8 @@
this.minuteClickHandler = function(displayVal) {
//remove leading ':'
var val = displayVal.substr(1);
- var updated = new Date(self.displayTime).setMinutes(Number(val));
+ var updated = new Date(self.displayTime);
+ updated.setMinutes(Number(val));
self.setNgModelValue(updated, 'minutes');
};
$scope.minuteClickHandler = this.minuteClickHandler;
@@ -152,6 +155,7 @@
TimePaneCtrl.prototype.configureNgModel = function(ngModelCtrl, sgTimePaneCtrl, timePaneElement) {
this.ngModelCtrl = ngModelCtrl;
+
var self = this;
ngModelCtrl.$render = function() {
self.changeSelectedTime(self.ngModelCtrl.$viewValue, sgTimePaneCtrl, timePaneElement);
@@ -164,8 +168,9 @@
TimePaneCtrl.prototype.changeSelectedTime = function(date, sgTimePaneCtrl, timePaneElement) {
var self = this;
var previousSelectedTime = this.selectedTime;
- this.selectedTime = new Date(date);
+ this.selectedTime = date;
this.changeDisplayTime(date).then(function() {
+
// Remove the selected class from the previously selected date, if any.
if (previousSelectedTime) {
var prevH = previousSelectedTime.getHours();
@@ -189,14 +194,13 @@
// Apply the select class to the new selected date if it is set.
if (date) {
- var d = new Date(date);
- var newH = d.getHours();
+ var newH = date.getHours();
var mCell, hCell = document.getElementById('tp-'+sgTimePaneCtrl.id+'-hour-'+newH);
if (hCell) {
hCell.classList.add(SELECTED_TIME_CLASS);
hCell.setAttribute('aria-selected', 'true');
}
- var newM = d.getMinutes();
+ var newM = date.getMinutes();
if (newM % 5 === 0) {
sgTimePaneCtrl.$scope.show5min = true;
mCell = document.getElementById('tp-'+sgTimePaneCtrl.id+'-minute5-'+newM);
@@ -372,7 +376,8 @@
// This pane will be detached from here and re-attached to the document body.
'
',
'
',
'
',
' =0 && h<=23 && m && m>=0 && m<= 59 && angular.isDate(newVal)) {
- newVal.setHours(h);
- newVal.setMinutes(m);
- this.ngModelCtrl.$setViewValue(newVal);
- this.time = newVal;
+
+ if (inputString === '') {
+ this.ngModelCtrl.$setViewValue(null);
+ this.time = null;
this.inputContainer.classList.remove(INVALID_CLASS);
}
- else {
- // If there's an input string, it's an invalid time.
+ else if (arr.length < 2) {
this.inputContainer.classList.toggle(INVALID_CLASS, inputString);
}
+ else {
+ var h = Number(arr[0]);
+ var m = Number(arr[1]);
+ var newVal = new Date(this.time);
+ if (h && h >= 0 && h <= 23 && m && m >= 0 && m <= 59 && angular.isDate(newVal)) {
+ newVal.setHours(h);
+ newVal.setMinutes(m);
+ this.ngModelCtrl.$setViewValue(newVal);
+ this.time = newVal;
+ this.inputContainer.classList.remove(INVALID_CLASS);
+ }
+ else {
+ this.inputContainer.classList.toggle(INVALID_CLASS, inputString);
+ }
+ }
};
/** Position and attach the floating calendar to the document. */
@@ -649,12 +695,36 @@
var elementRect = this.inputContainer.getBoundingClientRect();
var bodyRect = document.body.getBoundingClientRect();
- timePane.style.left = (elementRect.left - bodyRect.left) + 'px';
- timePane.style.top = (elementRect.top - bodyRect.top) + 'px';
+ // Check to see if the calendar pane would go off the screen. If so, adjust position
+ // accordingly to keep it within the viewport.
+ var paneTop = elementRect.top - bodyRect.top;
+ var paneLeft = elementRect.left - bodyRect.left;
+
+ // If the right edge of the pane would be off the screen and shifting it left by the
+ // difference would not go past the left edge of the screen.
+ var paneWidth = this.$mdMedia('sm')? TIME_PANE_WIDTH.SM : TIME_PANE_WIDTH.GTSM;
+ if (paneLeft + paneWidth > bodyRect.right &&
+ bodyRect.right - paneWidth > 0) {
+ paneLeft = bodyRect.right - paneWidth;
+ timePane.classList.add('sg-timepicker-pos-adjusted');
+ }
+ timePane.style.left = paneLeft + 'px';
+
+ // If the bottom edge of the pane would be off the screen and shifting it up by the
+ // difference would not go past the top edge of the screen.
+ var min = (typeof this.time == 'object' && this.time.getMinutes() % 5 === 0)? 'MIN5' : 'MIN1';
+ var paneHeight = this.$mdMedia('sm')? TIME_PANE_HEIGHT[min].SM : TIME_PANE_HEIGHT[min].GTSM;
+ if (paneTop + paneHeight > bodyRect.bottom &&
+ bodyRect.bottom - paneHeight > 0) {
+ paneTop = bodyRect.bottom - paneHeight;
+ timePane.classList.add('sg-timepicker-pos-adjusted');
+ }
+
+ timePane.style.top = paneTop + 'px';
document.body.appendChild(this.timePane);
// The top of the calendar pane is a transparent box that shows the text input underneath.
- // Since the pane is flowing, though, the page underneath the pane *adjacent* to the input is
+ // Since the pane is floating, though, the page underneath the pane *adjacent* to the input is
// also shown unless we cover it up. The inputMask does this by filling up the remaining space
// based on the width of the input.
this.inputMask.style.left = elementRect.width + 'px';
@@ -670,6 +740,7 @@
this.$element.removeClass('sg-timepicker-open');
this.$element.find('button').removeClass('md-primary');
this.timePane.classList.remove('md-pane-open');
+ this.timePane.classList.remove('md-timepicker-pos-adjusted');
if (this.timePane.parentNode) {
// Use native DOM removal because we do not want any of the angular state of this element
@@ -702,6 +773,8 @@
this.$mdUtil.nextTick(function() {
document.body.addEventListener('click', self.bodyClickHandler);
}, false);
+
+ window.addEventListener('resize', this.windowResizeHandler);
}
};
@@ -714,6 +787,7 @@
this.$mdUtil.enableScrolling();
document.body.removeEventListener('click', this.bodyClickHandler);
+ window.removeEventListener('resize', this.windowResizeHandler);
};
/** Gets the controller instance for the time in the floating pane. */
diff --git a/UI/WebServerResources/scss/components/timepicker/timepicker.scss b/UI/WebServerResources/scss/components/timepicker/timepicker.scss
index f1dc754b1..a02c6e467 100644
--- a/UI/WebServerResources/scss/components/timepicker/timepicker.scss
+++ b/UI/WebServerResources/scss/components/timepicker/timepicker.scss
@@ -17,6 +17,22 @@ sg-time-pane {
border-top: solid 1px rgb(224,224,224);
}
+.sg-time-scroll-mask {
+ display: inline-block;
+ overflow: hidden;
+ height: 6 * $sg-time-pane-cell-size;
+ width: 100%;
+
+ // These two properties are needed to get touch momentum to work.
+ // See https://css-tricks.com/snippets/css/momentum-scrolling-on-ios-overflow-elements
+ overflow-y: scroll;
+ -webkit-overflow-scrolling: touch;
+
+ &::-webkit-scrollbar {
+ display: none;
+ }
+}
+
.hours-pane,
.min1,
.min5 {
@@ -178,4 +194,11 @@ sg-timepicker[disabled] {
.sg-timepicker-triangle-button {
display: none;
}
-}
\ No newline at end of file
+}
+
+// When the position of the floating calendar pane is adjusted to remain inside
+// of the viewport, hide the inputput mask, as the text input will no longer be
+// directly underneath it.
+.sg-timepicker-pos-adjusted .sg-timepicker-input-mask {
+ display: none;
+}