commit 12/02/2026

This commit is contained in:
2026-02-12 17:08:20 +07:00
parent 546f2e27af
commit c797c6e7fe
47 changed files with 89103 additions and 214 deletions

View File

@@ -0,0 +1,978 @@
/*!
* ClockPicker v0.2.3 original by (http://weareoutman.github.io/clockpicker/)
* Copyright 2014 Wang Shenwei.
* Licensed under MIT (https://github.com/weareoutman/clockpicker/blob/gh-pages/LICENSE)
* Bootstrap 4 support by djibe
*/
(function($) {
var $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-header">',
'<span class="clockpicker-span-hours"></span>',
":",
'<span class="clockpicker-span-minutes text-white-50"></span>',
'<span class="clockpicker-buttons-am-pm"></span>',
"</div>",
'<div class="popover-body">',
'<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-close-block justify-content-end"></div>',
"</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"),
isInput = element.prop("tagName") === "INPUT",
input = isInput ? element : element.find("input"),
isHTML5 = input.prop("type") === "time",
addon = element.find(".input-group-addon"),
popoverBody = popover.find(".popover-body"),
closeBlock = popoverBody.find(".clockpicker-close-block"),
self = this,
timer;
this.id = uniqueId("cp");
this.element = element;
this.options = options;
this.options.hourstep = this.parseStep(this.options.hourstep, 12);
this.options.minutestep = this.parseStep(this.options.minutestep, 60);
this.isAppended = false;
this.isShown = false;
this.currentView = "hours";
this.isInput = isInput;
this.isHTML5 = isHTML5;
this.input = input;
this.addon = addon;
this.popover = popover;
this.plate = plate;
this.hoursView = hoursView;
this.minutesView = minutesView;
this.spanHours = popover.find(".clockpicker-span-hours");
this.spanMinutes = popover.find(".clockpicker-span-minutes");
this.buttonsAmPm = popover.find(".clockpicker-buttons-am-pm");
this.currentPlacementClass = options.placement;
this.raiseCallback = function() {
raiseCallback.apply(self, arguments);
};
// Setup for for 12 hour clock if option is selected
if (options.twelvehour) {
$(this.buttonsAmPm).css("display", "flex");
$('<a class="btn-am">AM</a>')
.on("click", function() {
self.amOrPm = "AM";
$(this).removeClass("text-white-50");
$(".btn-pm").addClass("text-white-50");
if (options.ampmSubmit) {
setTimeout(function() {
self.done();
}, duration / 2);
}
})
.appendTo(this.buttonsAmPm);
$('<a class="btn-pm text-white-50">PM</a>')
.on("click", function() {
self.amOrPm = "PM";
$(this).removeClass("text-white-50");
$(".btn-am").addClass("text-white-50");
if (options.ampmSubmit) {
setTimeout(function() {
self.done();
}, duration / 2);
}
})
.appendTo(this.buttonsAmPm);
}
if (!options.autoclose) {
// If autoclose is not setted, append a button
closeBlock
.append(
'<button type="button" class="btn btn-sm btn-outline-primary cancel">' +
options.canceltext +
"</button>"
)
.on("click", ".cancel", function () {
self.hide();
});
closeBlock
.css("display", "flex")
.append(
'<button type="button" class="btn btn-sm btn-outline-primary done">' +
options.donetext +
"</button>"
)
.on("click", ".done", $.proxy(this.done, this));
}
// Placement and arrow align - make sure they make sense.
if (
/^(top|bottom)/.test(options.placement) &&
(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
if (!options.addonOnly) {
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 = 0; i < 12; i += options.hourstep) {
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 ? 12 : i);
hoursView.append(tick);
tick.on(mousedownEvent, mousedown);
}
} else {
for (i = 0; i < 24; i += options.hourstep) {
var isDisabled = false;
if (
options.disabledhours &&
$.inArray(i, options.disabledhours) != -1
) {
var isDisabled = true;
}
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%");
}
if (isDisabled) {
tick.addClass("disabled");
}
tick.html(i === 0 ? "00" : i);
hoursView.append(tick);
if (!isDisabled) {
tick.on(mousedownEvent, mousedown);
}
}
}
// Minutes view
var incrementValue = Math.max(options.minutestep, 5);
for (i = 0; i < 60; i += incrementValue) {
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, 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, 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) {
if (!options.ampmSubmit) {
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", 3);
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;
}
this.raiseCallback(this.options.init, "init");
}
function raiseCallback(callbackFunction, triggerName) {
if (
callbackFunction &&
typeof callbackFunction === "function" &&
this.element
) {
var time = this.getTime() || null;
callbackFunction.call(this.element, time);
}
if (triggerName) {
this.element.trigger("clockpicker." + triggerName || "NoName");
}
}
/**
* Find most suitable vertical placement, doing our best to ensure it is inside of the viewport.
*
* First try to place the element according with preferredPlacement, then try the opposite
* placement and as a last resort, popover will be placed on the very top of the viewport.
*
* @param {jQuery} element
* @param {jQuery} popover
* @param preferredPlacement Preferred placement, if there is enough room for it.
* @returns {string} One of: 'top', 'bottom' or 'viewport-top'.
*/
function resolveAdaptiveVerticalPlacement(
element,
popover,
preferredPlacement
) {
var popoverHeight = popover.outerHeight(),
elementHeight = element.outerHeight(),
elementTopOffset = element.offset().top,
elementBottomOffset = element.offset().top + elementHeight,
minVisibleY = elementTopOffset - element[0].getBoundingClientRect().top,
maxVisibleY = minVisibleY + document.documentElement.clientHeight,
isEnoughRoomAbove = elementTopOffset - popoverHeight >= minVisibleY,
isEnoughRoomBelow = elementBottomOffset + popoverHeight <= maxVisibleY;
if (preferredPlacement === "top") {
if (isEnoughRoomAbove) {
return "top";
} else if (isEnoughRoomBelow) {
return "bottom";
}
} else {
if (isEnoughRoomBelow) {
return "bottom";
} else if (isEnoughRoomAbove) {
return "top";
}
}
return "viewport-top";
}
ClockPicker.prototype.parseStep = function(givenStepSize, wholeSize) {
return wholeSize % givenStepSize === 0 ? givenStepSize : 1;
};
// 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: "OK", // done button text
canceltext: "Cancel", // cancel 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
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
addonOnly: false, // only open on clicking on the input-addon
disabledhours: null // disabled hours (only 24 hour mode)
};
// Show or hide popover
ClockPicker.prototype.toggle = function() {
this[this.isShown ? "hide" : "show"]();
};
// Set new placement class for popover and remove the old one, if any.
ClockPicker.prototype.updatePlacementClass = function(newClass) {
if (this.currentPlacementClass) {
this.popover.removeClass(this.currentPlacementClass);
}
if (newClass) {
this.popover.addClass(newClass);
}
this.currentPlacementClass = newClass;
};
// Set popover position and update placement class, if needed
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,
windowHeight = $win.height(),
windowWidth = $win.width(),
popoverHeight = popover.height(),
popoverWidth = popover.width(),
styles = {},
self = this;
if (placement === "top-adaptive" || placement === "bottom-adaptive") {
var preferredPlacement = placement.substr(0, placement.indexOf("-"));
// Adaptive placement should be resolved into one of the "static" placement
// options, that is best suitable for the current window scroll position.
placement = resolveAdaptiveVerticalPlacement(
element,
popover,
preferredPlacement
);
this.updatePlacementClass(placement !== "viewport-top" ? placement : "");
}
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;
case "viewport-top":
styles.top = offset.top - element[0].getBoundingClientRect().top;
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;
}
// Correct the popover position outside the window
if (popoverHeight + styles.top > windowHeight) {
styles.top = windowHeight - popoverHeight;
}
if (popoverWidth + styles.left > windowWidth) {
styles.left = windowWidth - popoverWidth;
}
popover.css(styles);
};
// 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";
this.amOrPm = this.hours < 12 || period === "am" ? "AM" : "PM";
}
};
// Show popover
ClockPicker.prototype.show = function(e) {
// Not show again
if (this.isShown) {
return;
}
this.raiseCallback(this.options.beforeShow, "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 from the input field
this.parseInputValue();
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();
}
});
this.raiseCallback(this.options.afterShow, "afterShow");
};
// Hide popover
ClockPicker.prototype.hide = function() {
this.raiseCallback(this.options.beforeHide, "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();
this.raiseCallback(this.options.afterHide, "afterHide");
};
// Toggle to hours or minutes view
ClockPicker.prototype.toggleView = function(view, delay) {
var raiseAfterHourSelect = false;
if (
view === "minutes" &&
$(this.hoursView).css("visibility") === "visible"
) {
this.raiseCallback(this.options.beforeHourSelect, "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-white-50", !isHours);
this.spanMinutes.toggleClass("text-white-50", 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) {
this.raiseCallback(this.options.afterHourSelect, "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, 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;
}
if (
dragging &&
!options.twelvehour &&
options.disabledhours &&
$.inArray(value, options.disabledhours) != -1
) {
return;
}
} else {
value *= options.minutestep;
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);
};
// Allow user to get time time as Date object
ClockPicker.prototype.getTime = function(callback) {
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() {
this.raiseCallback(this.options.beforeDone, "beforeDone");
this.hide();
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");
}
this.raiseCallback(this.options.afterDone, "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);
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, getTime, e.g.
if (typeof data[option] === "function") {
return 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);
};
})(jQuery);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,729 @@
/*!
* 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)
*/
;(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);
}
}
});
};
}());

