648 lines
20 KiB
JavaScript
648 lines
20 KiB
JavaScript
(function($) {
|
|
'use strict';
|
|
|
|
let _defaults = {
|
|
dialRadius: 135,
|
|
outerRadius: 105,
|
|
innerRadius: 70,
|
|
tickRadius: 20,
|
|
duration: 350,
|
|
container: null,
|
|
defaultTime: 'now', // default time, 'now' or '13:14' e.g.
|
|
fromNow: 0, // Millisecond offset from the defaultTime
|
|
showClearBtn: false,
|
|
|
|
// internationalization
|
|
i18n: {
|
|
cancel: 'Cancel',
|
|
clear: 'Clear',
|
|
done: 'Ok'
|
|
},
|
|
|
|
autoClose: false, // auto close when minute is selected
|
|
twelveHour: true, // change to 12 hour AM/PM clock from 24 hour
|
|
vibrate: true, // vibrate the device when dragging clock hand
|
|
|
|
// Callbacks
|
|
onOpenStart: null,
|
|
onOpenEnd: null,
|
|
onCloseStart: null,
|
|
onCloseEnd: null,
|
|
onSelect: null
|
|
};
|
|
|
|
/**
|
|
* @class
|
|
*
|
|
*/
|
|
class Timepicker extends Component {
|
|
constructor(el, options) {
|
|
super(Timepicker, el, options);
|
|
|
|
this.el.M_Timepicker = this;
|
|
|
|
this.options = $.extend({}, Timepicker.defaults, options);
|
|
|
|
this.id = M.guid();
|
|
this._insertHTMLIntoDOM();
|
|
this._setupModal();
|
|
this._setupVariables();
|
|
this._setupEventHandlers();
|
|
|
|
this._clockSetup();
|
|
this._pickerSetup();
|
|
}
|
|
|
|
static get defaults() {
|
|
return _defaults;
|
|
}
|
|
|
|
static init(els, options) {
|
|
return super.init(this, els, options);
|
|
}
|
|
|
|
static _addLeadingZero(num) {
|
|
return (num < 10 ? '0' : '') + num;
|
|
}
|
|
|
|
static _createSVGEl(name) {
|
|
let svgNS = 'http://www.w3.org/2000/svg';
|
|
return document.createElementNS(svgNS, name);
|
|
}
|
|
|
|
/**
|
|
* @typedef {Object} Point
|
|
* @property {number} x The X Coordinate
|
|
* @property {number} y The Y Coordinate
|
|
*/
|
|
|
|
/**
|
|
* Get x position of mouse or touch event
|
|
* @param {Event} e
|
|
* @return {Point} x and y location
|
|
*/
|
|
static _Pos(e) {
|
|
if (e.targetTouches && e.targetTouches.length >= 1) {
|
|
return { x: e.targetTouches[0].clientX, y: e.targetTouches[0].clientY };
|
|
}
|
|
// mouse event
|
|
return { x: e.clientX, y: e.clientY };
|
|
}
|
|
|
|
/**
|
|
* Get Instance
|
|
*/
|
|
static getInstance(el) {
|
|
let domElem = !!el.jquery ? el[0] : el;
|
|
return domElem.M_Timepicker;
|
|
}
|
|
|
|
/**
|
|
* Teardown component
|
|
*/
|
|
destroy() {
|
|
this._removeEventHandlers();
|
|
this.modal.destroy();
|
|
$(this.modalEl).remove();
|
|
this.el.M_Timepicker = undefined;
|
|
}
|
|
|
|
/**
|
|
* Setup Event Handlers
|
|
*/
|
|
_setupEventHandlers() {
|
|
this._handleInputKeydownBound = this._handleInputKeydown.bind(this);
|
|
this._handleInputClickBound = this._handleInputClick.bind(this);
|
|
this._handleClockClickStartBound = this._handleClockClickStart.bind(this);
|
|
this._handleDocumentClickMoveBound = this._handleDocumentClickMove.bind(this);
|
|
this._handleDocumentClickEndBound = this._handleDocumentClickEnd.bind(this);
|
|
|
|
this.el.addEventListener('click', this._handleInputClickBound);
|
|
this.el.addEventListener('keydown', this._handleInputKeydownBound);
|
|
this.plate.addEventListener('mousedown', this._handleClockClickStartBound);
|
|
this.plate.addEventListener('touchstart', this._handleClockClickStartBound);
|
|
|
|
$(this.spanHours).on('click', this.showView.bind(this, 'hours'));
|
|
$(this.spanMinutes).on('click', this.showView.bind(this, 'minutes'));
|
|
}
|
|
|
|
_removeEventHandlers() {
|
|
this.el.removeEventListener('click', this._handleInputClickBound);
|
|
this.el.removeEventListener('keydown', this._handleInputKeydownBound);
|
|
}
|
|
|
|
_handleInputClick() {
|
|
this.open();
|
|
}
|
|
|
|
_handleInputKeydown(e) {
|
|
if (e.which === M.keys.ENTER) {
|
|
e.preventDefault();
|
|
this.open();
|
|
}
|
|
}
|
|
|
|
_handleClockClickStart(e) {
|
|
e.preventDefault();
|
|
let clockPlateBR = this.plate.getBoundingClientRect();
|
|
let offset = { x: clockPlateBR.left, y: clockPlateBR.top };
|
|
|
|
this.x0 = offset.x + this.options.dialRadius;
|
|
this.y0 = offset.y + this.options.dialRadius;
|
|
this.moved = false;
|
|
let clickPos = Timepicker._Pos(e);
|
|
this.dx = clickPos.x - this.x0;
|
|
this.dy = clickPos.y - this.y0;
|
|
|
|
// Set clock hands
|
|
this.setHand(this.dx, this.dy, false);
|
|
|
|
// Mousemove on document
|
|
document.addEventListener('mousemove', this._handleDocumentClickMoveBound);
|
|
document.addEventListener('touchmove', this._handleDocumentClickMoveBound);
|
|
|
|
// Mouseup on document
|
|
document.addEventListener('mouseup', this._handleDocumentClickEndBound);
|
|
document.addEventListener('touchend', this._handleDocumentClickEndBound);
|
|
}
|
|
|
|
_handleDocumentClickMove(e) {
|
|
e.preventDefault();
|
|
let clickPos = Timepicker._Pos(e);
|
|
let x = clickPos.x - this.x0;
|
|
let y = clickPos.y - this.y0;
|
|
this.moved = true;
|
|
this.setHand(x, y, false, true);
|
|
}
|
|
|
|
_handleDocumentClickEnd(e) {
|
|
e.preventDefault();
|
|
document.removeEventListener('mouseup', this._handleDocumentClickEndBound);
|
|
document.removeEventListener('touchend', this._handleDocumentClickEndBound);
|
|
let clickPos = Timepicker._Pos(e);
|
|
let x = clickPos.x - this.x0;
|
|
let y = clickPos.y - this.y0;
|
|
if (this.moved && x === this.dx && y === this.dy) {
|
|
this.setHand(x, y);
|
|
}
|
|
|
|
if (this.currentView === 'hours') {
|
|
this.showView('minutes', this.options.duration / 2);
|
|
} else if (this.options.autoClose) {
|
|
$(this.minutesView).addClass('timepicker-dial-out');
|
|
setTimeout(() => {
|
|
this.done();
|
|
}, this.options.duration / 2);
|
|
}
|
|
|
|
if (typeof this.options.onSelect === 'function') {
|
|
this.options.onSelect.call(this, this.hours, this.minutes);
|
|
}
|
|
|
|
// Unbind mousemove event
|
|
document.removeEventListener('mousemove', this._handleDocumentClickMoveBound);
|
|
document.removeEventListener('touchmove', this._handleDocumentClickMoveBound);
|
|
}
|
|
|
|
_insertHTMLIntoDOM() {
|
|
this.$modalEl = $(Timepicker._template);
|
|
this.modalEl = this.$modalEl[0];
|
|
this.modalEl.id = 'modal-' + this.id;
|
|
|
|
// Append popover to input by default
|
|
let containerEl = document.querySelector(this.options.container);
|
|
if (this.options.container && !!containerEl) {
|
|
this.$modalEl.appendTo(containerEl);
|
|
} else {
|
|
this.$modalEl.insertBefore(this.el);
|
|
}
|
|
}
|
|
|
|
_setupModal() {
|
|
this.modal = M.Modal.init(this.modalEl, {
|
|
onOpenStart: this.options.onOpenStart,
|
|
onOpenEnd: this.options.onOpenEnd,
|
|
onCloseStart: this.options.onCloseStart,
|
|
onCloseEnd: () => {
|
|
if (typeof this.options.onCloseEnd === 'function') {
|
|
this.options.onCloseEnd.call(this);
|
|
}
|
|
this.isOpen = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
_setupVariables() {
|
|
this.currentView = 'hours';
|
|
this.vibrate = navigator.vibrate
|
|
? 'vibrate'
|
|
: navigator.webkitVibrate
|
|
? 'webkitVibrate'
|
|
: null;
|
|
|
|
this._canvas = this.modalEl.querySelector('.timepicker-canvas');
|
|
this.plate = this.modalEl.querySelector('.timepicker-plate');
|
|
|
|
this.hoursView = this.modalEl.querySelector('.timepicker-hours');
|
|
this.minutesView = this.modalEl.querySelector('.timepicker-minutes');
|
|
this.spanHours = this.modalEl.querySelector('.timepicker-span-hours');
|
|
this.spanMinutes = this.modalEl.querySelector('.timepicker-span-minutes');
|
|
this.spanAmPm = this.modalEl.querySelector('.timepicker-span-am-pm');
|
|
this.footer = this.modalEl.querySelector('.timepicker-footer');
|
|
this.amOrPm = 'PM';
|
|
}
|
|
|
|
_pickerSetup() {
|
|
let $clearBtn = $(
|
|
`<button class="btn-flat timepicker-clear waves-effect" style="visibility: hidden;" type="button" tabindex="${
|
|
this.options.twelveHour ? '3' : '1'
|
|
}">${this.options.i18n.clear}</button>`
|
|
)
|
|
.appendTo(this.footer)
|
|
.on('click', this.clear.bind(this));
|
|
if (this.options.showClearBtn) {
|
|
$clearBtn.css({ visibility: '' });
|
|
}
|
|
|
|
let confirmationBtnsContainer = $('<div class="confirmation-btns"></div>');
|
|
$(
|
|
'<button class="btn-flat timepicker-close waves-effect" type="button" tabindex="' +
|
|
(this.options.twelveHour ? '3' : '1') +
|
|
'">' +
|
|
this.options.i18n.cancel +
|
|
'</button>'
|
|
)
|
|
.appendTo(confirmationBtnsContainer)
|
|
.on('click', this.close.bind(this));
|
|
$(
|
|
'<button class="btn-flat timepicker-close waves-effect" type="button" tabindex="' +
|
|
(this.options.twelveHour ? '3' : '1') +
|
|
'">' +
|
|
this.options.i18n.done +
|
|
'</button>'
|
|
)
|
|
.appendTo(confirmationBtnsContainer)
|
|
.on('click', this.done.bind(this));
|
|
confirmationBtnsContainer.appendTo(this.footer);
|
|
}
|
|
|
|
_clockSetup() {
|
|
if (this.options.twelveHour) {
|
|
this.$amBtn = $('<div class="am-btn">AM</div>');
|
|
this.$pmBtn = $('<div class="pm-btn">PM</div>');
|
|
this.$amBtn.on('click', this._handleAmPmClick.bind(this)).appendTo(this.spanAmPm);
|
|
this.$pmBtn.on('click', this._handleAmPmClick.bind(this)).appendTo(this.spanAmPm);
|
|
}
|
|
|
|
this._buildHoursView();
|
|
this._buildMinutesView();
|
|
this._buildSVGClock();
|
|
}
|
|
|
|
_buildSVGClock() {
|
|
// Draw clock hands and others
|
|
let dialRadius = this.options.dialRadius;
|
|
let tickRadius = this.options.tickRadius;
|
|
let diameter = dialRadius * 2;
|
|
|
|
let svg = Timepicker._createSVGEl('svg');
|
|
svg.setAttribute('class', 'timepicker-svg');
|
|
svg.setAttribute('width', diameter);
|
|
svg.setAttribute('height', diameter);
|
|
let g = Timepicker._createSVGEl('g');
|
|
g.setAttribute('transform', 'translate(' + dialRadius + ',' + dialRadius + ')');
|
|
let bearing = Timepicker._createSVGEl('circle');
|
|
bearing.setAttribute('class', 'timepicker-canvas-bearing');
|
|
bearing.setAttribute('cx', 0);
|
|
bearing.setAttribute('cy', 0);
|
|
bearing.setAttribute('r', 4);
|
|
let hand = Timepicker._createSVGEl('line');
|
|
hand.setAttribute('x1', 0);
|
|
hand.setAttribute('y1', 0);
|
|
let bg = Timepicker._createSVGEl('circle');
|
|
bg.setAttribute('class', 'timepicker-canvas-bg');
|
|
bg.setAttribute('r', tickRadius);
|
|
g.appendChild(hand);
|
|
g.appendChild(bg);
|
|
g.appendChild(bearing);
|
|
svg.appendChild(g);
|
|
this._canvas.appendChild(svg);
|
|
|
|
this.hand = hand;
|
|
this.bg = bg;
|
|
this.bearing = bearing;
|
|
this.g = g;
|
|
}
|
|
|
|
_buildHoursView() {
|
|
let $tick = $('<div class="timepicker-tick"></div>');
|
|
// Hours view
|
|
if (this.options.twelveHour) {
|
|
for (let i = 1; i < 13; i += 1) {
|
|
let tick = $tick.clone();
|
|
let radian = i / 6 * Math.PI;
|
|
let radius = this.options.outerRadius;
|
|
tick.css({
|
|
left:
|
|
this.options.dialRadius + Math.sin(radian) * radius - this.options.tickRadius + 'px',
|
|
top:
|
|
this.options.dialRadius - Math.cos(radian) * radius - this.options.tickRadius + 'px'
|
|
});
|
|
tick.html(i === 0 ? '00' : i);
|
|
this.hoursView.appendChild(tick[0]);
|
|
// tick.on(mousedownEvent, mousedown);
|
|
}
|
|
} else {
|
|
for (let i = 0; i < 24; i += 1) {
|
|
let tick = $tick.clone();
|
|
let radian = i / 6 * Math.PI;
|
|
let inner = i > 0 && i < 13;
|
|
let radius = inner ? this.options.innerRadius : this.options.outerRadius;
|
|
tick.css({
|
|
left:
|
|
this.options.dialRadius + Math.sin(radian) * radius - this.options.tickRadius + 'px',
|
|
top:
|
|
this.options.dialRadius - Math.cos(radian) * radius - this.options.tickRadius + 'px'
|
|
});
|
|
tick.html(i === 0 ? '00' : i);
|
|
this.hoursView.appendChild(tick[0]);
|
|
// tick.on(mousedownEvent, mousedown);
|
|
}
|
|
}
|
|
}
|
|
|
|
_buildMinutesView() {
|
|
let $tick = $('<div class="timepicker-tick"></div>');
|
|
// Minutes view
|
|
for (let i = 0; i < 60; i += 5) {
|
|
let tick = $tick.clone();
|
|
let radian = i / 30 * Math.PI;
|
|
tick.css({
|
|
left:
|
|
this.options.dialRadius +
|
|
Math.sin(radian) * this.options.outerRadius -
|
|
this.options.tickRadius +
|
|
'px',
|
|
top:
|
|
this.options.dialRadius -
|
|
Math.cos(radian) * this.options.outerRadius -
|
|
this.options.tickRadius +
|
|
'px'
|
|
});
|
|
tick.html(Timepicker._addLeadingZero(i));
|
|
this.minutesView.appendChild(tick[0]);
|
|
}
|
|
}
|
|
|
|
_handleAmPmClick(e) {
|
|
let $btnClicked = $(e.target);
|
|
this.amOrPm = $btnClicked.hasClass('am-btn') ? 'AM' : 'PM';
|
|
this._updateAmPmView();
|
|
}
|
|
|
|
_updateAmPmView() {
|
|
if (this.options.twelveHour) {
|
|
this.$amBtn.toggleClass('text-primary', this.amOrPm === 'AM');
|
|
this.$pmBtn.toggleClass('text-primary', this.amOrPm === 'PM');
|
|
}
|
|
}
|
|
|
|
_updateTimeFromInput() {
|
|
// Get the time
|
|
let value = ((this.el.value || this.options.defaultTime || '') + '').split(':');
|
|
if (this.options.twelveHour && !(typeof value[1] === 'undefined')) {
|
|
if (value[1].toUpperCase().indexOf('AM') > 0) {
|
|
this.amOrPm = 'AM';
|
|
} else {
|
|
this.amOrPm = 'PM';
|
|
}
|
|
value[1] = value[1].replace('AM', '').replace('PM', '');
|
|
}
|
|
if (value[0] === 'now') {
|
|
let now = new Date(+new Date() + this.options.fromNow);
|
|
value = [now.getHours(), now.getMinutes()];
|
|
if (this.options.twelveHour) {
|
|
this.amOrPm = value[0] >= 12 && value[0] < 24 ? 'PM' : 'AM';
|
|
}
|
|
}
|
|
this.hours = +value[0] || 0;
|
|
this.minutes = +value[1] || 0;
|
|
this.spanHours.innerHTML = this.hours;
|
|
this.spanMinutes.innerHTML = Timepicker._addLeadingZero(this.minutes);
|
|
|
|
this._updateAmPmView();
|
|
}
|
|
|
|
showView(view, delay) {
|
|
if (view === 'minutes' && $(this.hoursView).css('visibility') === 'visible') {
|
|
// raiseCallback(this.options.beforeHourSelect);
|
|
}
|
|
let 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);
|
|
|
|
// Transition view
|
|
hideView.classList.add('timepicker-dial-out');
|
|
$(nextView)
|
|
.css('visibility', 'visible')
|
|
.removeClass('timepicker-dial-out');
|
|
|
|
// Reset clock hand
|
|
this.resetClock(delay);
|
|
|
|
// After transitions ended
|
|
clearTimeout(this.toggleViewTimer);
|
|
this.toggleViewTimer = setTimeout(() => {
|
|
$(hideView).css('visibility', 'hidden');
|
|
}, this.options.duration);
|
|
}
|
|
|
|
resetClock(delay) {
|
|
let view = this.currentView,
|
|
value = this[view],
|
|
isHours = view === 'hours',
|
|
unit = Math.PI / (isHours ? 6 : 30),
|
|
radian = value * unit,
|
|
radius =
|
|
isHours && value > 0 && value < 13 ? this.options.innerRadius : this.options.outerRadius,
|
|
x = Math.sin(radian) * radius,
|
|
y = -Math.cos(radian) * radius,
|
|
self = this;
|
|
|
|
if (delay) {
|
|
$(this.canvas).addClass('timepicker-canvas-out');
|
|
setTimeout(() => {
|
|
$(self.canvas).removeClass('timepicker-canvas-out');
|
|
self.setHand(x, y);
|
|
}, delay);
|
|
} else {
|
|
this.setHand(x, y);
|
|
}
|
|
}
|
|
|
|
setHand(x, y, roundBy5) {
|
|
let radian = Math.atan2(x, -y),
|
|
isHours = this.currentView === 'hours',
|
|
unit = Math.PI / (isHours || roundBy5 ? 6 : 30),
|
|
z = Math.sqrt(x * x + y * y),
|
|
inner = isHours && z < (this.options.outerRadius + this.options.innerRadius) / 2,
|
|
radius = inner ? this.options.innerRadius : this.options.outerRadius;
|
|
|
|
if (this.options.twelveHour) {
|
|
radius = this.options.outerRadius;
|
|
}
|
|
|
|
// Radian should in range [0, 2PI]
|
|
if (radian < 0) {
|
|
radian = Math.PI * 2 + radian;
|
|
}
|
|
|
|
// Get the round value
|
|
let value = Math.round(radian / unit);
|
|
|
|
// Get the round radian
|
|
radian = value * unit;
|
|
|
|
// Correct the hours or minutes
|
|
if (this.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 (this.vibrate && this.options.vibrate) {
|
|
// Do not vibrate too frequently
|
|
if (!this.vibrateTimer) {
|
|
navigator[this.vibrate](10);
|
|
this.vibrateTimer = setTimeout(() => {
|
|
this.vibrateTimer = null;
|
|
}, 100);
|
|
}
|
|
}
|
|
}
|
|
|
|
this[this.currentView] = value;
|
|
if (isHours) {
|
|
this['spanHours'].innerHTML = value;
|
|
} else {
|
|
this['spanMinutes'].innerHTML = Timepicker._addLeadingZero(value);
|
|
}
|
|
|
|
// Set clock hand and others' position
|
|
let cx1 = Math.sin(radian) * (radius - this.options.tickRadius),
|
|
cy1 = -Math.cos(radian) * (radius - this.options.tickRadius),
|
|
cx2 = Math.sin(radian) * radius,
|
|
cy2 = -Math.cos(radian) * radius;
|
|
this.hand.setAttribute('x2', cx1);
|
|
this.hand.setAttribute('y2', cy1);
|
|
this.bg.setAttribute('cx', cx2);
|
|
this.bg.setAttribute('cy', cy2);
|
|
}
|
|
|
|
open() {
|
|
if (this.isOpen) {
|
|
return;
|
|
}
|
|
|
|
this.isOpen = true;
|
|
this._updateTimeFromInput();
|
|
this.showView('hours');
|
|
|
|
this.modal.open();
|
|
}
|
|
|
|
close() {
|
|
if (!this.isOpen) {
|
|
return;
|
|
}
|
|
|
|
this.isOpen = false;
|
|
this.modal.close();
|
|
}
|
|
|
|
/**
|
|
* Finish timepicker selection.
|
|
*/
|
|
done(e, clearValue) {
|
|
// Set input value
|
|
let last = this.el.value;
|
|
let value = clearValue
|
|
? ''
|
|
: Timepicker._addLeadingZero(this.hours) + ':' + Timepicker._addLeadingZero(this.minutes);
|
|
this.time = value;
|
|
if (!clearValue && this.options.twelveHour) {
|
|
value = `${value} ${this.amOrPm}`;
|
|
}
|
|
this.el.value = value;
|
|
|
|
// Trigger change event
|
|
if (value !== last) {
|
|
this.$el.trigger('change');
|
|
}
|
|
|
|
this.close();
|
|
this.el.focus();
|
|
}
|
|
|
|
clear() {
|
|
this.done(null, true);
|
|
}
|
|
}
|
|
|
|
Timepicker._template = [
|
|
'<div class= "modal timepicker-modal">',
|
|
'<div class="modal-content timepicker-container">',
|
|
'<div class="timepicker-digital-display">',
|
|
'<div class="timepicker-text-container">',
|
|
'<div class="timepicker-display-column">',
|
|
'<span class="timepicker-span-hours text-primary"></span>',
|
|
':',
|
|
'<span class="timepicker-span-minutes"></span>',
|
|
'</div>',
|
|
'<div class="timepicker-display-column timepicker-display-am-pm">',
|
|
'<div class="timepicker-span-am-pm"></div>',
|
|
'</div>',
|
|
'</div>',
|
|
'</div>',
|
|
'<div class="timepicker-analog-display">',
|
|
'<div class="timepicker-plate">',
|
|
'<div class="timepicker-canvas"></div>',
|
|
'<div class="timepicker-dial timepicker-hours"></div>',
|
|
'<div class="timepicker-dial timepicker-minutes timepicker-dial-out"></div>',
|
|
'</div>',
|
|
'<div class="timepicker-footer"></div>',
|
|
'</div>',
|
|
'</div>',
|
|
'</div>'
|
|
].join('');
|
|
|
|
M.Timepicker = Timepicker;
|
|
|
|
if (M.jQueryLoaded) {
|
|
M.initializeJqueryWrapper(Timepicker, 'timepicker', 'M_Timepicker');
|
|
}
|
|
})(cash);
|