123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730 |
- /*!
- * ClockPicker v{package.version} (http://weareoutman.github.io/clockpicker/)
- * Copyright 2014 Wang Shenwei.
- * Licensed under MIT (https://github.com/weareoutman/clockpicker/blob/gh-pages/LICENSE)
- */
- ;(function(){
- var $ = window.jQuery,
- $win = $(window),
- $doc = $(document),
- $body;
- // 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 = 100,
- outerRadius = 80,
- // innerRadius = 80 on 12 hour clock
- innerRadius = 54,
- tickRadius = 13,
- diameter = dialRadius * 2,
- duration = transitionSupported ? 350 : 1;
- // Popover template
- var tpl = [
- '<div class="popover clockpicker-popover">',
- '<div class="arrow"></div>',
- '<div class="popover-title">',
- '<span class="clockpicker-span-hours text-primary"></span>',
- ' : ',
- '<span class="clockpicker-span-minutes"></span>',
- '<span class="clockpicker-span-am-pm"></span>',
- '</div>',
- '<div class="popover-content">',
- '<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>',
- '<span class="clockpicker-am-pm-block">',
- '</span>',
- '</div>',
- '</div>'
- ].join('');
- // ClockPicker
- function ClockPicker(element, options) {
- var popover = $(tpl),
- plate = popover.find('.clockpicker-plate'),
- 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'),
- addon = element.find('.input-group-addon'),
- self = this,
- timer;
- this.id = uniqueId('cp');
- this.element = element;
- this.options = options;
- this.isAppended = false;
- this.isShown = false;
- this.currentView = 'hours';
- this.isInput = isInput;
- this.input = input;
- this.addon = addon;
- 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.amOrPm = "PM";
- // 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 btn-sm btn-default clockpicker-button clockpicker-am-button">',
- 'AM</button>',
- '<button type="button" class="btn btn-sm btn-default clockpicker-button clockpicker-pm-button">',
- 'PM</button>',
- '</div>'].join('');
- var amPmButtons = $(amPmButtonsTemplate);
- //amPmButtons.appendTo(plate);
- ////Not working b/c they are not shown when this runs
- //$('clockpicker-am-button')
- // .on("click", function() {
- // self.amOrPm = "AM";
- // $('.clockpicker-span-am-pm').empty().append('AM');
- // });
- //
- //$('clockpicker-pm-button')
- // .on("click", function() {
- // self.amOrPm = "PM";
- // $('.clockpicker-span-am-pm').empty().append('PM');
- // });
- $('<button type="button" class="btn btn-sm btn-default clockpicker-button am-button">' + "AM" + '</button>')
- .on("click", function() {
- self.amOrPm = "AM";
- $('.clockpicker-span-am-pm').empty().append('AM');
- }).appendTo(this.amPmBlock);
- $('<button type="button" class="btn btn-sm btn-default clockpicker-button pm-button">' + "PM" + '</button>')
- .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
- $('<button type="button" class="btn btn-sm btn-default btn-block clockpicker-button">' + options.donetext + '</button>')
- .click($.proxy(this.done, this))
- .appendTo(popover);
- }
- // 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';
- popover.addClass(options.placement);
- popover.addClass('clockpicker-align-' + options.align);
- 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));
- addon.on('click.clockpicker', $.proxy(this.toggle, this));
- // Build ticks
- var tickTpl = $('<div class="clockpicker-tick"></div>'),
- i, tick, radian, radius;
- // Hours view
- if (options.twelvehour) {
- for (i = 1; i < 13; i += 1) {
- tick = tickTpl.clone();
- radian = i / 6 * Math.PI;
- radius = outerRadius;
- tick.css('font-size', '120%');
- tick.css({
- left: dialRadius + Math.sin(radian) * radius - tickRadius,
- top: dialRadius - Math.cos(radian) * radius - tickRadius
- });
- tick.html(i === 0 ? '00' : i);
- hoursView.append(tick);
- tick.on(mousedownEvent, mousedown);
- }
- } else {
- for (i = 0; i < 24; i += 1) {
- 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
- 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', '120%');
- 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(){
- $body.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);
- $body.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', 3.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')
- placement: 'bottom', // clock popover placement
- align: 'left', // popover arrow align
- donetext: '完成', // done button text
- autoclose: false, // auto close when minute is selected
- twelvehour: false, // change to 12 hour AM/PM clock from 24 hour
- vibrate: true // vibrate the device when dragging clock hand
- };
- // 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(),
- placement = this.options.placement,
- align = this.options.align,
- styles = {},
- self = this;
- popover.show();
- // Place the popover
- switch (placement) {
- case 'bottom':
- styles.top = offset.top + height;
- break;
- case 'right':
- styles.left = offset.left + width;
- break;
- case 'top':
- styles.top = offset.top - popover.outerHeight();
- break;
- case 'left':
- styles.left = offset.left - popover.outerWidth();
- break;
- }
- // Align the popover arrow
- switch (align) {
- case 'left':
- styles.left = offset.left;
- break;
- case 'right':
- styles.left = offset.left + width - popover.outerWidth();
- break;
- case 'top':
- styles.top = offset.top;
- break;
- case 'bottom':
- styles.top = offset.top + height - popover.outerHeight();
- break;
- }
- popover.css(styles);
- };
- // Show popover
- ClockPicker.prototype.show = function(e){
- // Not show again
- if (this.isShown) {
- return;
- }
- raiseCallback(this.options.beforeShow);
- var self = this;
- // Initialize
- if (! this.isAppended) {
- // Append popover to body
- $body = $(document.body).append(this.popover);
- // Reset position when resize
- $win.on('resize.clockpicker' + this.id, function(){
- if (self.isShown) {
- self.locate();
- }
- });
- this.isAppended = true;
- }
- // Get the time
- var value = ((this.input.prop('value') || this.options['default'] || '') + '').split(':');
- if (value[0] === 'now') {
- var now = new Date(+ new Date() + this.options.fromnow);
- value = [
- now.getHours(),
- now.getMinutes()
- ];
- }
- this.hours = + value[0] || 0;
- this.minutes = + value[1] || 0;
- this.spanHours.html(leadingZero(this.hours));
- this.spanMinutes.html(leadingZero(this.minutes));
- // 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, input and addon
- $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) {
- 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.isShown = false;
- // 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',
- unit = Math.PI / (isHours || roundBy5 ? 6 : 30),
- z = Math.sqrt(x * x + y * y),
- options = this.options,
- inner = isHours && z < (outerRadius + innerRadius) / 2,
- radius = inner ? innerRadius : outerRadius,
- value;
- 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 (options.twelvehour) {
- if (isHours) {
- if (value === 0) {
- value = 12;
- }
- } else {
- if (roundBy5) {
- value *= 5;
- }
- if (value === 60) {
- value = 0;
- }
- }
- } else {
- if (isHours) {
- if (value === 12) {
- value = 0;
- }
- value = inner ? (value === 0 ? 12 : value) : value === 0 ? 0 : value + 12;
- } else {
- if (roundBy5) {
- value *= 5;
- }
- if (value === 60) {
- value = 0;
- }
- }
- }
- // 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);
- };
- // Hours and minutes are selected
- 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) {
- value = value + this.amOrPm;
- }
- this.input.prop('value', value);
- if (value !== last) {
- this.input.triggerHandler('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');
- this.addon.off('click.clockpicker');
- if (this.isShown) {
- this.hide();
- }
- if (this.isAppended) {
- $win.off('resize.clockpicker' + this.id);
- this.popover.remove();
- }
- };
- // Extends $.fn.clockpicker
- $.fn.clockpicker = function(option){
- var args = Array.prototype.slice.call(arguments, 1);
- return this.each(function(){
- 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 operatsions. show, hide, remove, e.g.
- if (typeof data[option] === 'function') {
- data[option].apply(data, args);
- }
- }
- });
- };
- }());
|