View File

@@ -139,6 +139,8 @@ function reloadTodaySchedule(APIURL = "ScheduleBank/") {
dayViewMode = 'all'; // all, everyday, monday, tuesday, wednesday, thursday, friday, saturday, sunday
scheduledate = null; // Litepicker instance for schedule date selection
scheduletime = null; // time picker instance for schedule time selection
$schedulemodal = null; // schedule modal jQuery object
$(document).ready(function () {
console.log("schedulebank.js loaded successfully");
@@ -153,6 +155,8 @@ $(document).ready(function () {
$btnEdit.prop('disabled', true);
$btnRemove.prop('disabled', true);
let APIURL = "ScheduleBank/";
if (dtScheduleBank === null) {
dtScheduleBank = new DataTable('#schedulebanktable', {
@@ -239,15 +243,24 @@ $(document).ready(function () {
})
let $schedulemodal = $('#schedulemodal');
$schedulemodal = $('#schedulemodal');
// text input
let $scheduleid = $schedulemodal.find('#scheduleid');
// text input
let $scheduledescription = $schedulemodal.find('#scheduledescription');
// number input 0-23
let $schedulehour = $schedulemodal.find('#schedulehour');
//let $schedulehour = $schedulemodal.find('#schedulehour');
// number input 0-59
let $scheduleminute = $schedulemodal.find('#scheduleminute');
//let $scheduleminute = $schedulemodal.find('#scheduleminute');
scheduletime = flatpickr("#scheduletime",{
enableTime: true,
noCalendar: true, // time only
dateFormat: "H:i", // HH:mm format
time_24hr: true, // firce 24-hour format
minuteIncrement: 1,
defaultDate: new Date()
});
// select2 for message
let $schedulemessage = $schedulemodal.find('#schedulemessage');
// number input 0-5
@@ -264,6 +277,7 @@ $(document).ready(function () {
let $weeklyselect = $schedulemodal.find('#weeklyselect');
// radio button for specific date
let $schedulespecialdate = $schedulemodal.find('#schedulespecialdate');
scheduledate = new Litepicker({
element: document.getElementById('scheduledate'),
@@ -276,6 +290,8 @@ $(document).ready(function () {
console.log("Selected special date: " + date.format('DD/MM/YYYY'));
}
})
// date input
//let $scheduledate = $schedulemodal.find('#scheduledate');
// select2 for language
@@ -294,8 +310,8 @@ $(document).ready(function () {
function clearScheduleModal() {
$scheduleid.prop('disabled', true).val('');
$scheduledescription.val('');
$schedulehour.val('0');
$scheduleminute.val('0');
//$schedulehour.val('0');
//$scheduleminute.val('0');
$schedulerepeat.val('1');
$scheduleenable.prop('checked', true);
$scheduleeveryday.prop('checked', false);
@@ -403,9 +419,9 @@ $(document).ready(function () {
const Language = $languageselect.val().join(';');
const broadcastZones = $schedulezones.val().join(';');
// Format time as HH:mm
const hour = parseInt($schedulehour.val(), 10);
const minute = parseInt($scheduleminute.val(), 10);
const _Time = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
//const hour = parseInt($schedulehour.val(), 10);
//const minute = parseInt($scheduleminute.val(), 10);
const _Time = $('#scheduletime').val();
if (Description.length > 0) {
if (_Day.length > 0) {
if (Message.length > 0) {
@@ -488,8 +504,9 @@ $(document).ready(function () {
$scheduleid.val(sr.index);
$scheduledescription.val(sr.Description);
let [hour, minute] = sr.Time.split(':').map(num => parseInt(num, 10));
$schedulehour.val(hour.toString());
$scheduleminute.val(minute.toString());
//$schedulehour.val(hour.toString());
//$scheduleminute.val(minute.toString());
scheduletime.setDate(sr.Time,true, "H:i");
$schedulemessage.val(sr.Soundpath).trigger('change');
$schedulerepeat.val(sr.Repeat);
$scheduleenable.prop('checked', sr.Enable);
@@ -562,9 +579,9 @@ $(document).ready(function () {
const BroadcastZones = $schedulezones.val().join(';');
const Language = $languageselect.val().join(';');
// Format time as HH:mm
const hour = parseInt($schedulehour.val(), 10);
const minute = parseInt($scheduleminute.val(), 10);
const Time = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
// const hour = parseInt($schedulehour.val(), 10);
//const minute = parseInt($scheduleminute.val(), 10);
const Time = $('#scheduletime').val();
if (Description && Description.length > 0) {
if (Day && Day.length > 0) {
if (Soundpath && Soundpath.length > 0) {

View File

@@ -67,6 +67,275 @@ function load_default_voice(){
});
}
function ValidLatitude(lat){
const num = parseFloat(lat);
return !isNaN(num) && num >= -90 && num <= 90;
}
function ValidLongitude(lon){
const num = parseFloat(lon);
return !isNaN(num) && num >= -180 && num <= 180;
}
function ValidTimezone(tz){
try{
Intl.DateTimeFormat(undefined, { timeZone: tz });
return true;
} catch(e){
return false;
}
}
function ValidHHMM(time){
const regex = /^([01]\d|2[0-3]):([0-5]\d)$/;
return regex.test(time);
}
/**
* Check if a date string is valid in DD/MM/YYYY format
* @param {string} dateStr date to check in DD/MM/YYYY format
* @returns {boolean} true if valid date in DD/MM/YYYY format, false otherwise
*/
function ValidDateDDMMYYYY(dateStr){
const regex = /^(0[1-9]|[12][0-9]|3[01])\/(0[1-9]|1[0-2])\/\d{4}$/;
if (!regex.test(dateStr)) return false;
const [day, month, year] = dateStr.split('/').map(Number);
const date = new Date(year, month - 1, day);
return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day;
}
function ValidFilePath(path){
if (typeof path !== 'string' || path.trim() === '') return false;
// test if ends with .wav or .mp3
const regex = /\.(wav|mp3)$/i;
return regex.test(path);
}
function IsEnabled(value){
// accept "true", "false", true, false, 1, 0
// detect if value is null or undefined
if (value === null || value === undefined) return false;
if (typeof value === 'boolean') return value;
if (typeof value === 'number') return value === 1;
if (typeof value === 'string') return value.toLowerCase() === "true" || value === "1";
return false;
}
/**
* @typedef {Object} AdzanSetting
* @property {string} latitude
* @property {string} longitude
* @property {string} timezone
* @property {string} fajar_sound
* @property {string} dzuhur_sound
* @property {string} ashar_sound
* @property {string} maghrib_sound
* @property {string} isya_sound
* @property {string} fajar_time
* @property {string} dzuhur_time
* @property {string} ashar_time
* @property {string} maghrib_time
* @property {string} isya_time
* @property {boolean} fajar_enable
* @property {boolean} dzuhur_enable
* @property {boolean} ashar_enable
* @property {boolean} maghrib_enable
* @property {boolean} isya_enable
*/
function Get_AdzanSetting(){
fetchAPI("Settings/AdzanSetting", "GET", {}, null,
/**
* returned AdzanSetting data
* @param {AdzanSetting} okdata
*/
(okdata) => {
// text input for latitude, longitude, timezone
if (ValidLatitude(okdata.latitude)) {
$('#adzanlatitude').val(okdata.latitude);
} else {
$('#adzanlatitude').val("N/A");
}
if (ValidLongitude(okdata.longitude)) {
$('#adzanlongitude').val(okdata.longitude);
} else {
$('#adzanlongitude').val("N/A");
}
if (ValidTimezone(okdata.timezone)) {
$('#adzantimezone').val(okdata.timezone);
} else {
$('#adzantimezone').val("N/A");
}
// text input for adzan sound file for each prayer time
if (ValidFilePath(okdata.fajar_sound)) {
$('#fajar .adzanfile').val(okdata.fajar_sound);
} else {
$('#fajar .adzanfile').val("N/A");
}
if (ValidFilePath(okdata.dzuhur_sound)) {
$('#dzuhur .adzanfile').val(okdata.dzuhur_sound);
} else {
$('#dzuhur .adzanfile').val("N/A");
}
if (ValidFilePath(okdata.ashar_sound)) {
$('#ashar .adzanfile').val(okdata.ashar_sound);
} else {
$('#ashar .adzanfile').val("N/A");
}
if (ValidFilePath(okdata.maghrib_sound)) {
$('#maghrib .adzanfile').val(okdata.maghrib_sound);
} else {
$('#maghrib .adzanfile').val("N/A");
}
if (ValidFilePath(okdata.isya_sound)) {
$('#isya .adzanfile').val(okdata.isya_sound);
} else {
$('#isya .adzanfile').val("N/A");
}
// checkbox adzanenable for each prayer time
$('#fajar .adzanenable').prop('checked', IsEnabled(okdata.fajar_enable));
$('#dzuhur .adzanenable').prop('checked', IsEnabled(okdata.dzuhur_enable));
$('#ashar .adzanenable').prop('checked', IsEnabled(okdata.ashar_enable));
$('#maghrib .adzanenable').prop('checked', IsEnabled(okdata.maghrib_enable));
$('#isya .adzanenable').prop('checked', IsEnabled(okdata.isya_enable));
// adzantime for each prayer time
// if valid HH:MM will set <input type="time"> value, else set to undefined
if (ValidHHMM(okdata.fajar_time)) {
$('#fajar .adzantime').val(okdata.fajar_time);
} else {
$('#fajar .adzantime').val("");
}
if (ValidHHMM(okdata.dzuhur_time)) {
$('#dzuhur .adzantime').val(okdata.dzuhur_time);
} else {
$('#dzuhur .adzantime').val("");
}
if (ValidHHMM(okdata.ashar_time)) {
$('#ashar .adzantime').val(okdata.ashar_time);
} else {
$('#ashar .adzantime').val("");
}
if (ValidHHMM(okdata.maghrib_time)) {
$('#maghrib .adzantime').val(okdata.maghrib_time);
} else {
$('#maghrib .adzantime').val("");
}
if (ValidHHMM(okdata.isya_time)) {
$('#isya .adzantime').val(okdata.isya_time);
} else {
$('#isya .adzantime').val("");
}
}, (errdata) => {
alert("Error getting Adzan settings : " + errdata.message);
});
}
function Set_AdzanSetting(){
let latitude = $('#adzanlatitude').val();
if (!ValidLatitude(latitude)){
alert("Please enter a valid latitude between -90 and 90.");
return;
}
let longitude = $('#adzanlongitude').val();
if (!ValidLongitude(longitude)){
alert("Please enter a valid longitude between -180 and 180.");
return;
}
let timezone = $('#adzantimezone').val();
if (!ValidTimezone(timezone)){
alert("Please enter a valid timezone.");
return;
}
let fajar_sound = $('#fajar .adzanfile').val();
if (!ValidFilePath(fajar_sound)){
alert("Please enter a valid file path for Fajar adzan sound (must end with .wav or .mp3).");
return;
}
let dzuhur_sound = $('#dzuhur .adzanfile').val();
if (!ValidFilePath(dzuhur_sound)){
alert("Please enter a valid file path for Dzuhur adzan sound (must end with .wav or .mp3).");
return;
}
let ashar_sound = $('#ashar .adzanfile').val();
if (!ValidFilePath(ashar_sound)){
alert("Please enter a valid file path for Ashar adzan sound (must end with .wav or .mp3).");
return;
}
let maghrib_sound = $('#maghrib .adzanfile').val();
if (!ValidFilePath(maghrib_sound)){
alert("Please enter a valid file path for Maghrib adzan sound (must end with .wav or .mp3).");
return;
}
let isya_sound = $('#isya .adzanfile').val();
if (!ValidFilePath(isya_sound)){
alert("Please enter a valid file path for Isya adzan sound (must end with .wav or .mp3).");
return;
}
let fajar_enable = $('#fajar .adzanenable').prop('checked');
let dzuhur_enable = $('#dzuhur .adzanenable').prop('checked');
let ashar_enable = $('#ashar .adzanenable').prop('checked');
let maghrib_enable = $('#maghrib .adzanenable').prop('checked');
let isya_enable = $('#isya .adzanenable').prop('checked');
let fajar_time = $('#fajar .adzantime').val();
if (!ValidHHMM(fajar_time)){
alert("Please enter a valid time for Fajar adzan (HH:MM).");
return;
}
let dzuhur_time = $('#dzuhur .adzantime').val();
if (!ValidHHMM(dzuhur_time)){
alert("Please enter a valid time for Dzuhur adzan (HH:MM).");
return;
}
let ashar_time = $('#ashar .adzantime').val();
if (!ValidHHMM(ashar_time)){
alert("Please enter a valid time for Ashar adzan (HH:MM).");
return;
}
let maghrib_time = $('#maghrib .adzantime').val();
if (!ValidHHMM(maghrib_time)){
alert("Please enter a valid time for Maghrib adzan (HH:MM).");
return;
}
let isya_time = $('#isya .adzantime').val();
if (!ValidHHMM(isya_time)){
alert("Please enter a valid time for Isya adzan (HH:MM).");
return;
}
/**
* @type {AdzanSetting}
*/
let data = {
latitude: latitude,
longitude: longitude,
timezone: timezone,
fajar_sound: fajar_sound,
dzuhur_sound: dzuhur_sound,
ashar_sound: ashar_sound,
maghrib_sound: maghrib_sound,
isya_sound: isya_sound,
fajar_enable: fajar_enable,
dzuhur_enable: dzuhur_enable,
ashar_enable: ashar_enable,
maghrib_enable: maghrib_enable,
isya_enable: isya_enable,
fajar_time: fajar_time,
dzuhur_time: dzuhur_time,
ashar_time: ashar_time,
maghrib_time: maghrib_time,
isya_time: isya_time
};
fetchAPI("Settings/AdzanSetting", "POST", {}, data, (okdata) => {
alert("Adzan settings updated successfully.");
}, (errdata) => {
alert("Error updating Adzan settings : " + errdata.message);
});
}
function Get_WebAccessSetting(){
fetchAPI("Settings/WebAccess", "GET", {}, null, (okdata) => {
let adminpass = okdata.adminpass || "password";
@@ -109,8 +378,10 @@ $(document).ready(function () {
load_default_voice();
Get_OldResultDays();
Get_WebAccessSetting();
Get_AdzanSetting();
load_messagebank(() => load_remark_selection());
$("#fiscodesave").off('click').on('click', function () {
$('#fiscodesave').click(function () {
Set_OldResultDays();
let gop = $("#input_GOP").val();
let gbd = $("#input_GBD").val();
@@ -133,10 +404,13 @@ $(document).ready(function () {
} else {
alert("Please select all FIS codes (GOP, GBD, GFC, FLD) and Default Voice before saving.");
}
});
$("#webaccesssave").off('click').on('click', function () {
Set_WebAccessSetting();
$('#webaccesssave').click(function () {
Set_WebAccessSetting();
});
$('#adzansave').click(function () {
Set_AdzanSetting();
});