Files
Facilitor/APPL/PDA/mobile.js
2025-08-05 12:55:34 +00:00

495 lines
19 KiB
JavaScript

/*
$Revision$
$Id$
File: mobile.js
Description: clientside functions for pda
*/
window.fcltmobile = 1;
if (window && window.localStorage && window.localStorage.getItem("interface") == "touch") {
toTouch();
}
$(function() {
// Alle niet :target's worden default onderdrukt, als er geen :target is; toon dan de eerste pagina
window.addEventListener("hashchange", function() {
var hash = location.hash.split("#")[1];
if (hash) {
$(".page.show").removeClass("show");
var $targetPage = $(".page#" + hash);
if ($targetPage.length) {
$targetPage.addClass("show");
} else {
// Als er geen pagina is, dan terug naar de eerste pagina
$(".page:first").addClass("show");
}
} else {
// Als er geen hash is, dan terug naar de eerste pagina
$(".page.show").removeClass("show");
$(".page:first").addClass("show");
}
});
if (!location.href.split("#")[1]) {
$(".page:first").addClass("show");
}
// Set initial theme
toggleTheme();
/* theme change eventHandler */
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", toggleTheme);
/* Footer eventHandlers */
$(".pda-button-more").off("click").on("click", function() {
$(this).toggleClass("open");
});
$(document)
.off("touchstart touchend touchcancel")
.on("touchstart", function(e) {
var $target = $(e.target).closest(".tappable");
if (!$target.hasClass("tappable")) {
return;
}
if ($target.find(".tappable-target").length) {
$target = $target.find(".tappable-target");
}
$target.addClass("fclt-active");
$(document).one("scrollstart", () => {
$(".tappable.fclt-active .tappable-target.fclt-active").removeClass("fclt-active");
});
}).on("touchend touchcancel", function(e) {
var $target = $(e.target).closest(".tappable");
if (!$target.hasClass("tappable")) {
return;
}
if ($target.find(".tappable-target").length) {
$target = $target.find(".tappable-target");
}
$target.removeClass("fclt-active");
$(document).off("scrollstart");
});
var path = window.location.pathname;
var page = path.split("/").pop();
$("a[href='" + page + "']").addClass("active-page");
/* \Footer eventHandlers */
// Card more button
$(".tappable.fclt-more").on("click", toggleRows);
// Swipe
let touchstart = { "x": 0, "y": 0 };
const SWIPE_THRESHOLD = {
"x": window.innerWidth / 2.5,
"y": window.innerHeight / 2.5
}
let touchmoveHandler = e => {
let translateX = (e.touches[0].screenX - touchstart.x);
let translateY = (e.touches[0].screenY - touchstart.y);
let isHoriSwiping = Math.abs(translateX) > Math.abs(translateY); /* else => vertical scrolling */
if (isHoriSwiping &&
(translateX > 0 && typeof swipeRightAction === "function" ||
translateX < 0 && typeof swipeLeftAction === "function")) {
e.currentTarget.style.transform = "translateX(" + translateX + "px)";
e.currentTarget.style.opacity = 1 - Math.abs(translateX) / window.innerWidth;
// } else if ( translateY < 0 && typeof swipeUpAction === "function" ||
// translateY > 0 && typeof swipeDownAction === "function") {
// e.currentTarget.style.transform = "translateY(" + translateX + "px)";
// e.currentTarget.style.opacity = 1 - translateY / window.innerHeight;
} else {
e.currentTarget.style.transform = null;
e.currentTarget.style.opacity = null;
if (!isHoriSwiping) { /* Cancel swipe */
e.currentTarget.removeEventListener("touchmove", touchmoveHandler);
e.currentTarget.dataset.swipeActive = 0;
}
}
}
let swipeables = document.getElementsByClassName("swipeable");
[...swipeables].forEach(element => {
element.addEventListener("touchstart", e => {
touchstart.x = e.touches[0].screenX;
touchstart.y = e.touches[0].screenY;
e.currentTarget.addEventListener("touchmove", touchmoveHandler);
e.currentTarget.dataset.swipeActive = 1;
});
element.addEventListener("touchend", e => {
if (touchstart.x === 0 && touchstart.y === 0 ||
e.currentTarget.dataset.swipeActive != 1) {
return;
}
let touchend = {
"x": e.changedTouches[0].screenX,
"y": e.changedTouches[0].screenY
};
let swipeFunc;
// if (touchstart.y - touchend.y > SWIPE_THRESHOLD.y) { // UP
// swipeFunc = window["swipeUpAction"];
// } else if (touchend.y - touchstart.y > SWIPE_THRESHOLD.y) { // DOWN
// swipeFunc = window["swipeDownAction"];
// }
if (touchstart.x - touchend.x > SWIPE_THRESHOLD.x) { // LEFT
swipeFunc = window["swipeLeftAction"];
} else if (touchend.x - touchstart.x > SWIPE_THRESHOLD.x) { // RIGHT
swipeFunc = window["swipeRightAction"];
}
if (typeof swipeFunc === "function") {
e.currentTarget.removeEventListener("touchmove", touchmoveHandler);
e.currentTarget.dataset.swipeActive = 0;
swipeFunc(e.currentTarget);
}
touchstart = { "x": 0, "y": 0 };
if (e.currentTarget) {
e.currentTarget.style.transform = null;
e.currentTarget.style.opacity = null;
e.currentTarget.removeEventListener("touchmove", touchmoveHandler);
e.currentTarget.dataset.swipeActive = 0;
}
});
});
// Multiactions
const $bulkables = $(".bulkable");
$bulkables.add(".tappable").on("contextmenu", e => e.preventDefault());
if ($bulkables.length) {
$(".cancel.multiaction").on("click", e => toggleBulkAction("off"));
const $counter = $(".multiaction-count");
const bulkEventHandler = e => {
e.preventDefault();
let $this = $(e.currentTarget);
let isActive = $this.hasClass("bulk-selected");
$this.toggleClass("bulk-selected", !isActive);
$counter.attr("data-count", +$counter.attr("data-count") + (isActive ? -1 : 1));
if ($(".bulkable.bulk-selected").length === 0) {
toggleBulkAction("off");
}
}
window.toggleBulkAction = function _toggleBulkAction(newState) {
let isActive = $(".fclt-content").hasClass("bulk-active");
if (newState === "on" && !isActive) {
$(".fclt-content").addClass("bulk-active");
$(".bulkable > .card-link, .bulkable.list-group-item-action").each((i, el) => {
$(el)
.data("href", $(el).attr("href"))
.data("onclick", $(el).attr("onclick"))
.attr({
"href": null,
"onclick": null,
"aria-disabled": true
});
});
$(".bulkable").on("click", bulkEventHandler);
} else if (newState === "off" && isActive) {
$(".fclt-content").removeClass("bulk-active");
$counter.attr("data-count", 0);
$(".bulkable").off("click", bulkEventHandler)
.removeClass("bulk-selected");
$(".bulkable > .card-link, .bulkable.list-group-item-action").each((i, el) => {
$(el)
.attr({
"href": $(el).data("href"),
"onclick": $(el).data("onclick"),
"aria-disabled": false
}).removeData("href onclick");
});
}
}
$bulkables.each((i, elem) => {
$(elem).on("fclt-longpress", e => {
e.preventDefault();
toggleBulkAction("on");
bulkEventHandler(e);
});
});
}
function addLongPressEventHandler($elem) {
$elem
.on("touchstart.longpress", e => {
let $this = $(e.currentTarget);
var longpressTimer = window.setTimeout($().trigger.bind($this, "fclt-longpress"), 750);
$this.data("longpressTimer", longpressTimer);
}).on("touchend.longpress touchcancel.longpress", e => {
let $this = $(e.currentTarget);
var longpressTimer = $this.data("longpressTimer");
if (longpressTimer) {
window.clearTimeout(longpressTimer);
$this.removeData("longpressTimer");
}
});
}
var $longpressables = $bulkables; // Kan worden uitgebreid
addLongPressEventHandler($longpressables);
// Filter
$(".list-group-filter").on("input", function () {
if (window.filterTimeout) {
clearTimeout(window.filterTimeout);
}
window.filterTimeout = setTimeout(searchList.bind(this), 500);
});
$("textarea").autogrow();
});
function toggleRows() {
var $card = $(this).closest(".card");
$card.find(".fa-chevron-down, .fa-chevron-up").toggleClass("fa-chevron-down fa-chevron-up");
$card.find(".row.open, .row.closed").toggleClass("open closed");
}
function getActiveTheme() {
let activeTheme = window.localStorage.getItem("color-scheme");
if (activeTheme !== "light" && activeTheme !== "dark") {
activeTheme = "auto";
}
return activeTheme;
}
function getThemeIcon(theme) {
theme = theme || getActiveTheme();
if (theme === "light") {
return "fa-sun-bright";
} else if (theme === "dark") {
return "fa-moon fas";
} else {
return "fa-circle-half-stroke far";
}
}
function toggleTheme(newTheme) {
if (newTheme === "light" || newTheme === "dark") {
window.localStorage.setItem("color-scheme", newTheme);
} else if (newTheme === "auto") {
window.localStorage.removeItem("color-scheme");
}
let activeTheme = getActiveTheme();
if (activeTheme !== "light" && activeTheme !== "dark") { // Dus auto
activeTheme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
}
$("html").attr("data-bs-theme", activeTheme);
$("[data-theme-toggle]").children("i").replaceWith(I(getThemeIcon(newTheme)));
}
function removeCardAnimated(elem) {
const elemStyle = window.getComputedStyle(elem);
let padding = parseInt(elemStyle.paddingTop) + parseInt(elemStyle.paddingBottom);
elem.style.flexBasis = elem.clientHeight - padding + "px";
elem.style.visibility = "hidden";
while (elem.lastChild) { // Vanilla .empty();
elem.removeChild(elem.lastChild);
}
elem.offsetHeight; // Flushes the CSS changes
elem.classList.add("card-removing"); // Sets transition duration
elem.addEventListener("transitionend", () => {
elem.remove();
}, { once: true });
elem.classList.add("card-remove"); // Animate object vertically disappearing
}
function searchList() {
var s = this.value.toLowerCase();
var $section = $(this).next("section");
// Remove expliciete 'first' en 'last' classes voor de styling
$(".list-group-item-first, .list-group-item-last").removeClass("list-group-item-first list-group-item-last");
if ($section.length) {
if (s === "") {
$section.find(".list-group-item").show();
} else {
// De regels
$section.find(".list-group-item").each((i, elem) => {
$(elem).toggle($(elem).text().toLowerCase().indexOf(s) != -1);
});
// De dividers (eventueel)
$section.find(".list-group-header").each((i, elem) => {
$(elem).toggle($(elem).nextUntil(".list-group-header").filter(":visible").length > 0);
});
}
}
// Add expliciete 'first' en 'last' classes voor de styling
$section.find(".list-group-item").filter(":visible").first().addClass("list-group-item-first");
$section.find(".list-group-item").filter(":visible").last().addClass("list-group-item-last");
}
function McltCallbackAndThen(afterAction) {
return function(json, textStatus) {
if (json.message) alert(json.message); // Normaal door FcltMgr.closeDetail
if (json.warning) alert(json.warning);
if (json.toaster) jqToast(json.toaster);
json.message = null;
json.warning = null;
json.toaster = null;
if (json.success) {
if (afterAction) afterAction(json);
}
}
};
function McltCallbackAndThenAlways(afterAction) {
return function(json, textStatus) {
if (json.message) alert(json.message);
if (json.warning) alert(json.warning);
if (json.toaster) jqToast(json.toaster);
json.message = null;
json.warning = null;
json.toaster = null;
if (afterAction) afterAction(json);
}
};
function jqToast(msg) {
var $toaster = $(`
<div class="toast align-items-center" role="alert" aria-live="assertive" aria-atomic="true">
<div class="d-flex">
<div class="toast-body">
${msg}
</div>
<button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
</div>
`).appendTo("body");
$toaster.on("hidden.bs.toast", function () { $(this).remove(); });
var toaster = new bootstrap.Toast($toaster);
toaster.show();
}
var McltCallbackSaved = McltCallbackAndThen(function(json) {
if (json.success)
jqToast(L("lcl_mobile_data_saved"));
});
var McltCallbackRefresh = McltCallbackAndThen(function(json) {
window.location.href = window.location.href;
});
var McltCallbackClose = McltCallbackAndThen(function(json) {
window.history.back(1);
});
var McltCallbackHome = McltCallbackAndThen(function(json) {
// window.history.back(1); doet geen refresh als je bijvoorbeeld net een reservering hebt verwijderd
window.location.href = rooturl + "/appl/pda/facilitor.asp";
});
var mobile = {
button: {
click: async function(evt, btn) {
FcltMgr.stopPropagation(evt);
if (btn.getAttribute("singlepress") && $(btn).hasClass("btn_disabled")) { // FcltMgr.alert("Heb geduld");
return;
}
if (btn.getAttribute("singlepress")) {
mobile.button.disable(btn);
}
var elem = btn.getAttribute("mobClick");
// window.fcltevent = evt;
var result = eval(elem);
if (typeof result === "object" && result instanceof Promise) {
result = await result;
}
// Dit lijkt erg onzinning (we zitten binnen mobile) maar soms als
// een scherm/dialoog net gesloten is door de action van de button
// blijkt de code toch nog hier te komen terwijl mobile weg is
if (typeof mobile == "undefined")
return;
if (result === false) {
mobile.button.enable(btn);
}
},
disable: function(btn) {
if (!btn || btn.tagName != 'A')
return; // not a mobile button
$(btn).toggleClass("btn_disabled", true)
.removeClass("btn_enabled");
},
enable: function(btn) {
if (!btn || btn.tagName != 'A')
return; // not a mobile button
$(btn).toggleClass("btn_enabled", true)
.removeClass("btn_disabled");
}
},
changePage: function _changePage(id) {
if (id) {
$(".page.show").removeClass("show");
location.hash = "#" + id;
} else { // Back
if (location.hash) {
$(".page:first").addClass("show");
location.hash = "";
} else {
history.back(1);
}
}
}
}
var $signatureButton;
var save_url;
function onBijlagenMobile(formurl /* protected */, saveUrl /* protected */, multi, objButton) {
save_url = saveUrl; /* Global voor uploadDone() */
$signatureButton = $(objButton); /* Global voor uploadDone() */
$.get(formurl, {}, function (data) { // Levert een HTML segment op; een <script> en een <div>
$("#form-signature").html(data).trigger("create"); // Zet dat in de bestaande hash-pagina
mobile.changePage("page-signature"); // En ga naar die pagina
});
}
// make filename safe for show
function safeFilename(s) {
return s.replace(/[\x00-\x1F|\/|\\|\*|\%\<\>\"\:\;\?\|\+]+/g, "_");
}
// remove element of deleted file
function removeElement(element) {
return function(json) {
let $bijlageButton = element.closest(".attachments_form").find(".add_attachment > [role=button][nBijlagen]");
if ($bijlageButton.length) {
$bijlageButton.attr("nBijlagen", parseInt($bijlageButton.attr("nBijlagen") || 0, 10) - 1);
}
element.remove();
if (json.toaster) {
jqToast(json.toaster);
}
}
};
// delete flex-attachment
function DeleteFile(fname, safeDeleteurl, element) {
FcltMgr.confirm(L("lcl_delete") + " " + safeFilename(fname) + "?", function() {
var data = {};
protectRequest.dataToken(data);
$.post("../shared/" + safeDeleteurl,
data,
McltCallbackAndThen(removeElement(element))
);
});
}
function disable(btn) {
$(btn).attr("onclick", "return false;")
.toggleClass("btn-disabled", true)
.removeClass("btn-enabled");
}
function toTouch() {
window.localStorage.setItem("interface", "touch");
parent.location.href = "../../default.asp?interface=touch";
}
function toDesktop() {
window.localStorage.setItem("interface", "desktop");
parent.location.href = "../../default.asp?interface=desktop";
}