mdbootstrap4-pro
Version:
MDBootstrap 4 PRO
770 lines (685 loc) • 25.6 kB
JavaScript
/*!
* ClockPicker v0.0.7 (http://weareoutman.github.io/clockpicker/)
* Copyright 2014 Wang Shenwei.
* Licensed under MIT (https://github.com/weareoutman/clockpicker/blob/gh-pages/LICENSE)
*
* Further modified
* Copyright 2015 Ching Yaw Hao.
*/
;(function(){
var $ = window.jQuery,
$win = $(window),
$doc = $(document);
// Can I use inline svg ?
var svgNS = 'http://www.w3.org/2000/svg',
svgSupported = 'SVGAngle' in window && (function() {
var supported,
el = document.createElement('div');
el.innerHTML = '<svg/>';
supported = (el.firstChild && el.firstChild.namespaceURI) == svgNS;
el.innerHTML = '';
return supported;
})();
// Can I use transition ?
var transitionSupported = (function() {
var style = document.createElement('div').style;
return 'transition' in style ||
'WebkitTransition' in style ||
'MozTransition' in style ||
'msTransition' in style ||
'OTransition' in style;
})();
// 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' : '');
// Vibrate the device if supported
var vibrate = navigator.vibrate ? 'vibrate' : navigator.webkitVibrate ? 'webkitVibrate' : null;
function createSvgElement(name) {
return document.createElementNS(svgNS, name);
}
function leadingZero(num) {
return (num < 10 ? '0' : '') + num;
}
// Get a unique id
var idCounter = 0;
function uniqueId(prefix) {
var id = ++idCounter + '';
return prefix ? prefix + id : id;
}
// Clock size
var dialRadius = 135,
outerRadius = 110,
// innerRadius = 80 on 12 hour clock
innerRadius = 80,
tickRadius = 20,
diameter = dialRadius * 2,
duration = transitionSupported ? 350 : 1;
// Popover template
var tpl = [
'<div class="clockpicker picker">',
'<div class="picker__holder">',
'<div class="picker__frame">',
'<div class="picker__wrap">',
'<div class="picker__box">',
'<div class="picker__date-display">',
'<div class="clockpicker-display">',
'<div class="clockpicker-display-column">',
'<span class="clockpicker-span-hours text-primary"></span>',
':',
'<span class="clockpicker-span-minutes"></span>',
'</div>',
'<div class="clockpicker-display-column clockpicker-display-am-pm">',
'<div class="clockpicker-span-am-pm"></div>',
'</div>',
'</div>',
'</div>',
'<div class="picker__calendar-container">',
'<div class="clockpicker-plate">',
'<div class="clockpicker-canvas"></div>',
'<div class="clockpicker-dial clockpicker-hours"></div>',
'<div class="clockpicker-dial clockpicker-minutes clockpicker-dial-out"></div>',
'</div>',
'<div class="clockpicker-am-pm-block">',
'</div>',
'</div>',
'<div class="picker__footer">',
'</div>',
'</div>',
'</div>',
'</div>',
'</div>',
'</div>'
].join('');
// ClockPicker
function ClockPicker(element, options) {
var popover = $(tpl),
plate = popover.find('.clockpicker-plate'),
holder = popover.find('.picker__holder'),
hoursView = popover.find('.clockpicker-hours'),
minutesView = popover.find('.clockpicker-minutes'),
amPmBlock = popover.find('.clockpicker-am-pm-block'),
isInput = element.prop('tagName') === 'INPUT',
input = isInput ? element : element.find('input'),
isHTML5 = input.prop('type') === 'time',
label = $("label[for=" + input.attr("id") + "]"),
self = this,
timer;
this.id = uniqueId('cp');
this.element = element;
this.holder = holder;
this.options = options;
this.isAppended = false;
this.isShown = false;
this.currentView = 'hours';
this.isInput = isInput;
this.input = input;
this.label = label;
this.popover = popover;
this.plate = plate;
this.hoursView = hoursView;
this.minutesView = minutesView;
this.amPmBlock = amPmBlock;
this.spanHours = popover.find('.clockpicker-span-hours');
this.spanMinutes = popover.find('.clockpicker-span-minutes');
this.spanAmPm = popover.find('.clockpicker-span-am-pm');
this.footer = popover.find('.picker__footer');
this.amOrPm = "";
// Setup for for 12 hour clock if option is selected
if (options.twelvehour) {
var amPmButtonsTemplate = [
'<div class="clockpicker-am-pm-block">',
'<button type="button" class="btn-floating btn-flat clockpicker-button clockpicker-am-button">',
'AM',
'</button>',
'<button type="button" class="btn-floating btn-flat clockpicker-button clockpicker-pm-button">',
'PM',
'</button>',
'</div>'
].join('');
var amPmButtons = $(amPmButtonsTemplate);
if (!options.ampmclickable) {
$('<button type="button" class="btn-floating btn-flat clockpicker-button am-button" tabindex="1">' + "AM" + '</button>').on("click", function() {
self.amOrPm = "AM";
self.amPmBlock.children('.pm-button').removeClass('active');
self.amPmBlock.children('.am-button').addClass('active');
self.spanAmPm.empty().append('AM');
}).appendTo(this.amPmBlock);
$('<button type="button" class="btn-floating btn-flat clockpicker-button pm-button" tabindex="2">' + "PM" + '</button>').on("click", function() {
self.amOrPm = 'PM';
self.amPmBlock.children('.am-button').removeClass('active');
self.amPmBlock.children('.pm-button').addClass('active');
self.spanAmPm.empty().append('PM');
}).appendTo(this.amPmBlock);
}
else {
this.spanAmPm.empty();
$('<div id="click-am">AM</div>').on("click", function() {
self.spanAmPm.children('#click-am').addClass("text-primary");
self.spanAmPm.children('#click-pm').removeClass("text-primary");
self.amOrPm = "AM";
}).appendTo(this.spanAmPm);
$('<div id="click-pm">PM</div>').on("click", function() {
self.spanAmPm.children('#click-pm').addClass("text-primary");
self.spanAmPm.children('#click-am').removeClass("text-primary");
self.amOrPm = 'PM';
}).appendTo(this.spanAmPm);
}
}
if(options.darktheme)
popover.addClass('darktheme');
// If autoclose is not setted, append a button
$('<button type="button" class="btn btn-flat clockpicker-button" tabindex="' + (options.twelvehour? '3' : '1') + '">' + options.donetext + '</button>').click($.proxy(this.done, this)).appendTo(this.footer);
this.spanHours.click($.proxy(this.toggleView, this, 'hours'));
this.spanMinutes.click($.proxy(this.toggleView, this, 'minutes'));
// Show or toggle
input.on('focus.clockpicker click.clockpicker', $.proxy(this.show, this));
// Build ticks
var tickTpl = $('<div class="clockpicker-tick"></div>'),
i, tick, radian, radius;
// Hours view
if (options.twelvehour) {
for (i = 0; i < 12; i += options.hourstep) {
tick = tickTpl.clone();
radian = i / 6 * Math.PI;
radius = outerRadius;
tick.css('font-size', '140%');
tick.css({
left: dialRadius + Math.sin(radian) * radius - tickRadius,
top: dialRadius - Math.cos(radian) * radius - tickRadius
});
tick.html(i === 0 ? 12 : i);
hoursView.append(tick);
tick.on(mousedownEvent, mousedown);
}
} else {
for (i = 0; i < 24; i += options.hourstep) {
tick = tickTpl.clone();
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
});
if (inner) {
tick.css('font-size', '120%');
}
tick.html(i === 0 ? '00' : i);
hoursView.append(tick);
tick.on(mousedownEvent, mousedown);
}
}
// Minutes view
var incrementValue = Math.max(options.minutestep, 5);
for (i = 0; i < 60; i += incrementValue) {
for (i = 0; i < 60; i += 5) {
tick = tickTpl.clone();
radian = i / 30 * Math.PI;
tick.css({
left: dialRadius + Math.sin(radian) * outerRadius - tickRadius,
top: dialRadius - Math.cos(radian) * outerRadius - tickRadius
});
tick.css('font-size', '140%');
tick.html(leadingZero(i));
minutesView.append(tick);
tick.on(mousedownEvent, mousedown);
}
}
// Clicking on minutes view space
plate.on(mousedownEvent, function(e) {
if ($(e.target).closest('.clockpicker-tick').length === 0)
mousedown(e, true);
});
// Mousedown or touchstart
function mousedown(e, space) {
var offset = plate.offset(),
isTouch = /^touch/.test(e.type),
x0 = offset.left + dialRadius,
y0 = offset.top + dialRadius,
dx = (isTouch ? e.originalEvent.touches[0] : e).pageX - x0,
dy = (isTouch ? e.originalEvent.touches[0] : e).pageY - y0,
z = Math.sqrt(dx * dx + dy * dy),
moved = false;
// When clicking on minutes view space, check the mouse position
if (space && (z < outerRadius - tickRadius || z > outerRadius + tickRadius))
return;
e.preventDefault();
// Set cursor style of body after 200ms
var movingTimer = setTimeout(function(){
self.popover.addClass('clockpicker-moving');
}, 200);
// Place the canvas to top
if (svgSupported)
plate.append(self.canvas);
// Clock
self.setHand(dx, dy, !space, true);
// Mousemove on document
$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)
// Clicking in chrome on windows will trigger a mousemove event
return;
moved = true;
self.setHand(x, y, false, true);
});
// Mouseup on document
$doc.off(mouseupEvent).on(mouseupEvent, function(e) {
$doc.off(mouseupEvent);
e.preventDefault();
var isTouch = /^touch/.test(e.type),
x = (isTouch ? e.originalEvent.changedTouches[0] : e).pageX - x0,
y = (isTouch ? e.originalEvent.changedTouches[0] : e).pageY - y0;
if ((space || moved) && x === dx && y === dy)
self.setHand(x, y);
if (self.currentView === 'hours')
self.toggleView('minutes', duration / 2);
else
if (options.autoclose) {
self.minutesView.addClass('clockpicker-dial-out');
setTimeout(function(){
self.done();
}, duration / 2);
}
plate.prepend(canvas);
// Reset cursor style of body
clearTimeout(movingTimer);
self.popover.removeClass('clockpicker-moving');
// Unbind mousemove event
$doc.off(mousemoveEvent);
});
}
if (svgSupported) {
// Draw clock hands and others
var canvas = popover.find('.clockpicker-canvas'),
svg = createSvgElement('svg');
svg.setAttribute('class', 'clockpicker-svg');
svg.setAttribute('width', diameter);
svg.setAttribute('height', diameter);
var g = createSvgElement('g');
g.setAttribute('transform', 'translate(' + dialRadius + ',' + dialRadius + ')');
var bearing = createSvgElement('circle');
bearing.setAttribute('class', 'clockpicker-canvas-bearing');
bearing.setAttribute('cx', 0);
bearing.setAttribute('cy', 0);
bearing.setAttribute('r', 2);
var hand = createSvgElement('line');
hand.setAttribute('x1', 0);
hand.setAttribute('y1', 0);
var bg = createSvgElement('circle');
bg.setAttribute('class', 'clockpicker-canvas-bg');
bg.setAttribute('r', tickRadius);
var fg = createSvgElement('circle');
fg.setAttribute('class', 'clockpicker-canvas-fg');
fg.setAttribute('r', 5);
g.appendChild(hand);
g.appendChild(bg);
g.appendChild(fg);
g.appendChild(bearing);
svg.appendChild(g);
canvas.append(svg);
this.hand = hand;
this.bg = bg;
this.fg = fg;
this.bearing = bearing;
this.g = g;
this.canvas = canvas;
}
raiseCallback(this.options.init);
}
function raiseCallback(callbackFunction) {
if(callbackFunction && typeof callbackFunction === "function")
callbackFunction();
}
// Default options
ClockPicker.DEFAULTS = {
'default': '', // default time, 'now' or '13:14' e.g.
fromnow: 0, // set default time to * milliseconds from now (using with default = 'now')
donetext: 'Done', // done button text
autoclose: false, // auto close when minute is selected
ampmclickable: false, // set am/pm button on itself
darktheme: false, // set to dark theme
twelvehour: false, // change to 12 hour AM/PM clock from 24 hour
vibrate: true, // vibrate the device when dragging clock hand
hourstep: 1, // allow to multi increment the hour
minutestep: 1, // allow to multi increment the minute
ampmSubmit: false, // allow submit with AM and PM buttons instead of the minute selection/picker
};
// Show or hide popover
ClockPicker.prototype.toggle = function() {
this[this.isShown ? 'hide' : 'show']();
};
// Set popover position
ClockPicker.prototype.locate = function() {
var element = this.element,
popover = this.popover,
offset = element.offset(),
width = element.outerWidth(),
height = element.outerHeight(),
align = this.options.align,
self = this;
popover.show();
};
// The input can be changed by the user
// So before we can use this.hours/this.minutes we must update it
ClockPicker.prototype.parseInputValue = function(){
var value = this.input.prop('value') || this.options['default'] || '';
if (value === 'now') {
value = new Date(+ new Date() + this.options.fromnow);
}
if (value instanceof Date) {
value = value.getHours() + ':' + value.getMinutes();
}
value = value.split(':');
// Minutes can have AM/PM that needs to be removed
this.hours = + value[0] || 0;
this.minutes = + (value[1] + '').replace(/\D/g, '') || 0;
this.hours = Math.round(this.hours / this.options.hourstep) * this.options.hourstep;
this.minutes = Math.round(this.minutes / this.options.minutestep) * this.options.minutestep;
if (this.options.twelvehour) {
var period = (value[1] + '').replace(/\d+/g, '').toLowerCase();
this.amOrPm = this.hours > 12 || period === 'pm' ? 'PM' : 'AM';
}
};
// Show popover
ClockPicker.prototype.show = function(e){
// Not show again
if (this.isShown) {
return;
}
raiseCallback(this.options.beforeShow);
$(':input').each(function() {
$(this).attr('tabindex', -1);
})
var self = this;
// Initialize
this.input.blur();
this.popover.addClass('picker--opened');
this.input.addClass('picker__input picker__input--active');
$(document.body).css('overflow', 'hidden');
if (!this.isAppended) {
// Append popover to body
this.popover.insertAfter(this.input);
if(this.options.twelvehour) {
this.amOrPm = 'PM';
if(!this.options.ampmclickable) {
this.amPmBlock.children('.am-button').removeClass('active');
this.amPmBlock.children('.pm-button').addClass('active');
this.spanAmPm.empty().append('PM');
}
else {
this.spanAmPm.children('#click-pm').addClass("text-primary");
this.spanAmPm.children('#click-am').removeClass("text-primary");
}
}
// Reset position when resize
$win.on('resize.clockpicker' + this.id, function() {
if (self.isShown) {
self.locate();
}
});
this.isAppended = true;
}
// Get the time
this.parseInputValue();
this.spanHours.html(leadingZero(this.hours));
this.spanMinutes.html(leadingZero(this.minutes));
if (this.options.twelvehour) {
this.spanAmPm.empty().append(this.amOrPm);
}
// Toggle to hours view
this.toggleView('hours');
// Set position
this.locate();
this.isShown = true;
// Hide when clicking or tabbing on any element except the clock and input
$doc.on('click.clockpicker.' + this.id + ' focusin.clockpicker.' + this.id, function(e) {
var target = $(e.target);
if (target.closest(self.popover.find('.picker__wrap')).length === 0 && target.closest(self.input).length === 0)
self.hide();
});
// Hide when ESC is pressed
$doc.on('keyup.clockpicker.' + this.id, function(e){
if (e.keyCode === 27)
self.hide();
});
raiseCallback(this.options.afterShow);
};
// Hide popover
ClockPicker.prototype.hide = function() {
raiseCallback(this.options.beforeHide);
this.input.removeClass('picker__input picker__input--active');
this.popover.removeClass('picker--opened');
$(document.body).css('overflow', 'visible');
this.isShown = false;
$(':input').each(function(index) {
$(this).attr('tabindex', index + 1);
});
// Unbinding events on document
$doc.off('click.clockpicker.' + this.id + ' focusin.clockpicker.' + this.id);
$doc.off('keyup.clockpicker.' + this.id);
this.popover.hide();
raiseCallback(this.options.afterHide);
};
// Toggle to hours or minutes view
ClockPicker.prototype.toggleView = function(view, delay) {
var raiseAfterHourSelect = false;
if (view === 'minutes' && $(this.hoursView).css("visibility") === "visible") {
raiseCallback(this.options.beforeHourSelect);
raiseAfterHourSelect = true;
}
var isHours = view === 'hours',
nextView = isHours ? this.hoursView : this.minutesView,
hideView = isHours ? this.minutesView : this.hoursView;
this.currentView = view;
this.spanHours.toggleClass('text-primary', isHours);
this.spanMinutes.toggleClass('text-primary', ! isHours);
// Let's make transitions
hideView.addClass('clockpicker-dial-out');
nextView.css('visibility', 'visible').removeClass('clockpicker-dial-out');
// Reset clock hand
this.resetClock(delay);
// After transitions ended
clearTimeout(this.toggleViewTimer);
this.toggleViewTimer = setTimeout(function() {
hideView.css('visibility', 'hidden');
}, duration);
if (raiseAfterHourSelect)
raiseCallback(this.options.afterHourSelect);
};
// Reset clock hand
ClockPicker.prototype.resetClock = function(delay) {
var view = this.currentView,
value = this[view],
isHours = view === 'hours',
unit = Math.PI / (isHours ? 6 : 30),
radian = value * unit,
radius = isHours && value > 0 && value < 13 ? innerRadius : outerRadius,
x = Math.sin(radian) * radius,
y = - Math.cos(radian) * radius,
self = this;
if(svgSupported && delay) {
self.canvas.addClass('clockpicker-canvas-out');
setTimeout(function(){
self.canvas.removeClass('clockpicker-canvas-out');
self.setHand(x, y);
}, delay);
} else
this.setHand(x, y);
};
// Set clock hand to (x, y)
ClockPicker.prototype.setHand = function(x, y, roundBy5, dragging) {
var radian = Math.atan2(x, - y),
isHours = this.currentView === 'hours',
z = Math.sqrt(x * x + y * y),
options = this.options,
inner = isHours && z < (outerRadius + innerRadius) / 2,
radius = inner ? innerRadius : outerRadius,
unit,
value;
// Calculate the unit
if (isHours) {
unit = options.hourstep / 6 * Math.PI
} else {
unit = options.minutestep / 30 * Math.PI
}
if (options.twelvehour)
radius = outerRadius;
// Radian should in range [0, 2PI]
if (radian < 0)
radian = Math.PI * 2 + radian;
// Get the round value
value = Math.round(radian / unit);
// Get the round radian
radian = value * unit;
// Correct the hours or minutes
if (isHours) {
value *= options.hourstep;
if (! options.twelvehour && (!inner)==(value>0)) {
value += 12;
}
if (options.twelvehour && value === 0) {
value = 12;
}
if (value === 24) {
value = 0;
}
} else {
value *= options.minutestep;
if (value === 60) {
value = 0;
}
}
if (isHours)
this.fg.setAttribute('class', 'clockpicker-canvas-fg');
else {
if(value % 5 == 0)
this.fg.setAttribute('class', 'clockpicker-canvas-fg');
else
this.fg.setAttribute('class', 'clockpicker-canvas-fg active');
}
// 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) {
navigator[vibrate](10);
this.vibrateTimer = setTimeout($.proxy(function(){
this.vibrateTimer = null;
}, this), 100);
}
this[this.currentView] = value;
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(){
var tick = $(this);
tick.toggleClass('active', value === + tick.html());
});
return;
}
// Place clock hand at the top when dragging
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');
} else {
// Or place it at the bottom
this.g.insertBefore(this.hand, this.bg);
this.g.insertBefore(this.fg, this.bg);
this.bg.setAttribute('class', 'clockpicker-canvas-bg');
}
// Set clock hand and others' position
var cx = Math.sin(radian) * radius,
cy = - Math.cos(radian) * radius;
this.hand.setAttribute('x2', cx);
this.hand.setAttribute('y2', cy);
this.bg.setAttribute('cx', cx);
this.bg.setAttribute('cy', cy);
this.fg.setAttribute('cx', cx);
this.fg.setAttribute('cy', cy);
};
// Allow user to get time as Date object
ClockPicker.prototype.getTime = function(callback) {
this.parseInputValue();
var hours = this.hours;
if (this.options.twelvehour && hours < 12 && this.amOrPm === 'PM') {
hours += 12;
}
var selectedTime = new Date();
selectedTime.setMinutes(this.minutes)
selectedTime.setHours(hours);
selectedTime.setSeconds(0);
return callback && callback.apply(this.element, selectedTime) || selectedTime;
}
// Hours and minutes are selected
ClockPicker.prototype.done = function() {
raiseCallback(this.options.beforeDone);
this.hide();
this.label.addClass('active');
var last = this.input.prop('value'),
outHours = this.hours,
value = ':' + leadingZero(this.minutes);
if (this.isHTML5 && this.options.twelvehour) {
if (this.hours < 12 && this.amOrPm === 'PM') {
outHours += 12;
}
if (this.hours === 12 && this.amOrPm === 'AM') {
outHours = 0;
}
}
value = leadingZero(outHours) + value;
if (!this.isHTML5 && this.options.twelvehour) {
value = value + this.amOrPm;
}
this.input.prop('value', value);
if(value !== last) {
this.input.trigger('change');
if(!this.isInput)
this.element.trigger('change');
}
if(this.options.autoclose)
this.input.trigger('blur');
raiseCallback(this.options.afterDone);
};
// Remove clockpicker from input
ClockPicker.prototype.remove = function() {
this.element.removeData('clockpicker');
this.input.off('focus.clockpicker click.clockpicker');
if (this.isShown)
this.hide();
if (this.isAppended) {
$win.off('resize.clockpicker' + this.id);
this.popover.remove();
}
};
// Extends $.fn.clockpicker
$.fn.pickatime = function(option){
var args = Array.prototype.slice.call(arguments, 1);
function handleClockPickerRequest(){
var $this = $(this),
data = $this.data('clockpicker');
if (!data) {
var options = $.extend({}, ClockPicker.DEFAULTS, $this.data(), typeof option == 'object' && option);
$this.data('clockpicker', new ClockPicker($this, options));
} else {
// Manual operations. show, hide, remove, e.g.
if (typeof data[option] === 'function')
data[option].apply(data, args);
}
}
// If we explicitly do a call on a single element then we can return the value (if needed)
// This allows us, for example, to return the value of getTime
if (this.length == 1) {
var returnValue = handleClockPickerRequest.apply(this[0]);
// If we do not have any return value then return the object itself so you can chain
return returnValue !== undefined ? returnValue : this;
}
// If we do have a list then we do not care about return values
return this.each(handleClockPickerRequest);
};
}());