diff --git a/src/clockpicker.css b/src/clockpicker.css
index 7d90a24..901b08c 100644
--- a/src/clockpicker.css
+++ b/src/clockpicker.css
@@ -81,10 +81,14 @@
cursor: pointer;
}
.clockpicker-tick.active,
-.clockpicker-tick:hover {
+.clockpicker-tick:not(.invalid):hover {
background-color: rgb(192, 229, 247);
background-color: rgba(0, 149, 221, .25);
}
+.clockpicker-tick.invalid {
+ color:#ccc;
+ cursor: not-allowed;
+}
.clockpicker-button {
background-image: none;
background-color: #fff;
diff --git a/src/clockpicker.js b/src/clockpicker.js
index 7f208aa..17237ad 100644
--- a/src/clockpicker.js
+++ b/src/clockpicker.js
@@ -4,7 +4,7 @@
* Licensed under MIT (https://github.com/weareoutman/clockpicker/blob/gh-pages/LICENSE)
*/
-;(function(){
+; (function () {
var $ = window.jQuery,
$win = $(window),
$doc = $(document),
@@ -12,7 +12,7 @@
// Can I use inline svg ?
var svgNS = 'http://www.w3.org/2000/svg',
- svgSupported = 'SVGAngle' in window && (function(){
+ svgSupported = 'SVGAngle' in window && (function () {
var supported,
el = document.createElement('div');
el.innerHTML = '';
@@ -22,7 +22,7 @@
})();
// Can I use transition ?
- var transitionSupported = (function(){
+ var transitionSupported = (function () {
var style = document.createElement('div').style;
return 'transition' in style ||
'WebkitTransition' in style ||
@@ -33,9 +33,9 @@
// Listen touch events in touch screen device, instead of mouse events in desktop.
var touchSupported = 'ontouchstart' in window,
- mousedownEvent = 'mousedown' + ( touchSupported ? ' touchstart' : ''),
- mousemoveEvent = 'mousemove.clockpicker' + ( touchSupported ? ' touchmove.clockpicker' : ''),
- mouseupEvent = 'mouseup.clockpicker' + ( touchSupported ? ' touchend.clockpicker' : '');
+ mousedownEvent = 'mousedown' + (touchSupported ? ' touchstart' : ''),
+ mousemoveEvent = 'mousemove.clockpicker' + (touchSupported ? ' touchmove.clockpicker' : ''),
+ mouseupEvent = 'mouseup.clockpicker' + (touchSupported ? ' touchend.clockpicker' : '');
// Vibrate the device if supported
var vibrate = navigator.vibrate ? 'vibrate' : navigator.webkitVibrate ? 'webkitVibrate' : null;
@@ -67,22 +67,22 @@
// Popover template
var tpl = [
'
',
+
+ var amPmButtonsTemplate = ['
',
'',
'',
'
'].join('');
-
+
var amPmButtons = $(amPmButtonsTemplate);
//amPmButtons.appendTo(plate);
-
+
////Not working b/c they are not shown when this runs
//$('clockpicker-am-button')
// .on("click", function() {
@@ -143,29 +147,60 @@
// self.amOrPm = "PM";
// $('.clockpicker-span-am-pm').empty().append('PM');
// });
-
+
$('
')
- .on("click", function() {
+ .on("click", function () {
self.amOrPm = "AM";
$('.clockpicker-span-am-pm').empty().append('AM');
}).appendTo(this.amPmBlock);
-
-
+
+
$('
')
- .on("click", function() {
+ .on("click", function () {
self.amOrPm = 'PM';
$('.clockpicker-span-am-pm').empty().append('PM');
}).appendTo(this.amPmBlock);
-
+
}
-
- if (! options.autoclose) {
- // If autoclose is not setted, append a button
+
+ if (!options.autoclose) {
+ // If autoclose is not set, append a button
$('
')
.click($.proxy(this.done, this))
.appendTo(popover);
}
+ // Parse Min Option
+ if (options.min) {
+ this.min = options.min.split(':');
+ if (this.min.length !== 2) {
+ this.min = null;
+ }
+ else {
+ for (let i = 0; i < this.min.length; i++) {
+ this.min[i]= +this.min[i];
+ }
+ }
+ }
+
+ // Parse Max Option
+ if (options.max) {
+ this.max = options.max.split(':');
+ if (this.max.length !== 2) {
+ this.max = null;
+ }
+ else {
+ for (let i = 0; i < this.max.length; i++) {
+ this.max[i]= +this.max[i];
+ }
+
+ // Ensure that max is after min, if not remove the max it's invalid
+ if (this.min && ((this.min[0] > this.max[0]) || (this.min[0] === this.max[0] && this.min[1] >= this.max[1]))) {
+ this.max = null;
+ }
+ }
+ }
+
// Placement and arrow align - make sure they make sense.
if ((options.placement === 'top' || options.placement === 'bottom') && (options.align === 'top' || options.align === 'bottom')) options.align = 'left';
if ((options.placement === 'left' || options.placement === 'right') && (options.align === 'left' || options.align === 'right')) options.align = 'top';
@@ -205,6 +240,7 @@
radian = i / 6 * Math.PI;
var inner = i > 0 && i < 13;
radius = inner ? innerRadius : outerRadius;
+
tick.css({
left: dialRadius + Math.sin(radian) * radius - tickRadius,
top: dialRadius - Math.cos(radian) * radius - tickRadius
@@ -212,8 +248,15 @@
if (inner) {
tick.css('font-size', '120%');
}
+
+ // Should any hours be disabled given the configured minimum value
+ if ((this.min && i < this.min[0]) || (this.max && i > this.max[0])) {
+ tick.addClass('invalid');
+ }
+
tick.html(i === 0 ? '00' : i);
hoursView.append(tick);
+
tick.on(mousedownEvent, mousedown);
}
}
@@ -227,13 +270,15 @@
top: dialRadius - Math.cos(radian) * outerRadius - tickRadius
});
tick.css('font-size', '120%');
+
tick.html(leadingZero(i));
minutesView.append(tick);
+
tick.on(mousedownEvent, mousedown);
}
// Clicking on minutes view space
- plate.on(mousedownEvent, function(e){
+ plate.on(mousedownEvent, function (e) {
if ($(e.target).closest('.clockpicker-tick').length === 0) {
mousedown(e, true);
}
@@ -250,6 +295,11 @@
z = Math.sqrt(dx * dx + dy * dy),
moved = false;
+ // If current element is invalid, do nothing with click
+ if ($(e.target).hasClass('invalid')) {
+ return;
+ }
+
// When clicking on minutes view space, check the mouse position
if (space && (z < outerRadius - tickRadius || z > outerRadius + tickRadius)) {
return;
@@ -257,7 +307,7 @@
e.preventDefault();
// Set cursor style of body after 200ms
- var movingTimer = setTimeout(function(){
+ var movingTimer = setTimeout(function () {
$body.addClass('clockpicker-moving');
}, 200);
@@ -267,15 +317,15 @@
}
// Clock
- self.setHand(dx, dy, ! space, true);
+ self.setHand(dx, dy, !space, true);
// Mousemove on document
- $doc.off(mousemoveEvent).on(mousemoveEvent, function(e){
+ $doc.off(mousemoveEvent).on(mousemoveEvent, function (e) {
e.preventDefault();
var isTouch = /^touch/.test(e.type),
x = (isTouch ? e.originalEvent.touches[0] : e).pageX - x0,
y = (isTouch ? e.originalEvent.touches[0] : e).pageY - y0;
- if (! moved && x === dx && y === dy) {
+ if (!moved && x === dx && y === dy) {
// Clicking in chrome on windows will trigger a mousemove event
return;
}
@@ -284,7 +334,7 @@
});
// Mouseup on document
- $doc.off(mouseupEvent).on(mouseupEvent, function(e){
+ $doc.off(mouseupEvent).on(mouseupEvent, function (e) {
$doc.off(mouseupEvent);
e.preventDefault();
var isTouch = /^touch/.test(e.type),
@@ -298,7 +348,7 @@
} else {
if (options.autoclose) {
self.minutesView.addClass('clockpicker-dial-out');
- setTimeout(function(){
+ setTimeout(function () {
self.done();
}, duration / 2);
}
@@ -374,12 +424,12 @@
};
// Show or hide popover
- ClockPicker.prototype.toggle = function(){
+ ClockPicker.prototype.toggle = function () {
this[this.isShown ? 'hide' : 'show']();
};
// Set popover position
- ClockPicker.prototype.locate = function(){
+ ClockPicker.prototype.locate = function () {
var element = this.element,
popover = this.popover,
offset = element.offset(),
@@ -428,7 +478,7 @@
};
// Show popover
- ClockPicker.prototype.show = function(e){
+ ClockPicker.prototype.show = function (e) {
// Not show again
if (this.isShown) {
return;
@@ -439,12 +489,12 @@
var self = this;
// Initialize
- if (! this.isAppended) {
+ if (!this.isAppended) {
// Append popover to body
$body = $(document.body).append(this.popover);
// Reset position when resize
- $win.on('resize.clockpicker' + this.id, function(){
+ $win.on('resize.clockpicker' + this.id, function () {
if (self.isShown) {
self.locate();
}
@@ -462,8 +512,8 @@
now.getMinutes()
];
}
- this.hours = + value[0] || 0;
- this.minutes = + value[1] || 0;
+ this.hours = +value[0] || 0;
+ this.minutes = +value[1] || 0;
this.spanHours.html(leadingZero(this.hours));
this.spanMinutes.html(leadingZero(this.minutes));
@@ -476,17 +526,17 @@
this.isShown = true;
// Hide when clicking or tabbing on any element except the clock, input and addon
- $doc.on('click.clockpicker.' + this.id + ' focusin.clockpicker.' + this.id, function(e){
+ $doc.on('click.clockpicker.' + this.id + ' focusin.clockpicker.' + this.id, function (e) {
var target = $(e.target);
if (target.closest(self.popover).length === 0 &&
- target.closest(self.addon).length === 0 &&
- target.closest(self.input).length === 0) {
+ target.closest(self.addon).length === 0 &&
+ target.closest(self.input).length === 0) {
self.hide();
}
});
// Hide when ESC is pressed
- $doc.on('keyup.clockpicker.' + this.id, function(e){
+ $doc.on('keyup.clockpicker.' + this.id, function (e) {
if (e.keyCode === 27) {
self.hide();
}
@@ -496,7 +546,7 @@
};
// Hide popover
- ClockPicker.prototype.hide = function(){
+ ClockPicker.prototype.hide = function () {
raiseCallback(this.options.beforeHide);
this.isShown = false;
@@ -511,7 +561,7 @@
};
// Toggle to hours or minutes view
- ClockPicker.prototype.toggleView = function(view, delay){
+ ClockPicker.prototype.toggleView = function (view, delay) {
var raiseAfterHourSelect = false;
if (view === 'minutes' && $(this.hoursView).css("visibility") === "visible") {
raiseCallback(this.options.beforeHourSelect);
@@ -524,7 +574,7 @@
this.currentView = view;
this.spanHours.toggleClass('text-primary', isHours);
- this.spanMinutes.toggleClass('text-primary', ! isHours);
+ this.spanMinutes.toggleClass('text-primary', !isHours);
// Let's make transitions
hideView.addClass('clockpicker-dial-out');
@@ -535,21 +585,76 @@
// After transitions ended
clearTimeout(this.toggleViewTimer);
- this.toggleViewTimer = setTimeout(function(){
+ this.toggleViewTimer = setTimeout(function () {
hideView.css('visibility', 'hidden');
}, duration);
+ // Check if the minutes need to be disabled for the selected hour (Based on the minimum value)
+ if (this.min || this.max) {
+ var ticks = this.minutesView.find('div.clockpicker-tick');
+ var tick,
+ tickValue;
+
+ if (!this.hours) {
+ this.hours = parseInt(this.spanHours.html());
+ }
+ if (!this.minutes) {
+ this.minutes = parseInt(this.spanMinutes.html());
+ }
+ if (!isHours && (this.hours === this.min[0])) {
+ // mark items as invalid for minutes if min hour applies
+ for (let i = 0; i < ticks.length; i++) {
+ tick = $(ticks[i]);
+ tickValue = parseInt(tick.html());
+ if (tickValue < this.min[1]) {
+ tick.addClass('invalid');
+ }
+ }
+ }
+ else if (!isHours && (this.hours === this.max[0])) {
+ // mark items as invalid for minutes if max hour applies
+ for (let i = 0; i < ticks.length; i++) {
+ tick = $(ticks[i]);
+ tickValue = parseInt(tick.html());
+ if (tickValue > this.max[1]) {
+ tick.addClass('invalid');
+ }
+ }
+ }
+ else {
+ for (let i = 0; i < ticks.length; i++) {
+ tick = $(ticks[i]);
+ tick.removeClass('invalid');
+ }
+ }
+ }
+
if (raiseAfterHourSelect) {
raiseCallback(this.options.afterHourSelect);
}
};
// Reset clock hand
- ClockPicker.prototype.resetClock = function(delay){
+ ClockPicker.prototype.resetClock = function (delay) {
var view = this.currentView,
value = this[view],
- isHours = view === 'hours',
- unit = Math.PI / (isHours ? 6 : 30),
+ isHours = view === 'hours';
+
+ // Update an invalid value to the closest valie one
+ if (!isHours && this.min && this.hours <= this.min[0] && value < this.min[1]) {
+ value = this.min[1];
+ }
+ else if (!isHours && this.max && this.hours >= this.max[0] && value > this.max[1]) {
+ value = this.max[1];
+ }
+ else if (isHours && this.min && value < this.min[0]) {
+ value = this.min[0];
+ }
+ else if (isHours && this.max && value > this.max[0]) {
+ value = this.max[0];
+ }
+
+ var unit = Math.PI / (isHours ? 6 : 30),
radian = value * unit,
radius = isHours && value > 0 && value < 13 ? innerRadius : outerRadius,
x = Math.sin(radian) * radius,
@@ -557,7 +662,7 @@
self = this;
if (svgSupported && delay) {
self.canvas.addClass('clockpicker-canvas-out');
- setTimeout(function(){
+ setTimeout(function () {
self.canvas.removeClass('clockpicker-canvas-out');
self.setHand(x, y);
}, delay);
@@ -567,7 +672,7 @@
};
// Set clock hand to (x, y)
- ClockPicker.prototype.setHand = function(x, y, roundBy5, dragging){
+ ClockPicker.prototype.setHand = function (x, y, roundBy5, dragging) {
var radian = Math.atan2(x, - y),
isHours = this.currentView === 'hours',
unit = Math.PI / (isHours || roundBy5 ? 6 : 30),
@@ -576,10 +681,10 @@
inner = isHours && z < (outerRadius + innerRadius) / 2,
radius = inner ? innerRadius : outerRadius,
value;
-
- if (options.twelvehour) {
- radius = outerRadius;
- }
+
+ if (options.twelvehour) {
+ radius = outerRadius;
+ }
// Radian should in range [0, 2PI]
if (radian < 0) {
@@ -621,14 +726,19 @@
}
}
}
-
+
+ // Cancel action if below the minimum or above the maximum
+ if ((isHours && (value < this.min[0] || value > this.max[0])) || (!isHours && ((this.hours <= this.min[0] && value < this.min[1] || (this.hours >= this.max[0] && value > this.max[1]))))) {
+ return;
+ }
+
// Once hours or minutes changed, vibrate the device
if (this[this.currentView] !== value) {
if (vibrate && this.options.vibrate) {
// Do not vibrate too frequently
- if (! this.vibrateTimer) {
+ if (!this.vibrateTimer) {
navigator[vibrate](10);
- this.vibrateTimer = setTimeout($.proxy(function(){
+ this.vibrateTimer = setTimeout($.proxy(function () {
this.vibrateTimer = null;
}, this), 100);
}
@@ -639,8 +749,8 @@
this[isHours ? 'spanHours' : 'spanMinutes'].html(leadingZero(value));
// If svg is not supported, just add an active class to the tick
- if (! svgSupported) {
- this[isHours ? 'hoursView' : 'minutesView'].find('.clockpicker-tick').each(function(){
+ if (!svgSupported) {
+ this[isHours ? 'hoursView' : 'minutesView'].find('.clockpicker-tick').each(function () {
var tick = $(this);
tick.toggleClass('active', value === + tick.html());
});
@@ -648,7 +758,7 @@
}
// Place clock hand at the top when dragging
- if (dragging || (! isHours && value % 5)) {
+ if (dragging || (!isHours && value % 5)) {
this.g.insertBefore(this.hand, this.bearing);
this.g.insertBefore(this.bg, this.fg);
this.bg.setAttribute('class', 'clockpicker-canvas-bg clockpicker-canvas-bg-trans');
@@ -671,19 +781,22 @@
};
// Hours and minutes are selected
- ClockPicker.prototype.done = function() {
+ ClockPicker.prototype.done = function () {
raiseCallback(this.options.beforeDone);
this.hide();
- var last = this.input.prop('value'),
- value = leadingZero(this.hours) + ':' + leadingZero(this.minutes);
- if (this.options.twelvehour) {
+ var last = this.input.prop('value');
+ if (!Number.isInteger(this.hours)) {
+ this.hours = parseInt(this.spanHours.html());
+ }
+ var value = leadingZero(this.hours) + ':' + leadingZero(this.minutes);
+ if (this.options.twelvehour) {
value = value + this.amOrPm;
}
-
+
this.input.prop('value', value);
if (value !== last) {
this.input.triggerHandler('change');
- if (! this.isInput) {
+ if (!this.isInput) {
this.element.trigger('change');
}
}
@@ -696,7 +809,7 @@
};
// Remove clockpicker from input
- ClockPicker.prototype.remove = function() {
+ ClockPicker.prototype.remove = function () {
this.element.removeData('clockpicker');
this.input.off('focus.clockpicker click.clockpicker');
this.addon.off('click.clockpicker');
@@ -710,12 +823,12 @@
};
// Extends $.fn.clockpicker
- $.fn.clockpicker = function(option){
+ $.fn.clockpicker = function (option) {
var args = Array.prototype.slice.call(arguments, 1);
- return this.each(function(){
+ return this.each(function () {
var $this = $(this),
data = $this.data('clockpicker');
- if (! data) {
+ if (!data) {
var options = $.extend({}, ClockPicker.DEFAULTS, $this.data(), typeof option == 'object' && option);
$this.data('clockpicker', new ClockPicker($this, options));
} else {