class AudioPlayer { constructor() { this.dbName = 'audioCacheDB'; this.audioElements = {}; // To keep track of audio elements by name this.initDB(); } async initDB() { if (!('indexedDB' in window)) { console.log('This browser doesn\'t support IndexedDB'); return; } const request = indexedDB.open(this.dbName, 1); request.onupgradeneeded = event => { const db = event.target.result; if (!db.objectStoreNames.contains('audios')) { db.createObjectStore('audios', { keyPath: 'name' }); } }; request.onerror = event => { console.error('Database error: ', event.target.errorCode); }; request.onsuccess = event => { this.db = event.target.result; }; } load(name, url) { if (this.audioElements[name]) { console.log(`Audio ${name} is already loaded or loading.`); return; } const transaction = this.db.transaction(['audios'], 'readonly'); const store = transaction.objectStore('audios'); const request = store.get(name); request.onerror = event => { console.error('Error fetching audio from DB:', event.target.errorCode); }; request.onsuccess = event => { if (request.result) { const audioBlob = request.result.audioBlob; this.audioElements[name] = new Audio(URL.createObjectURL(audioBlob)); } else { console.log(`Fetching ${name} from network`); fetch(url) .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.blob(); }) .then(blob => { const audio = new Audio(URL.createObjectURL(blob)); this.audioElements[name] = audio; const transaction = this.db.transaction(['audios'], 'readwrite'); const store = transaction.objectStore('audios'); store.add({ name: name, audioBlob: blob }); console.log(`Audio ${name} has been loaded and cached.`); }) .catch(error => { console.error(`Failed to fetch audio ${name}:`, error); // Optionally remove from memory if partially added delete this.audioElements[name]; }); } }; } play(name) { const audio = this.audioElements[name]; if (audio) { console.log(`Playing ${name}`); audio.play(); } else { console.log(`Audio ${name} is not loaded yet. Please load it first.`); } } flush() { return new Promise((resolve, reject) => { const transaction = this.db.transaction(['audios'], 'readwrite'); const store = transaction.objectStore('audios'); const request = store.clear(); request.onsuccess = () => { console.log('All audio files have been removed from the database.'); this.audioElements = {}; // Clear the audio elements stored in memory console.log('In-memory audio cache has been cleared.'); resolve(); }; request.onerror = (event) => { console.error('Failed to clear database:', event.target.errorCode); reject(event.target.errorCode); }; }); } } // search object var swSearch = { // timer for searching SearchTimer: false, // characters needed for invoking search SearchInvoke: 3, goSearch: function (forced) { // when forced cancel the timer if (forced === true) clearTimeout(this.SearchTimer); var searchstring = $("#sw_header_searchbar").val(); if (searchstring.length < swSearch.SearchInvoke) return; SW_UI.hideNavbar(); var url = swSearchEngine.executeSearch(searchstring); swSearchEngine.uiHide(); return; }, // timer handleSearch: function (element) { if (this.SearchTimer != undefined) clearTimeout(this.SearchTimer); this.SearchTimer = setTimeout(function () { var val = $(element).val(); if (val.length >= swSearch.SearchInvoke) { swSearch.goSearch(); } }, 1000); }, // bind some stuff and boot up the searchengine init: function () { // loadup the searchengine if it is not there already if ($("#sw_search_engine_script").length == 0) { $.get(SW_Router.go("MySWRetail", "searchEngine"), function (data) { $("#sw_userinterface_start").after(data); swSearchEngine.initSearch(); }); } $(document).off("input", "input#sw_header_searchbar"); $(document).on("input", "input#sw_header_searchbar", function (e) { swSearch.handleSearch(this); }); }, }; var SW_LangTable = undefined; var SW_Lang = { // translation function translate: function (input) { var retval = SW_LangTable[input]; return retval ?? input; }, getCurrent: function () { return $("#sw_app_lang_select").val(); }, langFormat: function (state) { if (!state.id) return state.text; // optgroup return " " + state.text; }, setupSelection: function (id) { id = id ?? "langselect"; $("#" + id).select2({ minimumResultsForSearch: Infinity, formatResult: SW_Lang.langFormat, formatSelection: SW_Lang.langFormat, escapeMarkup: function (m) { return m; } }); } } // generic translation helper function function __(input) { return SW_Lang.translate(input); } String.prototype.beginsWith = function (string) { return (this.indexOf(string) === 0); }; // serialize a grid, using sw_serialize_form, will need a data-sw-serializer-linenumber attribute in order for the serialization to work // will go by each line and return an array that can be used to do other neat things (like posting to the server) function sw_grid_serialize(line_selector) { var rows = $(line_selector).length - 1; var retval = {}; $.each($(line_selector), function (number) { var curline = $(this).attr("data-sw-serializer-linenumber"); if (curline != undefined) { var data = sw_serialize_form($(line_selector + '[data-sw-serializer-linenumber="' + curline + '"] input')); retval[curline] = data; } }); return retval; } // serialise all fields, based upon the name attribute, will create an array [name:value,name:value] that can be used. the fdields serialized do not have to be in a form, just everything the query catches will be serialized function sw_serialize_form(selector) { var inputs = $(selector); var retval = {}; var obj = $.map(inputs, function (n, i) { var o = {}; if (n.name == undefined) return; if (n.type == "checkbox") { if ($(n).is(':checked') == true) o[n.name] = 1; else o[n.name] = -1; } else o[n.name.replace('[]', '')] = $(n).val(); doit = 1; if (n.type == 'radio') { doit = 0; o = {}; var lbl = $(n).closest("label"); ; if (lbl.hasClass("active")) { o[n.name.replace('[]', '')] = n.id; jQuery.extend(retval, o); } } if (doit == 1) jQuery.extend(retval, o); return [[n.name, o]]; }); return retval; } function sw_deserialize_form(selector, data, load_undefined) { var inputs = $(selector); var retval = {}; load_undefined = load_undefined ?? true; $(selector).each(function (item) { input = $(this); name = input.attr("name"); if (input.attr("type") == "checkbox") { if (data[name] != undefined) { if (data[name] == 1) input.prop('checked', true); else input.prop('checked', false); } } else { if (load_undefined == true) { if (data[name] != undefined) input.val(data[name]); } else input.val(data[name]); } }); return; } // default alert box, use SW_UI.alert instead of this function sw_alert(input, callback) { bootbox.alert({ "message": input, "callback": callback, "animate": false }); } function toggleSidebar() { if ($('#sidebar-toggle').hasClass('collapsed')) { showSidebar(); } else { hideSidebar(); } // Reset fixedHeader if ($('.fixedHeader').length != 0) { handleFixedHeader(); } } function showSidebar() { $('#primary-sidebar').show(); //, .sidebar-background $('#sidebar-toggle').removeClass('collapsed'); $('body').addClass('with-sidebar'); $('#sidebar-toggle-left').hide() } function hideSidebar() { $('#primary-sidebar').hide(); //, .sidebar-background $('#sidebar-toggle').addClass('collapsed'); $('#sidebar-toggle-left').show() $('body').removeClass('with-sidebar'); } (function ($) { $.fn.customCheckbox = function () { var selector = this.selector; return this.each(function () { // Get the original element var el = this; // Hide the checkbox $(this).hide(); // Create replacement element var rep = $('').addClass('sw-slider').insertAfter(this); // Check help attr if ($(this).attr('data-help') != "undefined") { $(rep).attr('data-help', $(this).attr('data-help')); } // Set default state if ($(this).is(':checked')) { $(rep).addClass('on'); } else { $(rep).addClass('off'); } }); }; })(jQuery); // include element Library/sw_progress.ctp for this to work var SWProgressObject = { gSWProgress: "0", gSWProgressPump: "", last_modal_id: "", Show: function (title, subheader) { this.last_modal_id = SW_UI.hideModals(); if (title == undefined) $("#sw_progress_dialog_header").html(__("SW-Retail is bezig!")); else $("#sw_progress_dialog_header").html(title); if (subheader == undefined) $("#sw_progress_dialog_subheader").html(__("Sluit uw browser niet af en ga niet naar een andere pagina!")); else $("#sw_progress_dialog_subheader").html(subheader); $("#sw_progress_dialog").modal(); this.gSWProgress = 10; this.gSWProgressPump = setInterval(function () { $("#sw_progress_bar").css('width', SWProgressObject.gSWProgress + '%') SWProgressObject.gSWProgress += 10; if (SWProgressObject.gSWProgress > 99) { SWProgressObject.gSWProgress = 10; } }, 500); }, descriptionSet: function (description) { $("#sw_progress_dialog_customtext").html(description); }, Hide: function () { if (!SW_UI.modalsVisible("sw_progress_dialog")) return; $("#sw_progress_dialog").modal("hide"); //checks if the button exists and removes the button if it does. if ($("#cancelPaymentBtn").length) { $("#sw_progress_dialog_footer").empty(); } if (this.last_modal_id != "") { $("#" + this.last_modal_id).modal('show'); this.last_modal_id = ""; } clearInterval(this.gSWProgressPump); }, CancelButton: function () { $("#sw_progress_dialog_footer").append(''); } } // play a help wizard var SW_Wizard = { tileToggle: function (divider_element) { var divider = $(divider_element); divider.parent().children(".sw_tile").addClass("hidden"); var select = false; divider.parent().children().each(function () { var item = $(this); if (item.hasClass("sw_tile_divider")) { if (item.get(0) == divider.get(0)) { select = true; } else select = false; } if (item.hasClass("sw_tile")) { if (select) item.removeClass("hidden"); else item.addClass("hidden"); } }); }, hideAll: function () { $(".sw_help_wizard_page").addClass("hidden"); }, stopWizard: function () { $(".sw_help_item_start").removeClass("hidden"); SW_Wizard.hideAll(); SW_UI.setHistory("Wizard", SW_Router.currentURL()); $("#helpnavi").removeClass("hidden"); }, playStep: function (id, step) { if ($("#" + id + "_" + step).length == 0) { SW_UI.alert(__("Wizard bestaat niet")); return; } $(".sw_help_item_start").addClass("hidden"); $("#helpnavi").addClass("hidden"); SW_UI.setHistory("Wizard", SW_Router.currentURL() + "?wizard=" + id + "&step=" + step); SW_Wizard.hideAll(); SW_Wizard.playSlide($("#" + id + "_" + step)); }, playNext: function (button) { if ($(button).hasClass("disabled")) return; var next = $(button).closest(".sw_help_wizard_page").next(); SW_Wizard.hideAll(); SW_Wizard.playSlide(next); }, playPrevious: function (button) { if ($(button).hasClass("disabled")) return; var previous = $(button).closest(".sw_help_wizard_page").prev(); if (previous.length < 1) return; SW_Wizard.hideAll(); SW_Wizard.playSlide(previous); }, playSlide: function (item) { item.removeClass("hidden"); item.find(".sw_help_item_start").removeClass("hidden"); $(".sw-wizard-prev").removeClass("disabled"); if (item.attr("data-sw-wizard-number") == "0") { $(".sw-wizard-prev").addClass("disabled"); } var next = item.next(); $(".sw-wizard-next").removeClass("disabled"); if (next.attr("data-sw-wizard") != item.attr("data-sw-wizard")) { $(".sw-wizard-next").addClass("disabled"); } } }; class SW_InputValidator { constructor(inputId, rule, withblurring) { this.inputId = inputId; this.inputField = document.getElementById(inputId); if (this.inputField == undefined) return; this.validationEndpoint = SW_Router.go("SWGenericServices", "validator"); this.isValid = false; // Initialize isValid property this.validation_rule = rule; this.errorMessage = ""; this.inputClass = "form-group"; if (withblurring == true) this.inputField.addEventListener('blur', () => this.validateInput()); } validateInput() { const valueToValidate = this.inputField.value; this.clearError(); this.errorMessage = ""; return fetch(this.validationEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({value: valueToValidate, rule: this.validation_rule}), }) .then(response => response.json()) .then(data => this.handleValidationResponse(data)) .catch(error => { console.error('Error during validation:', error); }); } handleValidationResponse(data) { this.isValid = false; if (data.status == "error") { this.errorMessage = data.extended; this.showError(); } // Set isValid based on the validation result if (data.status == "ok") this.isValid = true; } showError() { // for some reason, there are also input-groups used, i donw know the difference. for now we only use class form-group but i suspect this is wrong $("#" + this.inputId).closest("." + this.inputClass).addClass('has-error'); $("#" + this.inputId).closest("." + this.inputClass).append('
' + this.errorMessage + '
'); } clearError() { $("#" + this.inputId).closest("." + this.inputClass).removeClass('has-error'); $("#" + this.inputId).closest("." + this.inputClass).find(".invalid-feedback").remove(); } } var SW_Services = { currentModuleName: "", // get val from generic services getVal: function ($item, param, callback) { $.get(SW_Router.go("SWGenericServices", "val_only_" + $item, [param]), function (data) { callback(data); }, 'json'); }, modulenameSetCurrent: function (name) { this.currentModuleName = name; }, // get current running module name (for display purposes etc..) modulenameGetCurrent: function () { return this.currentModuleName; }, jsonToCommaSep: function (json_input) { return json_input; }, removeLastPart: function (url_to_remove_last_part) { var lastSlashIndex = url_to_remove_last_part.lastIndexOf("/"); if (lastSlashIndex > url_to_remove_last_part.indexOf("/") + 1) { // if not in http:// return url_to_remove_last_part.substr(0, lastSlashIndex); // cut it off } else { return url_to_remove_last_part; } }, loggedOnCheck: function () { var data = {}; data.current = window.location.href; data.device_token = this.getDeviceToken(); $.post(SW_Router.go("sw_lightning", "ajaxloggedoncheck.php"), data, function (data) { if (data.loggedon == 0) { if (SW_UI.isPWA()) { SW_Services.relogin(); return; } SW_UI.blockUI(); window.location = SW_Router.go("Medewerker", "logout"); return; } ; // and reload the page since the version has changed (very important for PWA) if (SW_Router.getVersion() != data.version) { location.reload(); } }, 'json'); }, sendTelemetry: function () { var data = {}; data.current = window.location.href; var width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; var height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; data.vp_width = width; data.vp_height = height; data.platform = navigator.platform; data.is_pwa = SW_UI.isPWA(); data.current_url = SW_Router.getController(); $.post(SW_Router.go("Medewerker", "telemetry"), data, function (data) { }, 'json'); }, // perform relogin in background when necessary relogin: function () { if (SW_UI.isPWA()) { var logindata = Lockr.get("logindata"); if (logindata != undefined) { SW_UI.blockUI(); $(".content").hide(); $.post(SW_Router.go('Medewerker', "appHashedLogon", [logindata.id, logindata.hash, logindata.env]), function (data) { try { var obj = JSON.parse(data); SW_UI.unblockUI(); } catch (err) { console.log(err); console.log("logon failed"); SW_UI.blockUI(); window.location = SW_Router.go("Medewerker", "logout") + "?fromrelogin"; } }); } } }, // use only in conjunction with the callback getNewDeviceToken: function (type, callback) { if (SW_Cache.read("device_token") != undefined) { if (callback && typeof (callback) === "function") callback(); } $.post(SW_Router.go("Medewerker", "getDeviceToken", [type]), function (data) { SW_Cache.write("device_token", data.additional.device_token); if (callback && typeof (callback) === "function") callback(); }, 'json'); }, // get the device token getDeviceToken: function () { return SW_Cache.read("device_token"); }, //check wether the device token is still valid, and otherwise invalidates the cache checkDeviceToken: function () { if (this.getDeviceToken() == undefined) return; $.get(SW_Router.go("Medewerker", "checkDeviceToken", [this.getDeviceToken()]), function (data) { if (data.status == "error") SW_Cache.clear("device_token"); }, 'json'); return true; }, } // for rendereing a correct UI. Remember -> ALWAYS validate on server!!!! var SW_UI_Auth = { tagCheck: function (tag, callback) { $.get(SW_Router.go("Medewerker", "verifyTag", [tag]), function (data) { if (callback && typeof (callback) === "function") { if (data.result == 0) callback(false); else callback(true); } }, 'json'); }, // make sure elements with a certain tag are visible updateElements: function (tag) { } } // the one and only sw_ui var SW_UI = { isLoading: false, timerID: 0, overlays: 0, resizerOffsetBottom: 0, resizerID: "", resizerInit: 0, inDialogLoading: 0, isBlocked: 0, configUpdateController: "Dashboard", sounds: true, vibrate: true, assistantShow: function () { if ($("#assistant_master_block").length == 0) { $.get(SW_Router.go("MySWRetail", "assistant"), function (data) { $("#sw_userinterface_start").after(data); swAssistant.showMe(); }) } else { swAssistant.showMe(); } }, articlePopup: function (id, warehouse_id) { SW_UI.blockUI(); var data = {}; if (warehouse_id != undefined) data.warehouse_id = warehouse_id; data.article_id = id; SW_UI.runDialog(SW_Router.go('ArticleInfo', 'lightInfo'), data, "sw_dynamic_data", null, function () { SW_UI.unblockUI(); }); }, articlePopupBarcode: function (barcode, warehouse_id) { SW_UI.blockUI(); var data = {}; if (warehouse_id != undefined) data.warehouse_id = warehouse_id; data.barcode = barcode; SW_UI.runDialog(SW_Router.go('ArticleInfo', 'lightInfo'), data, "sw_dynamic_data", null, function (data) { if (data.article_found == 0) SW_UI.Beep_Error(); else SW_UI.Beep_OK(); SW_UI.unblockUI(); }); }, // plave a sound when a barcode has been scanned (after that we play beep_ok or beep_warning /beep_error Code_OK: function (pattern) { if (!this.sounds) return; if (!this.isPWA()) return; audioManager.play("code_ok"); if (!this.vibrate) return; if (pattern == undefined) pattern = [70]; if (typeof navigator.vibrate === 'function') navigator.vibrate([70]); }, // we beep only in pwa mode Beep_OK: function () { if (!this.sounds) return; if (!this.isPWA()) return; audioManager.play("beep_ok"); }, // we beep only in pwa mode Beep_Warning: function () { if (!this.sounds) return; if (!this.isPWA()) return; audioManager.play("beep_warning"); }, // we beep only in pwa mode Beep_Error: function () { if (!this.sounds) return; if (!this.isPWA()) return; audioManager.play("beep_error"); }, // character counter for better seo maxMetaDesc: 160, maxMetaTitle: 70, attachCharacterCount: function (inputIdOrTextAreaId, maxCount) { // Get a reference to the input or textarea var inputOrTextArea = document.getElementById(inputIdOrTextAreaId); if (inputOrTextArea == undefined) return; // Create a new character count element (a span) var charCount = document.createElement('span'); // Initialize the character count in the span charCount.textContent = inputOrTextArea.value.length + " / " + maxCount; // Append the character count element after the input or textarea inputOrTextArea.parentNode.insertBefore(charCount, inputOrTextArea.nextSibling); // Add an event listener to the input or textarea for input events inputOrTextArea.addEventListener('input', function () { // Get the length of the text in the input or textarea var text = inputOrTextArea.value; var length = text.length; // Update the character count element with the current count and maximum count charCount.textContent = length + " / " + maxCount; // Check if the maximum count is reached and add a class if necessary if (maxCount && length > maxCount) { charCount.classList.add('char-count-limit-exceeded'); } else { charCount.classList.remove('char-count-limit-exceeded'); } }); // Initialize the character count and apply the class if needed if (maxCount && inputOrTextArea.value.length > maxCount) { charCount.classList.add('char-count-limit-exceeded'); } }, // pwa menu pwaMenu: function () { if (!this.isPWA()) return true; if ($("#pwa-menu").length < 1) { $("body").append('
'); SW_UI.blockUI(); $.get(SW_Router.go("Medewerker", "menuPWA", [], true), function (data) { $("#pwa-menu").html(data.html); SW_UI.unblockUI(); swPWAPopup.display(); }, 'json'); return false; } swPWAPopup.display(); return false; }, OverlayInc: function () { SW_UI.overlays++; }, OverlayDec: function () { SW_UI.overlays--; if (SW_UI.overlays < 0) SW_UI.overlays = 0; }, // Overlay, status can be show / hide Overlay: function (status) { return false; }, // to hide the normal ui and display a custom overlayed thing UIHide: function () { $("#sw_userinterface_start div div.row-fluid").addClass("hide"); $("#sw_userinterface_start div.sidebar").addClass("hide"); $("#sw_userinterface_start div.main-content").addClass("hide"); }, // to hide the normal ui and display a custom overlayed thing UIShow: function () { $("#sw_userinterface_start div div.row-fluid").removeClass("hide"); $("#sw_userinterface_start div.main-content").removeClass("hide"); $("#sw_userinterface_start div.sidebar").removeClass("hide"); }, OverlayHook: function (status) { //console.log("HOOK "+status); //console.trace(); if (status == "show") { SW_UI.OverlayInc(); if (SW_UI.overlays > 0) SW_UI.Overlay("show"); } if (status == "hide") { //console.log("HOOK HIDING"); SW_UI.OverlayDec(); if (SW_UI.overlays == 0) SW_UI.Overlay("hide"); } }, // scroll to div, if div in a sw_scrolltainer div we scroll there scrollTo: function (divid, offset) { target = $("#" + divid); if (target.length) { offset = offset ?? 200; var top = target.offset().top - offset; //console.log(top); var done = false; if (target.closest("div .sw_scrolltainer").length) { target.closest("div .sw_scrolltainer").animate({scrollTop: top}, 100); done = true; } // proper scrolling in a modal if (target.closest("div.modal-body").length) { target.closest("div.modal-body").animate({scrollTop: top}, 100); done = true; } if (!done) { $('html,body').animate({scrollTop: top}, 100); } return false; } }, setLanguage: function (elem) { var lang = $(elem).val(); $.post(SW_Router.go("Medewerker", "setLanguage", [lang]), function (data) { location.reload(); }); }, // set history setHistory: function (title, url) { var stateObj = {url: url, title: title}; /*if (history) { if (history.state) { console.log(history.state.title); console.log(stateObj); if (history.state.title==stateObj.title) { return; } } } */ document.title = title; history.pushState(stateObj, title, url); }, sizeIT: function (element) { var bottommargin = 0; $(element + ".modal").height(($(window).height() - bottommargin) + "px"); var parent = $(element + ".modal").height(); var mh = $(element + " .modal-header").height(); var mf = $(element + " .modal-footer").height(); $(element + " .modal-body").height((parent - 200) + "px"); }, // Loading top bar setLoading: function () { this.isLoading = true; $("#top-loading").removeClass("hide").fadeIn(50); }, // set a dialog to full screen, except when sw_do_not_fullsize class has been set dialogSizeToScreen: function (element, bottommargin) { if ($(element).hasClass("sw_do_not_fullsize")) return; bottommargin = bottommargin ?? 20; $(element + ".modal").height(($(window).height() - bottommargin) + "px"); var parent = $(element + ".modal").height(); bottom_offset = 220; if ($(window).width() < 1031) bottom_offset = 110; $(element + " .modal-body").height((parent - bottom_offset) + "px"); }, redactorize_simple: function (selector) { var redactor_object = RedactorX(selector, { context: true, embed: { checkbox: true }, addbar: false, image: false, quote: false, block: false, table: false, control: false, editor: { lang: SW_Lang.getCurrent(), minHeight: "200px", }, shortcutsRemove: true, subscribe: { // we gotta trigger the on change of the originazl element. set the value and go with the flow 'editor.blur': function () { $(this.$element.nodes[0]).val(this.app.editor.getContent()); var event = new Event('change'); this.$element.nodes[0].dispatchEvent(event); } }, paste: { images: false, blockTags: ['p'] }, }) ; return redactor_object; }, // remove top bar removeLoading: function () { this.isLoading = false; $("#top-loading").fadeOut(50); }, // block just a div blockDIV: function (selector, message) { message = message ?? __(""); message = ""; $(selector).block({ message, css: { border: 'none', padding: '15px', top: '10%', '-webkit-border-radius': '10px', '-moz-border-radius': '10px', 'z-index': 95000, }, // styles for the overlay overlayCSS: { backgroundColor: '#000', opacity: 0.6, cursor: 'wait', 'border-radius': '4px', 'z-index': 94999 }, }); }, unblockDIV: function (selector) { $(selector).unblock(); }, // block the user interface blockUI: function (message) { if (this.isBlocked == 1) { return; } this.isBlocked = 1; message = message ?? __("Een moment alstublieft"); message = "
" + message + "
"; SW_UI.OverlayHook("show"); // removing this //this.setLoading(); $.blockUI({ message: message, css: { border: 'none', padding: '15px', '-webkit-border-radius': '10px', '-moz-border-radius': '10px', 'z-index': 95000, }, // styles for the overlay overlayCSS: { backgroundColor: '#000', opacity: 0.6, cursor: 'wait', 'z-index': 94999 }, onUnblock: function () { SW_UI.removeLoading(); SW_UI.OverlayHook("hide"); } }); }, // returns a link with title. url needs to be relative linkApp: function (title, url) { var link = '' + title + ''; return link; }, blockUIMessage: function (message) { $(".blockMsg").html(message); }, unblockUI: function () { this.isBlocked = 0; $.unblockUI(); }, goHome: function () { window.location = SW_Router.go("Dashboard", ""); }, // setup the modal fullscreen. Sets the dialog to fullscreen and also sets the table to fullscreen scrolling fullScreenGridModal: function (table_id) { return; var modal = $("#" + table_id + "_wrapper").closest("div.modal"); // nothing to see here if (modal.length < 1) return; var modal_id = modal.attr("id"); $("#" + modal_id).addClass("modal-fullheight"); var header_height = $("#" + modal_id + " .modal-header").outerHeight(); var footer_height = $("#" + modal_id + " .modal-footer").outerHeight(); var ht = window.innerHeight - 238; var pp = ht - header_height - footer_height; $('#' + modal_id + ' .dataTables_scrollBody').css({ "overflow": "visible", "height": pp + "px", "overflow-y": "scroll", }); // and even more luxuries, autoscale when window resized // do not add a listeren when we already have done this if ($("#" + modal_id).hasClass("has_scale_listener")) return; $('#' + modal_id).addClass("has_scale_listener"); $(window).on('resize', function () { SW_UI.fullScreenGridModal(table_id); }); }, // wil open a ticket at the helpdesk sendMessage: function (to, subject, body) { data = { 'to': to, 'subject': subject, 'body': body, }; SW_UI.runDialog(SW_Router.go("MySWRetail", "ticketUI"), data, "sw_dynamic_data"); }, webContentUI: function () { SW_UI.blockUI(); SW_UI.runDialog(SW_Router.go("CMSForm", "webContentUI")); }, hideTooltips: function () { $('.tooltip').hide(); }, // run a dialog (fetch dialog from backend) runDialog: function (dialog_url, requestdata, dialog_div, callback, run_before_dialog_start) { if (SW_UI.inDialogLoading == 1) { return; } else { SW_UI.inDialogLoading = 1; } // hide tooltips $('.tooltip').hide(); dialog_div = dialog_div ?? "sw_dynamic_data"; if ($("#" + dialog_div).length == 0) { var newdiv = "
"; $("body").append(newdiv); } val = $.post( dialog_url, requestdata, function (data) { if (data.error !== undefined) { SW_UI.alert("Fout! ".data.errortext); SW_UI.inDialogLoading = 0; return; } if (run_before_dialog_start && typeof (run_before_dialog_start) === "function") { run_before_dialog_start(data); } $("#" + dialog_div).html(data.html); SW_RUN_DYNAMIC(window[callback]); SW_UI.inDialogLoading = 0; }, 'json' ); }, // hide all modals hideModals: function () { lastid = ""; $(".modal.in").each(function () { $(this).modal("hide"); lastid = $(this).attr("id"); }); return lastid; }, systemidSet: function (id) { Lockr.set('CashRegister', id); }, sideBarToggler: function (hide) { if (hide == undefined) { if ($(".sidebar:visible").length == 0) { showSidebar(); } else { hideSidebar(); } // Reset fixedHeader if ($('.fixedHeader').length != 0) { handleFixedHeader(); } return; } ; if (hide == true) hideSidebar(); if (hide == false) showSidebar(); }, systemidGet: function () { var sysid = Lockr.get('CashRegister'); return sysid ?? ""; }, // hide an expanded navbar hideNavbar: function () { if (SW_UI.isMobileDevice()) { $(".navbar-toggle").click(); } }, startMyRegister: function () { var location = SW_Router.go("TbltMain", "/"); if (SW_UI.systemidGet() != "") { location += "/index/" + SW_UI.systemidGet(); } window.location = location; }, // test for visible modals, modal_id can be left empty or be the id of an mobile to test wether it is visible modalsVisible: function (modal_id) { if (modal_id != undefined) { if ($("#" + modal_id + ".modal.in").length > 0) { return true; } ; return false; } if ($(".modal.in").length > 0) { return true; } ; return false; }, // double callback function. if coice_b is a function then only one option button will be shown aorb_dialog: function (question, dismissable, choice_a, choice_b, callback_a, callback_b, callback_close) { SW_UI.hideModals(); SW_UI.OverlayHook("show"); var buttons = {}; var cb = true; if ((choice_b && typeof (choice_b) === "function")) { callback_a = choice_b; } ; buttons.choicea = { label: choice_a, className: "btn-default", callback: function () { SW_UI.OverlayHook("hide"); if (callback_a && typeof (callback_a) === "function") { callback_a(); } } }; if (!(choice_b && typeof (choice_b) === "function")) { buttons.choiceb = { label: choice_b, className: "btn-default", callback: function () { SW_UI.OverlayHook("hide"); if (callback_b && typeof (callback_b) === "function") { callback_b(); } } }; } ; bootbox.dialog({ message: question, title: "", buttons: buttons, closeButton: cb, onEscape: function () { if (callback_close && typeof (callback_close) === "function") { SW_UI.OverlayHook("hide"); callback_close(); } } }); }, // ask yes or nbo question with callback yesorno: function (question, callback_yes, callback_no) { last_modal_id = SW_UI.hideModals(); SW_UI.OverlayHook("show"); var confirm_settings = { "message": question, "callback": function (result) { SW_UI.OverlayHook("hide"); if (last_modal_id != "") { $("#" + last_modal_id).modal('show'); } if (result == true) { if (callback_yes && typeof (callback_yes) === "function") { callback_yes(); } } else { if (callback_no && typeof (callback_no) === "function") { callback_no(); } } }, "animate": false }; bootbox.confirm(confirm_settings); }, // default alert box alert: function (input, callback) { SW_UI.OverlayHook("show"); var message = input; var title = ""; if ((Array.isArray(input))) { title = input[0]; message = input[1]; } bootbox.alert({ "message": message, "title": title, "animate": false, "callback": function (result) { SW_UI.OverlayHook("hide"); if (callback && typeof (callback) === "function") { callback(); } } }); $(".bootbox-alert.in button").attr("data-sw-key", "F9"); }, // default alert box alert_wide: function (input, callback) { SW_UI.OverlayHook("show"); bootbox.alert({ "message": input, "animate": false, "size": "large", "callback": function (result) { SW_UI.OverlayHook("hide"); if (callback && typeof (callback) === "function") { callback(); } } }); $(".bootbox-alert.in button").attr("data-sw-key", "F9"); }, valCompare: function (input) { myval = input.value; compval = $("#" + $(input).attr("valcheck")).val(); console.log(myval + " " + compval); if (myval != compval) { console.log("nop"); $(input).closest("div").addClass("has-warning") } else { $(input).closest("div").removeClass("has-warning") } }, valCompareSetup: function (input) { val = 'SW_UI.valCompare(this);'; input.attr('onblur', val); input.attr('onkeyup', val); input.attr('onfocus', val); }, setTooltips: function () { // Tooltips $('[rel="tooltip"]').tooltip(); }, showReceipt: function (id) { this.setLoading(); this.runDialog(SW_Router.go("SWReceiptsView", "getReceiptUI", [id]), "", "receiptUI"); }, inViewport: function (elem) { var el = document.getElementById($(elem).attr("id")); var r, html; if (!el || 1 !== el.nodeType) { return false; } html = document.documentElement; r = el.getBoundingClientRect(); return (!!r && r.bottom >= 0 && r.right >= 0 && r.top <= html.clientHeight && r.left <= html.clientWidth ); }, // get the screen height getScreenHeight: function (offset) { if (offset == undefined) offset = 0; return (document.documentElement.clientHeight + offset); }, barcodeFieldFocus: function () { if ($("#barcodeinput").length > 0) $("#barcodeinput").focus(); }, alertInDiv: function (text, div, type) { $("#" + div).html(''); $("#" + div).show(); clearTimeout(this.timerID); this.timerID = setTimeout(function () { $("#" + div).hide(); }, 15000); }, reloadPage: function () { location.reload(); }, logoff: function () { Lockr.rm("logindata"); window.location = SW_Router.go("Medewerker", "logout"); }, // are we in PWA mode? i.e. on the homescreen of an android of iphone device? isPWA: function () { // ipad if (window.navigator.standalone == true) { return true; //android } else if (window.matchMedia('(display-mode: standalone)').matches === true) { return true; } if (Lockr.get('isPWA') == "1") return true; return false; }, forcePWA: function (off) { off = off ?? 1; Lockr.set('isPWA', off); }, // start the helppoup helpPopup: function (id, showall, callback, dismiss, fullscreen) { SW_Help.helpPopup(id, showall, callback, dismiss, fullscreen); }, marketingPopup: function (id) { $("#sw_help_popup").remove(); SW_UI.blockUI(); $.post(SW_Router.go("SWHelp", "helppopup", [id, "marketing"]), function (data) { SW_UI.unblockUI(); if (data.dialog != "") { $("body").append(data.dialog); $("#sw_help_popup").modal(); $('#sw_help_popup').on('hidden.bs.modal', function () { $("#sw_help_popup").find(".sw_video").remove(); }) } }, 'json').fail(function (response) { SW_UI.unblockUI(); SW_UI.alert("Couldn't find item!"); }); }, loginHelpPopup: function (id) { $("#sw_help_popup").remove(); SW_UI.blockUI(); $.post(SW_Router.go("SWHelp", "helppopup", [id, "small"]), function (data) { SW_UI.unblockUI(); if (data.dialog != "") { $("body").append(data.dialog); $("#sw_help_popup").modal(); $('#sw_help_popup').on('hidden.bs.modal', function () { $("#sw_help_popup").find(".sw_video").remove(); }) } }, 'json'); }, loginHelpPopupHide: function () { $("#sw_help_popup").modal('hide'); }, helpPage: function (id) { window.location = SW_Router.go("SWHelp", "index", [id, "item_id"]); }, // handle enter key processing in fields enterHandler: function (event, callback) { if (event.key == "Enter") { if (callback && typeof (callback) === "function") { callback(); } return false; } return true; }, // get the value of a update configUpdaterGetValue: function (inputs) { var data = []; $(inputs).each(function (index) { data[index] = {}; item = $(inputs)[index]; if ($(item).hasClass("sw_form_block_title_collapser")) { if ($(item).hasClass("sw_form_block_title_open")) data[index].value = 1; else data[index].value = 0; } else { data[index].value = $(item).val(); } if ($(item).attr("type") == "checkbox") { data[index].value = 0; if ($(item).is(':checked')) data[index].value = 1; } if ($(item).prop('nodeName') == "BUTTON") data[index].value = "pressed"; data[index].id = $(item).attr("data-sw-id"); data[index].hash = $(item).attr("data-sw-hash"); data[index].element_id = $(item).attr("id"); data[index].element_name = $(item).attr("name"); data[index].element_parent_form = $(item).closest("form").attr("id"); }); return data; }, //generic configuration update, gets called from forms / grid configUpdate: function (inputs, callback) { var data = {"items": SW_UI.configUpdaterGetValue(inputs)}; // remove all errors $(inputs).each(function (i) { $($(inputs)[i]).closest(".form-group").removeClass('has-error'); $($(inputs)[i]).closest(".form-group").find(".invalid-feedback").remove(); }); $.post(SW_Router.go(this.configUpdateController, "directUpdateMulti"), data, function (data) { if (typeof (SW_UI_configUpdateCallback) === "function") { SW_UI_configUpdateCallback(data); } // we're ok if (data.status == "ok") { $(data.additional).each(function (i) { var cur = data.additional[i]; var t = cur["element_id"]; // and select the item var item = $("#" + t); if (cur.status == "error") { $(item).closest(".form-group").addClass('has-error'); $(item).closest(".form-group").append('
' + cur.extended + '
'); } if (item[0].hasAttribute("callback_after_update")) eval($(item).attr("callback_after_update")); }); } if (callback && typeof (callback) === "function") { callback(data); } }, 'json'); // update the shadow items and dismissboxes $(inputs).each(function (i) { var item = $(inputs)[i]; var item_id = $(item).attr("id"); $("input[data-sw-shadow='" + item_id + "']").each(function (index) { $(this).val($(item).val()); SW_UI.configUpdate(this); }); // and sync the input boxes if ($(item).hasClass("sw_dismissbox")) { $(item).closest("div").closest(".sw_dismissbox").hide(); } ; }); }, // update configuration, hash comes from json_encoded $this->SWPanelCompiler->paramDirectEmployee, of when made paramDirect configUpdateJSon: function (hash, value) { var data = {}; data.id = hash.id; data.value = value data.hash = hash.hash; $.post(SW_Router.go(this.configUpdateController, "directUpdate"), data, function (data) { if (typeof (SW_UI_configUpdateCallback) === "function") { SW_UI_configUpdateCallback(data); } }, 'json'); }, //generic configuration read configRead: function (item) { var data = {}; data.id = $(item).attr("data-sw-id"); data.hash = $(item).attr("data-sw-hash"); $.post(SW_Router.go(this.configUpdateController, "directRead"), data, function (data) { $(item).val(data.additional.value); }, 'json'); }, jumpTo: function (url, newwindow) { newwindow = newwindow ?? true; if (newwindow == true) window.open(url); else window.location = url; }, //set the barcode scanned module name scannerSetModuleName: function (name) { gScannerModuleName = name + " : "; $('#barcodeinput').attr('placeholder', gScannerModuleName + 'Scan een barcode'); }, //returns true if IOS device, else it returns false isIos: function () { var ios = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; if (ios == false) { if (this.isIpad() == true) return true; } return ios; }, isAndroid: function () { return /(android)/i.test(navigator.userAgent); }, // uses a media query to determine mobile device (actually whether it is a device with a small screen but ok) isMobileDevice: function () { var result = window.matchMedia("(max-width: 768px)"); return result.matches; }, isIpad: function () { // ipados detection if (navigator.maxTouchPoints != undefined) { if (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1) { return true; } } return /iPad/.test(navigator.userAgent) && !window.MSStream; }, tilesSave: function () { var tiles_visible = {}; var itemIds = gTileGrid.getItems().map(function (item) { if (item.isVisible()) tiles_visible[$(item.getElement()).attr("id")] = "1" }); $.post(SW_Router.go("Dashboard", "Save"), tiles_visible, function (data) { }, 'json'); }, // close tile tileClose: function (id, save) { save = save ?? true; if ($("#" + id).hasClass("sw_tile_always")) return; $("li[name='" + id + "']").show(); gTileGrid.hide([$("#" + id)[0]], {instant: true}); // also stop video when there is video var id = $("#" + id + " .video-js").attr("id"); if (id !== undefined) { var player = videojs(id); player = player.pause(); } if (save) this.tilesSave(); }, tileUp: function (id) { if ($("#" + id).hasClass("sw_tile_always")) return; gTileGrid.synchronize(); var number = $(".muuri-item").index($("#" + id)); var next = $("#" + id).prevAll(".muuri-item-shown")[0]; var nextnumber = $(".muuri-item").index($("#" + $(next).attr("id"))); if (nextnumber == -1) return; // nothing to do here if (number == 0) return; gTileGrid.move(number, nextnumber, {action: 'swap'}); gTileGrid.synchronize(); this.tilesSave(); }, tileDown: function (id) { if ($("#" + id).hasClass("sw_tile_always")) return; gTileGrid.synchronize(); var number = $(".muuri-item").index($("#" + id)); var next = $("#" + id).nextAll(".muuri-item-shown")[0]; var nextnumber = $(".muuri-item").index($("#" + $(next).attr("id"))); if (nextnumber == -1) return; if (number > $(".muuri-item").length) return; gTileGrid.move(number, nextnumber, {action: 'swap'}); gTileGrid.synchronize(); this.tilesSave(); }, tileShow: function (id, save, movetofront) { if (id == "_show_all_") return this.tilesShowAll(); gTileGrid.show([$("#" + id)[0]]); // move to 1st widget if ((movetofront == undefined) || (movetofront == true)) { gTileGrid.move($("#" + id)[0], 0); } $("li[name='" + id + "']").hide(); // resetup a possible graph if ($("#" + id).find(".sw-chart").attr("data-sw-chartname") != undefined) eval($("#" + id).find(".sw-chart").attr("data-sw-chartname") + '.render()'); if ((save == undefined) || (save == true)) { this.tilesSave(); } }, openSupplies: function () { window.open("https://www.swretail.nl/supplies"); }, tilesShowAll: function () { $(".muuri-item").each(function (number, item) { SW_UI.tileShow($(item).attr("id"), false, false); }); $(".sw_tile_always").each(function (ind, elem) { gTileGrid.move(elem, 0); }); SW_UI.tilesSave(); }, // puts an alert above the where element // when no parameter given the alert is removed inlineAlert: function (message, where) { $(".sw_ui_inline_alert").remove(); if (message == undefined) return; var html = '
' + message + '
'; $(html).insertBefore(where); }, tileSync: function () { var activeItems = gTileGrid.getItems(); $.each(activeItems, function (index, item) { if (item.isVisible()) { $("li[name='" + $(item.getElement()).attr("id") + "']").hide(); } else $("li[name='" + $(item.getElement()).attr("id") + "']").show(); }); }, select2: function (selector) { $(selector).select2({"containerCssClass": "form-control", "width": "resolve"}); }, select2_selectall: function (item) { var selector = $(item).attr("selector"); $(selector + " >option").prop("selected", "selected") $(selector).trigger("change"); }, select2_deselectall: function (item) { var selector = $(item).attr("selector"); $(selector + " > option").prop("selected", false); $(selector).trigger("change"); }, select2setval: function (selector, val) { $(selector).val(val).trigger('change.select2'); }, click: function (element) { if ($(element).attr("target") == undefined || $(element).attr("target") == "" || $(element).attr("target") == "_self") { //get the destination of the link clicked var dest = $(element).attr("href"); //if the destination is an absolute url, ignore it if (!dest.match(/^http(s?)/g)) { //prevent default behavior (opening safari) if (typeof (event) != "undefined") event.preventDefault(); //update location of the web app console.log(dest); self.location = dest; } } ; }, resizeToWindow: function (id, offsetbottom) { SW_UI.resizerOffsetBottom = offsetbottom; SW_UI.resizerID = id; var height = $(window).height(); var rect = document.getElementById(id).getBoundingClientRect(); var remaining = height - rect.top; remaining = remaining - offsetbottom; $("#" + id).height(remaining + "px"); if (SW_UI.resizerInit == 0) { SW_UI.resizerInit = 1; $(window).resize(function () { SW_UI.resizeToWindow(SW_UI.resizerID, SW_UI.resizerOffsetBottom); }); } }, helpRateItem: function (item, rating) { SW_UI.blockUI(); $.post(SW_Router.go("SWHelp", "rateItem", [item, rating]), function (data) { SW_UI.helpRatingsLoad(); SW_UI.unblockUI(); }); }, helpRatingsLoad: function () { $.post(SW_Router.go("SWHelp", "rateItemGet"), function (data) { var my = data.additional; my.forEach(function (entry) { $("[data-item='" + entry.item + "'] [data-state='up']").removeClass("btn-info"); $("[data-item='" + entry.item + "'] [data-state='down']").removeClass("btn-info"); if (entry.rating == "thumb_up") $("[data-item='" + entry.item + "'] [data-state='up'] ").addClass("btn-info"); if (entry.rating == "thumb_down") $("[data-item='" + entry.item + "'] [data-state='down'] ").addClass("btn-info"); }); }, 'json'); }, expt_waiting_start: function (selector) { selector = selector ?? "div.modal-body:visible"; tmp_html = $(selector).html(); $(selector).empty(); $(selector).html('
'); }, expt_waiting_end: function (selector) { selector = selector ?? "div.modal-body:visible"; $(selector).empty(); $(selector).html(tmp_html); }, bottomToID: function (id, bottom, offsetbottom) { var height = $("#" + bottom).height(); $("#" + bottom).height("1px"); ; var torect = document.getElementById(id).getBoundingClientRect(); var rect = document.getElementById(bottom).getBoundingClientRect(); var parentheight = torect.bottom - torect.top; var posinparent = rect.top - torect.top; console.log("pos in parent = " + posinparent); console.log("parentheight = " + parentheight); $("#" + bottom).height((parentheight - 20) + "px"); }, pwaSetupAutostart: function (module) { Lockr.set("autoStart", module); }, // setup specific styles for swretail in conjuntion with pwa etc setupDynamicCSS: function (noscrolling, withbackground) { var sheet = document.createElement('style') if (this.isIpad()) { $(sheet).append(".sw_notontablet {display:none}"); } if (this.isPWA()) { $(sheet).append(".sw_notonpwa {display:none !important}"); $(sheet).append("@media only screen and (max-width:768px) { " + ".sw_notonpwa_menu {display:none !important} " + ".sw_notonpwa_small {display:none !important} " + ".panel-body {padding:0px} " + "} "); $(sheet).append("@media only screen and (max-width:768px) { .sw_notonpwa_small {display:none !important} } "); //$(sheet).append( "@media only screen and (max-width:768px) { .sw_notonpwa_small_menu {display:none !important} } "); $(sheet).append("@media only screen and (min-width:768px) { .sw_onpwa_small {display:none !important} } "); if (withbackground != undefined) if (withbackground == true) { $(sheet).append(' body {background:#ffffff}'); } if (noscrolling == true) $(sheet).append(' body {position:fixed;width:100%}'); } else { $(sheet).append(".sw_onpwa {display:none !important}"); $(sheet).append("sw_onpwa_small {display:none !important} "); } // maximize screen real-estate on small screens $(sheet).append("@media only screen and (max-width { .panel-body {padding:0px} }"); $(sheet).append(' #sw_totals {box-shadow:none}'); document.body.appendChild(sheet); }, openWebshop: function () { window.open(SW_Router.go("MySWRetail", "gotoWebshop"), '_blank'); }, addSerialnumber: function () { $("#sw_modal_add_serialnumber").modal(); swSerialnumber.init(); }, filterNonPrintableCharacters: function (input) { if (input == null || typeof input != 'string') return false; return input.replace(/[\x00-\x09\x14-\x1F\x7F\xA0]/g, ''); } } // catch these to handle modals $(document).on("show.bs.modal", function (e) { // lotta code for preventing the padding right on the navbar if ($(document).height() > $(window).height()) { $("body").addClass("modal_has_underlaying_scrollbar"); } SW_UI.hideTooltips(); SW_UI.OverlayHook("show"); }); // catch these to handle modals $(document).on("hidden.bs.modal", function (e) { $("body").removeClass("modal_has_underlaying_scrollbar"); SW_UI.OverlayHook("hide"); if (SW_UI.inViewport($("#barcodeinput"))) SW_UI.barcodeFieldFocus(); var tgt = $(e.target); if (tgt.attr("data-sw-onclose") != undefined) { eval(tgt.attr("data-sw-onclose")); } SW_UI.hideTooltips(); }); // Hide overlays when menus shown. However do not hide overlays on dropdows that have class keepoverlay. This is needed so that the dropdownmenus in a floating block do not cause the floating block to disappear $(document).on("shown.bs.dropdown", function (e) { if (!$(e.target).hasClass("keepoverlay")) SW_UI.OverlayHook("show"); }); $(document).on("hide.bs.dropdown", function (e) { if (!$(e.target).hasClass("keepoverlay")) { SW_UI.OverlayHook("hide"); } }); // startup function $(function () { bootbox.setDefaults({ locale: SW_Lang.getCurrent() }); // Check for mobile device. var isMobile = false; if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Opera Mobile|Kindle|Windows Phone|PSP|AvantGo|Atomic Web Browser|Blazer|Chrome Mobile|Dolphin|Dolfin|Doris|GO Browser|Jasmine|MicroB|Mobile Firefox|Mobile Safari|Mobile Silk|Motorola Internet Browser|NetFront|NineSky|Nokia Web Browser|Obigo|Openwave Mobile Browser|Palm Pre web browser|Polaris|PS Vita browser|Puffin|QQbrowser|SEMC Browser|Skyfire|Tear|TeaShark|UC Browser|uZard Web|wOSBrowser|Yandex.Browser mobile/i.test(navigator.userAgent)) isMobile = true; // Tooltips if (!isMobile) { // Hide tooltip for mobile devices. $('#sidebar-toggle').tooltip({placement: 'bottom'}); $('[rel="tooltip"]').tooltip(); } // Toggle sidebar if ($('#sidebar-toggle').length != 0) { $('#sidebar-toggle').click(toggleSidebar); $('.main-header:not(.no-click) ').click(toggleSidebar); } }); // some selection api changes for the grids $.fn.dataTable.Api.register('SW_Selected()', function (row) { tableId = $(row).closest("table").attr("id"); if (tableId === undefined) return; url = $("#" + tableId).attr("myurl"); id = $(row).attr("id"); var item = {'parent_ids': id, 'table': tableId}; $.post(url + "selectRow", item, function (data) { if ($(row).hasClass('active')) { $(row).removeClass('active'); } else { $(row).addClass('active'); } }); }); $.fn.dataTable.Api.register('SW_SelectedCount()', function (callback) { var thetable = $(this.table().node()); url = thetable.attr("myurl"); $.post(url + "countSelection/" + thetable.attr("id"), function (data) { if (callback && typeof (callback) === "function") { callback(data); } }, 'json'); }); $.fn.dataTable.Api.register('SW_SelectedClear()', function (callback) { var thetable = $(this.table().node()); url = thetable.attr("myurl"); $.post(url + "clearSelection/" + thetable.attr("id"), function (data) { if (callback && typeof (callback) === "function") { callback(data); } }, 'json'); }); $.fn.dataTable.Api.register('SW_getColumnIndex()', function (tosearchfor) { var colidx = 0; var colfound = -1; this.columns().every(function () { var data = this.data(); if ($(this.header()).attr("data-sw-binding") == tosearchfor) { colfound = colidx; } colidx++; }); return colfound; }); // pressing enter should not proceed to the next input box var gEnterBlocking = true; $(document).on('keypress', 'input', function (e) { if ((gEnterBlocking == true)) { var target = $(e.target); if (e.keyCode == 13) { e.preventDefault(); if (target.attr("sw-enter-next")) eval(target.attr("sw-enter-next")); $.tabNext(); } } ; }); // stay in brwoser always. needed for ios, links must be loaded via javascript otherwise app opens the webbrowser ;(function ($) { //extend the jQuery object, adding $.stayInWebApp() as a function $.extend({ stayInWebApp: function (selector) { //detect iOS full screen mode if (("standalone" in window.navigator) && window.navigator.standalone) { //if the selector is empty, default to all links if (!selector) { selector = 'a'; } //bind to the click event of all specified elements $("body").delegate(selector, "click", function (event) { //only stay in web app for links that are set to _self (or not set) and do not have an onclick if ($(this).attr("onclick") == "") if ($(this).attr("target") == undefined || $(this).attr("target") == "" || $(this).attr("target") == "_self") { //get the destination of the link clicked var dest = $(this).attr("href"); //if the destination is an absolute url, ignore it if (!dest.match(/^http(s?)/g)) { //prevent default behavior (opening safari) event.preventDefault(); //update location of the web app self.location = dest; } } }); } } //end stayInWebApp func }); })(jQuery); window.onpopstate = function (event) { console.log("popstate"); if (event.state) { console.log(event.state.url); } return false; }; // OSD keyboard handling // for normal processing loop trhough the $(document).keypress(function(e) { sw_keyb.processKeypress(e);}); just needed in cases where the is no barcodehandler like the swreceiptsview var sw_keyb = { gGotFocus: null, gTimer: null, gShift: false, fKeys: { "112": "F1", "113": "F2", "114": "F3", "115": "F4", "116": "F5", "117": "F6", "118": "F7", "119": "F8", "120": "F9", "121": "F10" }, keyLookup: function (keycode) { if (this.fKeys[keycode] != undefined) { return this.fKeys[keycode]; } return undefined; }, genericPadSetFocus: function (item) { this.gGotFocus = "#" + $(item).attr("id"); item.onblur = function (event) { sw_keyb.onBlur(this, event) } // clear out on doubleclick item.ondblclick = function () { $(item).val(""); } }, // set data-sw-onchange to handle a notification when clicked onBlur: function (input, event) { var cur = $(event.relatedTarget); if (cur.hasClass("sw_generic_pad")) return true; if (!cur.hasClass("sw_generic_pad")) { if ($(input).attr("data-sw-onchange")) { var func = $(input).attr("data-sw-onchange") + "($('#" + $(input).attr("id") + "'))"; eval(func); } else { $(input).trigger("onchange"); } } ; if (!cur.hasClass("sw_want_keyinput")) { this.gGotFocus = null; } }, // called when input has changed, can run a generic funtoin from data-sw-oninput attribute onChange: function (input) { if ($(input).attr("data-sw-oninput")) { var func = $(input).attr("data-sw-oninput") + "($('#" + $(input).attr("id") + "'))"; eval(func); } return; }, keyDownHandler: function (e) { // should event bubble return true; }, processBackspace: function (e) { }, processKeypress: function (e) { }, // load the buttons with their values setItems: function () { $(".sgp_shift").each(function (index) { var val = $(this).attr("sw_data"); if (sw_keyb.gShift == false) val = val.charAt(0); else val = val.charAt(1); $(this).html(val); }); }, // items si the first input that will be selected. genericPadClick: function (e, items) { var val = $(e).attr('sw_data'); if (items != undefined) { var t = items.split(";"); var fc = t[0]; if (this.gGotFocus == null) this.gGotFocus = fc; } var target = this.gGotFocus; //apptmp console.log("genericPadClick Target = "+target); if (val !== 'undefined') { switch (val) { case 'SHIFT': if (this.gShift == true) this.gShift = false; else this.gShift = true; this.setItems(); break; case 'BS': var [astart, aend] = [$(target)[0].selectionStart, $(target)[0].selectionEnd]; // when no content do nothing if (astart == 0) return; $(target)[0].setRangeText("", astart - 1, aend, 'end'); break; // normal key default: if (this.gShift == false) val = val.charAt(0); else { val = val.charAt(1); } const [start, end] = [$(target)[0].selectionStart, $(target)[0].selectionEnd]; $(target)[0].setRangeText(val, start, end, 'end'); break; } } this.onChange(target); } } // help wrapper for clean help handling var SW_Help = { // email a topic to indicated addresses emailTopic: function (item_id) { $.get(SW_Router.go("Configuration", "getAllAdministrators"), function (data) { var url = SW_Router.go("SWHelp", "mailHelpText", [item_id]); bootbox.prompt({ title: __("Geef email adressen (; gescheiden)"), value: data.additional, callback: function (emailadress) { if (emailadress !== null) { SW_UI.blockUI(); url = url + "?to_address=" + encodeURI(emailadress); $.ajax({ url: url, }).done(function (data) { SW_UI.unblockUI(); SW_UI.alert(__("E-Mail verstuurd!")); }); } } }); }, 'json'); }, // same as topic but we can also specify the file emailHelpChapter: function (file, item_id) { $.get(SW_Router.go("Configuration", "getAllAdministrators"), function (data) { var url = SW_Router.go("SWHelp", "mailHelpChapter", [file, item_id]); bootbox.prompt({ title: __("Geef e-mail adressen (; gescheiden)"), value: data.additional, callback: function (emailadress) { if (emailadress !== null) { SW_UI.blockUI(); url = url + "?to_address=" + encodeURI(emailadress); $.ajax({ url: url, }).done(function (data) { SW_UI.unblockUI(); SW_UI.alert(__("E-Mail verstuurd!")); }); } } }); }, 'json'); }, // start the helppoup helpPopup: function (id, showall, callback, dismiss, fullscreen) { fullscreen = fullscreen ?? true; $("#sw_help_popup").remove(); SW_UI.blockUI(); var collapsed = 1; if (showall != undefined) if (showall == true) collapsed = 0; $.post(SW_Router.go("SWHelp", "helppopup", [id, "normal", collapsed, dismiss]), function (data) { SW_UI.unblockUI(); if (data.dismissed == 1) { if (callback != undefined) eval(callback + "()"); return; } if (data.dialog != "") { $("body").append(data.dialog); $("#sw_help_popup").attr("callback", callback); // some nice ballpark width $("#sw_help_jumper").select2({'width': ' 100%'}); $("#sw_help_popup").modal(); $('#sw_help_popup').off('hidden.bs.modal'); if (fullscreen == false) $("#sw_help_popup").addClass("sw_do_not_fullsize"); $('#sw_help_popup').on('hidden.bs.modal', function () { $("#sw_help_popup").find(".sw_video").remove(); if ($("#sw_help_popup").attr("callback") != "") if ($("#sw_help_popup").attr("callback") != undefined) eval($("#sw_help_popup").attr("callback") + "()"); }); } }, 'json').fail(function (response) { SW_UI.unblockUI(); SW_UI.alert("Couldn't find helpitem!"); }); }, } // caching engine for SW-Retail for in the browser. wrapper around lockr var SW_Cache = { prefix: 'sw_cache_', read: function (tag) { return Lockr.get(this.prefix + "_" + tag); }, write: function (tag, value) { Lockr.set(this.prefix + "_" + tag, value); }, // read user specific cache read_user: function (tag) { return Lockr.get(this.prefix + "_uid" + SW_RoutingData.currentUserID + "_" + tag + "_" + tag); }, // write user specific cache write_user: function (tag, value) { Lockr.set(this.prefix + "_uid" + SW_RoutingData.currentUserID + "_" + tag, value); }, // set timestamp setts: function (ts) { Lockr.set(this.prefix + "_timestamp", ts); }, getts: function () { return Lockr.get(this.prefix + "_timestamp"); }, clear: function (prefix) { var pfx = this.prefix; if (prefix != undefined) pfx = this.prefix + "_" + prefix; for (var key in localStorage) { if (key.beginsWith(pfx)) { Lockr.rm(key); } } } } /** SW_Router.go(controller,action,[array with parameters] ) SW_Router.go(action) -> to current controller and action SW_Router.go(action,[array with parameters]) -> to current controller and action SW_Router.currentURL -> get a link to the current URL (except options uses SWRoutingData for generating urls */ var SW_Router = { getVersion: function () { return SW_RoutingData.currentVersion; }, // get the base url to the app getBaseURL: function (options, version) { var addversion = ""; if (version == undefined) version = false; if (version == true) addversion = "?version=" + SW_RoutingData.currentVersion; if (options != undefined) { if (Array.isArray(options)) { options.forEach(function (element, index) { options[index] = encodeURI(element); }); var tmp = options.join("/"); return SW_RoutingData.baseURL + options + addversion; } else return SW_RoutingData.baseURL + options + addversion; } return SW_RoutingData.baseURL + addversion; }, getController: function () { return SW_RoutingData.currentController; }, getAction: function () { return SW_RoutingData.currentAction; }, currentURL: function () { return SW_RoutingData.baseURL + SW_RoutingData.currentController + "/" + SW_RoutingData.currentAction; }, // options should be an array of extra url parameters go: function (controller, action, options, random) { if (controller == undefined) return SW_RoutingData.baseURL + SW_RoutingData.currentController; // shift right all params if ((Array.isArray(action)) || (action == undefined)) { options = action; action = controller; controller = SW_RoutingData.currentController; } if (action == "/") action = ""; else action = "/" + action; if (options == undefined) return SW_RoutingData.baseURL + controller + action; if (random != undefined) random = "?" + Math.random(); else random = ""; if (Array.isArray(options)) { options.forEach(function (element, index) { options[index] = encodeURI(element); }); var tmp = options.join("/"); return SW_RoutingData.baseURL + controller + action + "/" + tmp + random; } return SW_RoutingData.baseURL + controller + action + "/" + tmp + random; }, jumpModule: function (module) { SW_UI.blockUI(); switch (module) { case "cashregister": window.location = SW_Router.go("TbltMain", "index", [SW_UI.systemidGet()]); break; case "orderpicker": window.location = SW_Router.go("SWBackofficeOP", "/"); break; case "retourpicker": window.location = SW_Router.go("SWBackofficeRP", "/"); break; case 'artomatic': window.location = SW_Router.go("ArticleInfo", "artomatic"); break; case "dashboard": window.location = SW_Router.go("Dashboard", "/"); break; case "swinventory": window.location = SW_Router.go("SWInventory", "/"); break; case "scanner": window.location = SW_Router.go("scan", "/"); break; default: window.location = SW_Router.go("Dashboard", "Show"); break; } }, getURLParameters: function () { return window.location.search; }, //helper for creating a nice object helper_paramsToObject: function (entries) { const result = {} for (const [key, value] of entries) { // each 'entry' is a [key, value] tupple result[key] = value; } return result; }, // get the url parameters as an object getURLParamsObject: function () { let params = new URLSearchParams(window.location.search); return this.helper_paramsToObject(params); } }; var gRemoveAllScannerInputs = true; const audioManager = new AudioPlayer(); function loadAudio() { audioManager.load('beep_ok', SW_Router.getBaseURL(["beep_ok.wav"], true)); audioManager.load('beep_error', SW_Router.getBaseURL(["boing_x.wav"], true)); audioManager.load('beep_warning', SW_Router.getBaseURL(["beep_warning.wav"], true)); audioManager.load('code_ok', SW_Router.getBaseURL(["scan_ok.wav"], true)); } // startup function $(function () { // setup specific styles for swretail in conjuntion with pwa etc SW_UI.setupDynamicCSS(); // add a custom header to all ajax requests $.ajaxSetup({ beforeSend: function (xhr) { xhr.setRequestHeader('x-swretail-webclient', 'true'); } }); var version=SW_Cache.read("audio_version"); if (version!=SW_Router.getVersion()) { console.log("audio version reset"); SW_Cache.write("audio_version",SW_Router.getVersion()); audioManager.flush().then(() => { loadAudio(); }).catch(error => { console.error('Error clearing the database:', error); }); } else loadAudio(); // scroll to a div click handler $(document).on("click", ".sw_scroller", function (e) { e.preventDefault(); // maybe there is an offset in the parent diff var offset = 200; var scroll_offset = $(this).closest("ul").attr("data-scroll-offset"); if (scroll_offset != undefined) offset = offset + parseInt(scroll_offset); SW_UI.scrollTo($(this).attr("name"), offset); }); // readonly checkboxes should not do anything $(document).on("click", ".form-switch input.readonly", function (data) { return false; }) // pull to refresh init const ptr = PullToRefresh.init({ mainElement: 'body', distThreshold: 180, distMax: 200, instructionsPullToRefresh: __("Omlaag trekken om te verversen."), instructionsReleaseToRefresh: __("Loslaten om te verversen."), instructionsRefreshing: __("Verversen."), onRefresh() { SW_UI.blockUI(); window.location.reload(); } }); }); // experimental, does nothing var gridNav = { getCurPage: function (grid) { console.log($("#" + grid + "_paginate .pagination li.paginate_button.active a").attr("data-dt-idx")); }, nextPage: function () { }, } /// this is what?, going to remove this /*document.onkeydown = function (e) { e = e || window.event; var keyCode = e.keyCode || e.which; var arrow = {left: 37, up: 38, right: 39, down: 40 }; if (e.ctrlKey) { switch (keyCode) { case arrow.left: //... handle Ctrl-LeftArrow break; //... } } }; */ /////////////////////////////////////////////////// // autocomplete control class Autocomplete { // data can either be a json string, or an url to a dataprovider // the url gets called with ?query= or /clear for clearing data constructor(inputId, dropdownId, data) { this.input = document.getElementById(inputId); this.dropdown = document.getElementById(dropdownId); if (this.dropdown == undefined) { const dropdown = document.createElement('div'); dropdown.className = 'autocomplete-dropdown'; dropdown.id = 'autocomplete-dropdown-' + this.input.id; console.log(dropdown); var parent = this.input.parentNode; var nextelem = this.input.nextSibling; parent.insertBefore(dropdown, nextelem); this.dropdown = dropdown; //this.input.appendChild(dropdown); } if (Array.isArray(data)) { this.data = data; this.url = false; } else { this.data = false; this.url = data; } this.selectedIndex = -1; this.addClear = false; this.setupEventListeners(); } addClearButton() { this.addClear = true; } setupEventListeners() { // Event listener for changes in the input this.input.addEventListener('input', () => this.handleInput()); // Event listener to open the dropdown on the first click this.input.addEventListener('click', () => this.handleFirstClick()); // Event listener to close the dropdown when clicking outside the autocomplete box document.addEventListener('click', (event) => this.handleClickOutside(event)); // Prevent the input click event from propagating to the document click event this.input.addEventListener('click', (event) => event.stopPropagation()); // Event listener for keydown events this.input.addEventListener('keydown', (event) => this.handleKeydown(event)); } handleKeydown(event) { const dropdownItems = this.dropdown.querySelectorAll('.autocomplete-dropdown-item'); switch (event.key) { case 'ArrowDown': event.preventDefault(); this.navigateDropdown('down', dropdownItems); break; case 'ArrowUp': event.preventDefault(); this.navigateDropdown('up', dropdownItems); break; case 'Enter': event.preventDefault(); this.handleEnterKey(); break; } } navigateDropdown(direction, items) { if (items.length === 0) return; if (direction === 'down' && this.selectedIndex < items.length - 1) { this.selectedIndex++; } else if (direction === 'up' && this.selectedIndex > 0) { this.selectedIndex--; } this.updateSelectedClass(items); } handleEnterKey() { const selectedElement = this.dropdown.querySelector('.autocomplete-selected'); if (selectedElement) { this.input.value = selectedElement.childNodes[1].textContent; console.log(this.input.value); this.clearDropdown(); this.itemSelected(); } else { this.itemSelected(); } } updateSelectedClass(items) { // Remove the "selected" class from all items items.forEach((item, index) => { if (index === this.selectedIndex) { item.classList.add('autocomplete-selected'); } else { item.classList.remove('autocomplete-selected'); } }); } async handleInput() { const inputValue = this.input.value.toLowerCase(); // Clear previous results this.dropdown.innerHTML = ''; this.selectedIndex = -1; try { var data; // Fetch data from the server if (!this.data) { const response = await fetch(`${this.url}?query=${inputValue}`); data = await response.json(); } else { data = this.data; } this.dropdown.innerHTML = ''; // Create and append dropdown items data.forEach((item, index) => { const dropdownItem = document.createElement('div'); dropdownItem.className = 'autocomplete-dropdown-item'; dropdownItem.textContent = item; // Create delete button const deleteButton = document.createElement('span'); deleteButton.className = 'autocomplete-delete-button'; deleteButton.textContent = 'X'; deleteButton.addEventListener('click', (event) => { event.stopPropagation(); // Prevent dropdown from closing this.deleteDropdownItem(item); // Call deleteDropdownItem function dropdownItem.remove(); // Remove item from dropdown }); dropdownItem.prepend(deleteButton); // Add delete button to dropdown item dropdownItem.addEventListener('click', () => this.handleItemClick(item)); dropdownItem.addEventListener('mouseenter', () => this.handleItemMouseEnter(index)); this.dropdown.appendChild(dropdownItem); }); if (this.addClear) { const clearListButton = document.createElement('div'); clearListButton.className = 'clear-list-button'; clearListButton.textContent = __('Lijst wissen'); clearListButton.addEventListener('click', () => this.removeAutoCompleteData()); this.dropdown.appendChild(clearListButton); } // Show the dropdown this.dropdown.style.display = data.length ? 'block' : 'none'; // If there are no results, hide the dropdown and call SW_UI.OverlayHook("hide"); if (data.length === 0) this.clearDropdown(); if (this.isDropdownVisible()) SW_UI.OverlayHook("show"); } catch (error) { console.error('Error fetching data:', error); } } // and clear the data async removeAutoCompleteData() { this.clearDropdown(); const response = await fetch(`${this.url}/clear`); await response.json(); } handleItemMouseEnter(index) { // Update the selected index on mouse enter this.selectedIndex = index; const dropdownItems = this.dropdown.querySelectorAll('.autocomplete-dropdown-item'); this.updateSelectedClass(dropdownItems); } handleFirstClick() { if (this.isDropdownVisible()) return; // Show the dropdown on the first click this.handleInput(); // Show the dropdown //this.dropdown.style.display = 'block'; } isDropdownVisible() { // Check if the dropdown is currently visible return this.dropdown.style.display === 'block'; } handleClickOutside(event) { const isClickInside = this.input.contains(event.target) || this.dropdown.contains(event.target); if (!isClickInside) { // Clicked outside the autocomplete box, hide the dropdown if (this.isDropdownVisible()) this.clearDropdown(); } } handleItemClick(item) { // Set the selected item in the input field this.input.value = item; // Clear the dropdown this.clearDropdown(); this.itemSelected(); } async deleteDropdownItem(item) { console.log('Deleted item:', item); const response = await fetch(`${this.url}/remove?data=${item}`); data = await response.json(); // Add logic to delete item from your data source } clearDropdown() { // Hide the dropdown this.dropdown.style.display = 'none'; // Call SW_UI.OverlayHook("hide"); SW_UI.OverlayHook("hide"); } // Gets run when an item is selected. needs the data-onselect item to be filled itemSelected() { // Get the function name from the data-attribute const script = this.input.dataset.onselect; if (script != "") eval(script); } } // fancy textarea control class FancyTextarea { constructor(selector) { this.container = document.querySelector(selector); if (this.container) { this.textarea = this.container.querySelector('textarea'); this.button = this.container.querySelector('button'); this.initEvents(); this.autoGrow(); } } initEvents() { this.textarea.addEventListener('focus', () => this.container.classList.add('focused')); this.textarea.addEventListener('blur', () => this.container.classList.remove('focused')); this.textarea.addEventListener('input', () => this.autoGrow()); // verbetering zodat de clik op de caontiner ook leidt tot een focus this.container.addEventListener('click', () => this.focusTextarea()); this.textarea.addEventListener('keydown', (e) => this.handleEnterPress(e)); } focusTextarea() { this.textarea.focus(); } handleEnterPress(e) { if (e.key === 'Enter' || e.keyCode === 13) { e.preventDefault(); // Voorkom dat de Enter-toets een nieuwe regel invoegt this.button.click(); // Simuleer een klik op de knop } } autoGrow() { this.textarea.style.height = '18px'; // Reset de hoogte zodat deze kan krimpen bij verwijderen van tekst this.textarea.style.height = (this.textarea.scrollHeight) + 'px'; // Stel de hoogte in op de scrollHeight om alle tekst te passen } } // handle overlays efficiently // looks like the card manager but it is different var swOverlayer = { showOverlay: function (selector) { $(".sw-ui-overlayed").addClass("hide"); $(selector).removeClass("hide"); $(selector).addClass("sw-ui-overlayed"); }, hideOverlay: function () { $(".sw-ui-overlayed").addClass("hide"); } } /////////////////////////////////////////////////// // card manager for overlaying screens on top of each other var SW_CardMGT = { currentCard: "#sw_block_main", getCurrent: function () { return SW_CardMGT.currentCard; }, historyClear: function () { Lockr.set('cash_CardHistory', []); }, toggleInputs: function () { // init when not run if ($(this.currentCard).attr("data-sw-disabled") == undefined) $(this.currentCard).attr("data-sw-disabled", "false"); if ($(this.currentCard).attr("data-sw-disabled") == "false") { $.each($(this.currentCard + " input," + this.currentCard + " button," + this.currentCard + " select"), function (index, item) { if ($(item).prop("disabled")) $(item).attr("data-sw-disabled-was", "1"); $(item).prop("disabled", true) }); $(this.currentCard).attr("data-sw-disabled", "true"); } else { $(this.currentCard).attr("data-sw-disabled", "false"); $.each($(this.currentCard + " input," + this.currentCard + " button," + this.currentCard + " select"), function (index, item) { if ($(item).attr("data-sw-disabled-was") != "1") $(item).prop("disabled", false) }); } }, historyPush: function (cardid) { var history = Lockr.get('cash_CardHistory'); if (history == undefined) var history = []; history.push(cardid); if (history.length > 20) history = history.slice(-1); Lockr.set('cash_CardHistory', history); }, showCard: function (cardid) { if (cardid == undefined) cardid = this.currentCard; else { if (this.currentCard != cardid) this.historyPush(this.currentCard); this.currentCard = cardid; } $(cardid).addClass("sw_tablet_card"); $(cardid).removeClass("hidden"); if ($(cardid).attr("data-sw-card-additional-div") != "") $("#" + $(cardid).attr("data-sw-card-additional-div")).removeClass("hidden"); if ($(cardid).attr("data-sw-card-has-scanning") == "1") barcodescanningSwitch(true); if ($(cardid).attr("data-sw-card-set-state") != undefined) { $.post(SW_Router.go(SW_Router.getController(), "updateState"), getCommonRequestData({"state": $(cardid).attr("data-sw-card-set-state")}), function (data) { }, 'json'); } }, hideCard: function () { if ($(this.currentCard).attr("data-sw-card-has-scanning") == "1") barcodescanningSwitch(false); if ($(this.currentCard).attr("data-sw-card-additional-div") != "") $("#" + $(this.currentCard).attr("data-sw-card-additional-div")).addClass("hidden"); $(this.currentCard).addClass("hidden"); return this.currentCard; } }