/*!
 * Thorium Core functions for framework7 projects
 * Version 4.0 January, 2024
 * framework7 v8.x (https://framework7.io) MIT Licensed
 * Copyright 2018-2024 Thorium builder, All Rights Reserved.
 */

String.prototype.replaceAll = function (search, replacement) {
    var target = this;
    return target.replace(new RegExp(search, 'g'), replacement);
};
String.prototype.escapeSpecialChars = function () {
    return this.replace(/[\\]/g, '\\\\').replace(/[\/]/g, '\\/').replace(/[\b]/g, '\\b').replace(/[\f]/g, '\\f').replace(/[\n]/g, '\\n').replace(/[\r]/g, '\\r').replace(/[\t]/g, '\\t').replace(/[\"]/g, '\\"').replace(/\\'/g, "\\'");
};
var Base64 = {
    _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
    encode: function (e) {
        var t = "";
        var n, r, i, s, o, u, a;
        var f = 0;
        e = Base64._utf8_encode(e);
        while (f < e.length) {
            n = e.charCodeAt(f++);
            r = e.charCodeAt(f++);
            i = e.charCodeAt(f++);
            s = n >> 2;
            o = (n & 3) << 4 | r >> 4;
            u = (r & 15) << 2 | i >> 6;
            a = i & 63;
            if (isNaN(r)) {
                u = a = 64
            } else if (isNaN(i)) {
                a = 64
            }
            t = t + this._keyStr.charAt(s) + this._keyStr.charAt(o) + this._keyStr.charAt(u) + this._keyStr.charAt(a)
        }
        return t
    },
    decode: function (e) {
        var t = "";
        var n, r, i;
        var s, o, u, a;
        var f = 0;
        e = e.replace(/[^A-Za-z0-9+/=]/g, "");
        while (f < e.length) {
            s = this._keyStr.indexOf(e.charAt(f++));
            o = this._keyStr.indexOf(e.charAt(f++));
            u = this._keyStr.indexOf(e.charAt(f++));
            a = this._keyStr.indexOf(e.charAt(f++));
            n = s << 2 | o >> 4;
            r = (o & 15) << 4 | u >> 2;
            i = (u & 3) << 6 | a;
            t = t + String.fromCharCode(n);
            if (u != 64) {
                t = t + String.fromCharCode(r)
            }
            if (a != 64) {
                t = t + String.fromCharCode(i)
            }
        }
        t = Base64._utf8_decode(t);
        return t
    },
    _utf8_encode: function (e) {
        e = e.replace(/rn/g, "n");
        var t = "";
        for (var n = 0; n < e.length; n++) {
            var r = e.charCodeAt(n);
            if (r < 128) {
                t += String.fromCharCode(r)
            } else if (r > 127 && r < 2048) {
                t += String.fromCharCode(r >> 6 | 192);
                t += String.fromCharCode(r & 63 | 128)
            } else {
                t += String.fromCharCode(r >> 12 | 224);
                t += String.fromCharCode(r >> 6 & 63 | 128);
                t += String.fromCharCode(r & 63 | 128)
            }
        }
        return t
    },
    _utf8_decode: function (e) {
        var t = "";
        var n = 0;
        var r = c1 = c2 = 0;
        while (n < e.length) {
            r = e.charCodeAt(n);
            if (r < 128) {
                t += String.fromCharCode(r);
                n++
            } else if (r > 191 && r < 224) {
                c2 = e.charCodeAt(n + 1);
                t += String.fromCharCode((r & 31) << 6 | c2 & 63);
                n += 2
            } else {
                c2 = e.charCodeAt(n + 1);
                c3 = e.charCodeAt(n + 2);
                t += String.fromCharCode((r & 15) << 12 | (c2 & 63) << 6 | c3 & 63);
                n += 3
            }
        }
        return t
    }
};

function isDarkMode() {
    if (document.documentElement.classList.contains("dark")) {
        return "1";
    } else {
        return "0";
    }
}

var thoriumCorePlugin = {
    name: "Thorium Builder Core Plugin",
    bundleid: "com.thoriumbuilder.core",
    version: "4.0.0",
    isLoading: false,
    httpRoot: "",
    language: 'en',
    direction: 'ltr',
    currentPopover: null,
    currentBlob: null,
    emtyResultTemplate: '<p class="repeater-noresult">no results found</p>',
    app: {
        init: function () {
            document.addEventListener('deviceready', thoriumapi.logEvent(0, "[com.thoriumbuilder.core] App Loaded"));
            thoriumCorePlugin.app.showAppLoader(true);
            thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Initializing App..");
            if (kAutoUpdate == true) {
                thoriumCorePlugin.app.checkversion();
            }
            if (kMultilingual == true) {
                thoriumCorePlugin.translations.translate();
            }
            if (kOneSignalAppId.length > 0) {
                thoriumCorePlugin.app.initializeServiceWorker();
            }
            if ((thoriumapi.isLocal() == false) && (kDesktopAllowed == false) && (((app.device.desktop) || (!Framework7.support.touch)) || (!(app.device.android) && !(Framework7.device.ios)))) {
                $('#page').hide();
                thoriumCorePlugin.app.showAppLoader(false);
                thoriumCorePlugin.showToast('This App can only run on a Mobile device', false, 'center', 9999999);
            } else {
                if (app.device.desktop == true) {
                    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] App is running in Desktop mode");
                }
                if (Framework7.support.touch == true) {
                    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Touch support detected");
                }
                if (app.device.android == true) {
                    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Android Device detected");
                }
                if (app.device.ios == true) {
                    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] iOS Device detected");
                }
                if ((app.device.cordova == true) || (app.device.capacitor == true)) {
                    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] App is running in Native Hybrid mode");
                } else {
                    if (app.device.webView == true) {
                        thoriumapi.logEvent(0, "[com.thoriumbuilder.core] App is installed to Home Screen");
                    }
                }
                if (kHybridAppsLog == true) {
                    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Debug Log is activated");
                }

                if ((app.device.cordova == true) || (app.device.capacitor == true)) { //Native App
                    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] App is running in Native Hybrid mode, set DB host to [" + kRemoteHost + "]");
                    thoriumCorePlugin.httpRoot = kRemoteHost;
                } else if (thoriumapi.isLocal() == true) {
                    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] App is running in Local mode, set DB host to [" + kLocalRoot + "]");
                    thoriumCorePlugin.httpRoot = kLocalRoot;
                } else { //PWA
                    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] App is running in PWA mode, set DB host to empty");
                    thoriumCorePlugin.httpRoot = "";
                }

                if ((kForceInstall == true) && (app.device.webView == false) && (window.location.protocol != "file:") && (app.device.cordova == false) && ((app.device.ios) || (app.device.android))) {
                    thoriumCorePlugin.app.addToHomeScreen();
                }
                thoriumCorePlugin.app.setStyle();
                thoriumCorePlugin.swiper.init();
                thoriumCorePlugin.timepicker.init();
                thoriumCorePlugin.calendars.init();
                thoriumCorePlugin.texteditor.init();
                thoriumCorePlugin.forms.init();
                thoriumCorePlugin.initVirtualMediaLists();
                thoriumCorePlugin.initAllAutoComplete();
                thoriumCorePlugin.app.pullToRefreshHandle(null);
                thoriumCorePlugin.gauge.init();
                if (app.device.cordova == true) {
                    $('.webshare').hide();
                }
                setTimeout(function () {
                    thoriumCorePlugin.effects.init();
                }, 100);
                thoriumCorePlugin.app.initPlugins();
                thoriumapi.logEvent(0, "[com.thoriumbuilder.core] App initialized");
                app.emit('appInitialised');
            }
        },
        initPlugins: function() {
            thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Initializing Plugins");
            if (typeof jsonApiPlugin == "object") { jsonApiPlugin.initialize(); }
            if (typeof openStreetMapPlugin == "object") { openStreetMapPlugin.initialize(); }
            if (typeof nodeQrcode == "object") { nodeQrcode.initialize(); }
            if (typeof chartJS == "object") { chartJS.initialize(); }
            if (typeof qrcodeReader == "object") { qrcodeReader.initialize(); }
            if (typeof googleMapPlugin == "object") { googleMapPlugin.initialize();  }     
            if (typeof firebasePlugin == "object") { firebasePlugin.initialize(); }
            if (typeof firestorePlugin == "object") { firestorePlugin.initialize(); }
            if (typeof auth == "object") {  auth.initialize(); }            
        },
        setStyle: function () {
            if (kCustomColor) {
                thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Apply Custom Color " + kCustomColor);
                document.documentElement.style.setProperty('--f7-theme-color', kCustomColor);
            }
        },
        forceReload: function (ShowProgress) {
            //Clear cache
            var p=typeof caches;
            if (!(p) || (p == "undefined")) {
                thoriumapi.logEvent(1, "[com.thoriumbuilder.core] 'Caches' is onbly available with with https web sites");
                return;
            }
            if (ShowProgress === true) { app.dialog.progress("Updating App..."); }
            caches.open("thorium-cache")
                .then(function (cache) {
                    cache.delete("thorium-cache")
                        .then(function (response) {
                            thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Service Worker Cache Deleted [" + response + "]");
                        });
                })
            //Unregister ServiceWorker
            if (('serviceWorker' in navigator) && (window.location.protocol != "file:")) {
                if (navigator.serviceWorker.controller) {
                    navigator.serviceWorker.getRegistrations()
                        .then(function (registrations) {
                            for (let registration of registrations) { registration.unregister(); }
                        }
                        );
                    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] service worker unregistred");
                }
            }
            setTimeout(function () {
                try {
                    const form = document.createElement('form');
                    form.method = "POST";
                    form.action = location.href;
                    document.body.appendChild(form);
                    form.submit();
                    document.body.removeChild(form);
                } finally {
                    app.dialog.close();
                }
            }, 2000);

        },
        checkversion: function () {
            var currentversion = localStorage.getItem(app.id + ".version");
            //Get online version #
            const ms = Date.now();
            fetch("./version.json?dummy=" + ms, {
                method: 'get',
                headers: {
                    'Content-type': 'application/json'
                }
            })
                .then(
                    function (response) {
                        response.json()
                            .then(function (data) {
                                if ((data) && (data.version)) {
                                    if (parseInt(data.version) != parseInt(currentversion)) {
                                        //app.dialog.alert("reload "+data.version+" not "+currentversion);
                                        localStorage.setItem(app.id + ".version", data.version);
                                        thoriumCorePlugin.app.forceReload(true);
                                    }
                                }
                            })
                    })
        },
        initializeServiceWorker: function () {
            if (('serviceWorker' in navigator) && (window.location.protocol != "file:")) {
                if (navigator.serviceWorker.controller) {
                    thoriumapi.logEvent(0, '[com.thoriumbuilder.core] Thorium Service worker found');
                } else {
                    navigator.serviceWorker.register('OneSignalSDKWorker.js', {
                        scope: './'
                    })
                    .then(function (reg) {
                        thoriumapi.logEvent(0, '[com.thoriumbuilder.core] Thorium Service worker registered for scope:' + reg.scope);
                    })
                    .catch(function (err) {
                        thoriumapi.logEvent(2, "[com.thoriumbuilder.core] Error [" + err.message + "] While loading oneSignal Service Worker");
                        app.dialog.alert(err.message);
                    });
                }
            } else {
                thoriumapi.logEvent(1, '[com.thoriumbuilder.core] No Service Worker Support on this Device');
            }
            var OneSignal = window.OneSignal || [];
            OneSignal.push(function () {
                OneSignal.init({
                    appId: kOneSignalAppId,
                });
            });
            thoriumapi.logEvent(0, '[com.thoriumbuilder.core] One Signal Plugin Initialization with ID [' + kOneSignalAppId + "]");
        },
        pullToRefreshHandle: function (p) {
            var ptrContent = $('.ptr-content');
            var key;
            if (p) {
                key = p.detail.route.query.key || null;
            }
            ptrContent.on('ptr:refresh', function (e) {
                if (thoriumCorePlugin.isLoading == false) {
                    thoriumCorePlugin.isLoading = true;
                    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Call to 'Pull to Refresh'");
                    setTimeout(function () {
                        if  (e.target.classList.contains("page-content")) {
                            var fname=e.target.getAttribute("data-function");
                            if ((fname) && (fname.length>0)) {
                                var fn = window[fname];
                                thoriumapi.callFunctionByName(fn,null);
                            }
                        }
                        var o = e.target.getAttribute('data-ptr-repeater');
                        if (o) {
                            var tgt=document.getElementById(o);               
                            if ( (tgt) && (tgt.classList.contains("firebase-virtual-list-content")) ) {
                                tgt.setAttribute("data-loaded", 'false');
                                if (typeof firestorePlugin == "object") {
                                    firestorePlugin.firebase_initFirebaseVirtualLists(false, key);
                                }
                            }
                            if ( (tgt) && (tgt.classList.contains("json-virtual-list-content")) ) {
                                tgt.setAttribute("data-loaded", 'false');
                                if (typeof jsonApiPlugin == "object") {
                                    jsonApiPlugin.initAllRepeaters(key, true);
                                }
                            }
                            if ( (tgt) && (tgt.classList.contains("dbexpress-virtual-list-content")) ) {
                                tgt.setAttribute("data-loaded", 'false');
                                if (typeof dbExpressData == "object") {
                                    dbExpressData.initAllRepeaters(key);
                                }
                            }
                        }
                        ptrContent.removeClass('ptr-pull-up');
                        ptrContent.removeClass('ptr-transitioning');
                        ptrContent.removeClass('ptr-refreshing');
                        thoriumCorePlugin.isLoading = false;
                    }, 1000);

                }
            });
        },
        addToHomeScreen: function () {
            if ((kAddToHomeScreenIos == false) && (app.device.ios == true)) {
                return;
            }
            if ((kAddToHomeScreenMd == false) && (app.device.android == true)) {
                return;
            }
            var ahstx;
            if (app.device.ios == true) {
                ahstx = kAddToHomeScreenIosText;
                ahstx = ahstx.replaceAll("#ICON", "<i class=\"fa f7-icons\">square_arrow_up</i>");
            } else {
                ahstx = kAddToHomeScreenMdText;
                ahstx = ahstx.replaceAll("#ICON", "<span class=\"fa material-icons\">more_vert</span>");
            }
            var ct = '<div class="sheet-modal addtohomescreen">' +
                '<div class="sheet-modal-inner">' +
                '<p class="ahs-title font-weight-bold">' + kAddToHomeScreenTitle + '</p>' +
                '<p>' + ahstx + '</p>';
            if (kSkipButton == true) {
                ct = ct + '<p><a class="link sheet-close">' + kSkipButtonText + '</a></p>';
            }
            ct = ct + '<p class="ios-only"><i class="fa f7-icons">chevron_down</i></p>' +
                '</div>' +
                '</div>';
            var dynamicSheet = app.sheet.create({
                swipeToClose: false,
                backdrop: true,
                closeOnEscape: false,
                closeByBackdropClick: false,
                content: ct,
            });
            dynamicSheet.open(true);
        },
        resize: function () {
            var x = document.querySelectorAll(".virtual-list");
            for (var i = 0; i < x.length; i++) {
                var elt = x[i];
                var virtualList = app.virtualList.get(elt);
                if (virtualList) {
                    var setHeight = virtualList.params.height;
                    var firstElement = elt.querySelector("li");
                    if (firstElement) {
                        var eltHeight = firstElement.offsetHeight || 0;
                        if ((eltHeight > 0) && (eltHeight != setHeight)) {
                            //Reassign actual item height
                            virtualList.params.height = eltHeight;
                            virtualList.update();
                        }
                    }
                }
            }
        },
        showAppLoader: function (show) {
            var al = document.querySelector(".apploader");
            if (al) {
                if (show == true) {
                    al.style.display = "block";
                    app.preloader.showIn(al);
                }
                else {
                    al.style.display = "none";
                    app.preloader.hideIn(al);
                }
            }
        },
    },
    swiper: {
        init: function () {
            var x = document.querySelectorAll(".swiper");
            for (var i = 0; i < x.length; i++) {
                var elt = x[i];
                var swiper = elt.swiper;
                if (!swiper) {
                    const dir = elt.getAttribute("data-direction") || "horizontal";
                    const is = elt.getAttribute("data-initial-slide") || 0;
                    const dataloop = elt.getAttribute("data-loop") || true;
                    const dynamicbullets = (elt.getAttribute("data-swiper-dynamicbullets") == 'true') || false;
                    const effect = elt.getAttribute("data-effect") || "slide"; //'slide', 'fade', 'cube', 'coverflow', 'flip' or 'creative'
                    const pagtype = elt.getAttribute("data-swiper-type") || "bullets";
                    const spv = elt.getAttribute("data-slides-per-view") || 1;
                    const sb = elt.getAttribute("data-space-between") || 0;
                    const draggable = (elt.getAttribute("data-draggable") == "true");
                    swiper = new Swiper("#" + elt.id, {
                        effect: effect,
                        direction: dir,
                        initialSlide: is,
                        slidesPerView: spv,
                        spaceBetween: sb,
                        autoHeight: false,
                        preloadImages: true,
                        loop: dataloop,
                        pagination: {
                            el: '.swiper-pagination',
                            type: pagtype,
                            clickable: true,
                            dynamicBullets: dynamicbullets,
                            dynamicMainBullets: 5,
                        },
                        draggable: draggable,
                        simulateTouch: draggable,
                    });
                }
            }
        },
    },
    timepicker: {
        init: function () {
            thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Initializing Time Picker");
            var x = document.querySelectorAll("input[data-date-type='time']");
            for (var i = 0; i < x.length; i++) {
                thoriumCorePlugin.timepicker.setup(x[i].id);
            }
        },
        setup: function (id) {
            var status = document.getElementById(id).getAttribute("picker-initialized");
            if (status == 'true') {
                return;
            }
            var picker = app.picker.get('#' + id);
            if (!picker) {
                var values;
                if (kHours12 == false) {
                    values = [{
                        values: (function () {
                            var arr = [];
                            for (var i = 0; i <= 23; i++) {
                                arr.push(i < 10 ? '0' + i : i);
                            }
                            return arr;
                        })(),
                    },
                    {
                        divider: true,
                        content: ':'
                    },
                    {
                        values: (function () {
                            var arr = [];
                            for (var i = 0; i <= 59; i++) {
                                arr.push(i < 10 ? '0' + i : i);
                            }
                            return arr;
                        })(),
                    }
                    ];
                } else {
                    values = [{
                        values: (function () {
                            var arr = [];
                            for (var i = 0; i <= 11; i++) {
                                arr.push(i < 10 ? '0' + i : i);
                            }
                            return arr;
                        })(),
                    },
                    {
                        divider: true,
                        content: ':'
                    },
                    {
                        values: (function () {
                            var arr = [];
                            for (var i = 0; i <= 59; i++) {
                                arr.push(i < 10 ? '0' + i : i);
                            }
                            return arr;
                        })(),
                    },
                    {
                        divider: true,
                        content: ' '
                    },
                    {
                        values: (function () {
                            var arr = [];
                            arr.push('am');
                            arr.push('pm');
                            return arr;
                        })(),
                    }
                    ];
                }
                picker = app.picker.create({
                    inputEl: '#' + id,
                    rotateEffect: false,
                    sheetPush: false,
                    sheetSwipeToClose: false,
                    scrollToInput: true,
                    inputReadOnly: true,
                    backdrop: true,
                    closeByOutsideClick: true,
                    toolbar: true,
                    toolbarCloseText: 'OK',
                    cols: values,
                    formatValue: function (values, displayValues) {
                        return values[0] + ':' + values[1];
                    },
                    on: {
                        init: function (picker) {
                            var currentTime = document.getElementById(id).value;
                            var h = currentTime.substr(0, currentTime.indexOf(':'));
                            var m = currentTime.substr(currentTime.indexOf(':') + 1, 2);
                            var p = currentTime.substr(currentTime.indexOf(' ') + 1, 2);
                            picker.setValue([h, m, p], 200);
                        },
                        change: function (picker) {
                            if (picker.value[0] == "") {
                                return;
                            }
                            var time;
                            var result = picker.value[0] + ":" + picker.value[1];
                            if (kHours12 == true) {
                                result = result + " " + picker.value[2];
                                var ampm = picker.value[2];
                                if (ampm == "am") {
                                    if (picker.value[0] == "12") {
                                        time = "00:" + picker.value[1];
                                    } else (time = picker.value[0] + ":" + picker.value[1])
                                } else { //pm
                                    var h = parseInt(picker.value[0]) + 12;
                                    if ((h == 24) && (parseInt(picker.value[1]) > 0)) {
                                        picker.value[1] = "00";
                                        result = "12:00 pm";
                                    }
                                    if (h < 10) {
                                        h = "0" + h
                                    }
                                    time = h + ":" + picker.value[1]
                                }
                            } else {
                                time = picker.value[0] + ":" + picker.value[1];
                            }

                            picker.setValue(result);
                            var inpt = document.getElementById(id);
                            if (inpt) {
                                // format: hh:mm:ss.ms
                                inpt.setAttribute("type", "text");
                                inpt.value = time;
                                inpt.setAttribute("value", time);
                            }
                        },
                    }
                });
                document.getElementById(id).setAttribute("picker-initialized", true);
            }
        },


    },
    calendars: {
        init: function () {
            thoriumapi.logEvent(0, "[com.thoriumbuilder.core] initializing Calendars");
            var elts = document.querySelectorAll("input[data-date-type]:not([data-date-type='time']");
            for (var i = 0; i < elts.length; i++) {
                var elt = elts[i];
                var initialized = (elt.getAttribute("calendar-initialized") == 'true') || false;
                if (initialized != true) {
                    var attr = elt.getAttribute('data-date-type');
                    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Init Calendar Instance with ID [" + elt.id + "]");
                    var showTime = (attr == 'datetime-local') || false;
                    var minToday = (elt.getAttribute('data-date-min-today') == "true") || false;
                    thoriumCorePlugin.calendars.setup(elt.id, showTime, minToday);
                }
            }
            var x = document.querySelectorAll(".custom-calendar-inline");
            for (var i = 0; i < x.length; i++) {
                var elt = x[i];
                thoriumCorePlugin.calendars.initInnerCalendar(elt);
            }
        },
        setup: function (id, showtime, fromToday) {
            var status = document.getElementById(id).getAttribute("calendar-initialized");
            if (status == 'true') {
                return;
            }
            var cal = app.calendar.get('#' + id);
            if (!cal) {
                var dateFormat;
                if (showtime == true) {
                    //dateFormat = kDateTimeFormat;
                    dateFormat = kDateFormat;
                    if (kHours12==true) {
                        dateFormat=dateFormat+" hh::mm a";
                    } else {
                        dateFormat=dateFormat+" HH::mm";
                    }

                } else {
                    dateFormat = kDateFormat;
                }
                var minDate = null;
                if (fromToday == true) {
                    minDate = new Date();
                    minDate.setDate(minDate.getDate() - 1);
                }
                cal = app.calendar.create({
                    inputEl: '#' + id,
                    inputReadOnly: true,
                    timePicker: showtime,
                    timePickerFormat: {
                        hour: 'numeric',
                        minute: 'numeric'
                    },
                    touchMove: false,
                    animate: true,
                    backdrop: true,
                    closeOnSelect: true,
                    weekHeader: true,
                    monthSelector: true,
                    monthPicker: true,
                    yearSelector: true,
                    dateFormat: dateFormat,
                    minDate: minDate,
                    on: {
                        change: function (calendar, value) {
                            if ((value) && (value!="0") ) {
                                var selectedDate = new Date(value);
                                var year = selectedDate.getFullYear();
                                var month = selectedDate.getMonth() ;
                                var day = selectedDate.getDate();
                                calendar.inputEl.value = selectedDate.toLocaleString();
                                var timeOffsetInMS = selectedDate.getTimezoneOffset() * 60000;
                                selectedDate.setTime(selectedDate.getTime() - timeOffsetInMS);
                                var timestamp = (selectedDate.getTime() / 1000);
                                calendar.inputEl.setAttribute("data-timestamp", timestamp);
                            } else {
                                calendar.inputEl.setAttribute("data-timestamp", "");
                            }
                        },
                        open: function (calendar) {
                            var timestamp = calendar.inputEl.getAttribute("data-timestamp") || null;
                            if ((timestamp) && (timestamp.length>0)) {
                                var selectedDate = new Date(timestamp * 1000);
                                calendar.currentYear = selectedDate.getFullYear();
                                calendar.currentMonth = selectedDate.getMonth();
                                calendar.setValue([selectedDate]);
                                var sy = document.querySelector('.calendar .current-year-value');
                                if (sy) {
                                    sy.textContent = selectedDate.getFullYear();
                                }
                            }
                            calendar.update();
                        },
                    }
                });
                cal.dayNames = kdayNames;
                cal.dayNamesShort = kdayNamesShort;
                cal.monthNames = kmonthNames;
                cal.monthNamesShort = kmonthNamesShort;
                document.getElementById(id).setAttribute("calendar-initialized", "true");
            }
        },
        initInnerCalendar: function (parent) {
            if (parent.getAttribute("data-initialized")=="true") {return}
            var calendarInline=null;
            var today = new Date();
            var yesterday = new Date();
            yesterday.setDate(today.getDate() - 1);
            var tomorrow = new Date();
            tomorrow.setDate(today.getDate() + 1);
            var minToday = (parent.getAttribute('data-date-min-today') == "true");
            var minDate = null;
            if (minToday == true) {
                minDate = yesterday;
            }
            var multiple = (parent.getAttribute('data-date-multiple') == "true");
            var touchMove = (parent.getAttribute('data-date-touchmove') == "true");
            var weekHeader = (parent.getAttribute('data-date-weekheader') == "true");
            var monthSelector = (parent.getAttribute('data-date-monthselector') == "true");
            var yearSelector = (parent.getAttribute('data-date-yearselector') == "true");
            var toolbar = false;
            if ((monthSelector == true) || (yearSelector == true)) {
                toolbar = true;
            }
            var locale = parent.getAttribute('data-date-locale') || "en-US"; //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat#Parameters
            var timeSelector = (parent.getAttribute('data-date-timeselector') == "true");
            var timeSelectorText = parent.getAttribute('data-date-timeselector-text') || "Select Time";

            if (parent.getAttribute("data-initialized")=="true") {
                var cal=parent.querySelector(".calendar");
                if (cal) {
                    calendarInline = app.calendar.get(cal);
                    if (calendarInline) {
                        parent.innerHTML="";
                        app.calendar.destroy(calendarInline);
                    }
                }
            }
            
           
            // app.dialog.alert(parent.id);
            calendarInline = app.calendar.create({
                containerEl: parent,
                locale: locale,
                dayNames: kdayNames,
                dayNamesShort: kdayNamesShort,
                monthNames: kmonthNames,
                monthNamesShort: kmonthNamesShort,
                value: null,
                disabled: null,
                events: null,
                multiple: multiple,
                rangePicker: false,
                rangePickerMinDays: null,
                rangePickerMaxDays: null,
                direction: 'horizontal',
                minDate: minDate,
                maxDate: null,
                touchMove: touchMove,
                animate: true,
                weekHeader: weekHeader,
                monthSelector: monthSelector,
                monthPicker: false,
                yearSelector: yearSelector,
                yearPicker: false,
                yearPickerMin: null,
                yearPickerMax: null,
                timePicker: timeSelector,
                timePickerFormat: {
                    hour: 'numeric',
                    minute: 'numeric'
                },
                timePickerPlaceholder: timeSelectorText,
                toolbar: toolbar,
                toolbarCloseText: "Close",
                header: false,
                headerPlaceholder: "Select date",
                on: {
                    init: function (calendar) {
                        thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Initializing Calendar Widget");
                        app.emit('calendarInit', calendar);
                        parent.setAttribute("data-initialized","true");
                    },
                    dayClick: function (calendar, dayEl, year, month, day) {
                        thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Calendar Widget DayClick");
                        app.emit('customcalendarDayClick', calendar, dayEl, year, month, day);
                    },
                    change: function (calendar, value) {
                       var isDBCalendar=parent.classList.contains("custom-db-calendar");
                       if (isDBCalendar==true) {
                        thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Calendar Widget Change");
                        //Get Custom Data
                        var customdata={};
                        if ((calendar.params.events) && (value)) {
                            for (var i = 0; i < calendar.params.events.length; i++) {
                                var d=calendar.params.events[i].date.toString();
                                var v=value[0].toString();
                                if (d == v) {
                                    customdata=calendar.params.events[i].custom;
                                    break;
                                }
                            }
                        }
                        var selectedDate = new Date([value[value.length - 1]]);
                        if (selectedDate) {
                            var timestamp = selectedDate.getTime() / 1000;
                            parent.setAttribute("data-timestamp", timestamp);
                            var o = calendar.$containerEl[0].closest(".custom-db-calendar");
                            if (o) {
                                var datatarget = o.getAttribute('data-target') || null;
                                var uid = customdata.uid;
                                var rowindex = customdata.dataindex;
                                var pk = customdata.pk;
                                var view = app.view;
                                var service = o.getAttribute('data-service') || null;
                                if ((view) && (view.length > 0) && (datatarget) && (datatarget.length>0) && (service) && (service.length>0) && (customdata) && (uid) ) {
                                    var url='/' + datatarget + "/?rowindex=" + rowindex+ "&uid=" + uid + "&pk=" + pk + "&service=" + service;
                                    var props={};
                                    props.rowindex=rowindex;
                                    props.uid=uid;
                                    props.pk=pk;
                                    props.service=service;
                                    thoriumapi.loadpage(url,props,null);
                                }
                            }
                        }
                        app.emit('dbItemChange[id="'+parent.id+'"]', calendar, value,customdata);
                       }
                    },
                    monthAdd: function (calendar, monthEl) {
                        app.emit('calendarMonthAdd', calendar, monthEl);
                    },
                    monthYearChangeStart: function (calendar, year, month) {
                        app.emit('calendarMonthYearChangeStart', year, month);
                    },
                    open: function (calendar) {
                        app.emit('calendarOpen', calendar);
                        thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Opening Calendar Widget");
                        var timestamp = parent.getAttribute("data-timestamp") || null;
                        if (timestamp) {
                            var selectedDate = new Date(timestamp * 1000);
                            calendar.currentYear = selectedDate.getFullYear();
                            calendar.currentMonth = selectedDate.getMonth();
                        }
                    },
                    opened: function (calendar) {
                        app.emit('calendarOpened', calendar);
                        thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Calendar Widget Opened");
                    },
                    close: function (calendar) {
                        app.emit('calendarClose', calendar);
                        thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Closing Calendar Widget");
                    },
                    closed: function (calendar) {
                        app.emit('calendarClosed', calendar);
                        thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Calendar Widget Closed");
                    },
                    beforeDestroy: function (calendar) {
                        app.emit('calendarBeforeDestroy', calendar);
                        thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Destroying Calendar Widget");
                    },
                }
            });
        },
    },
    texteditor: {
        init: function () {
            var elts = document.querySelectorAll(".text-editor");
            for (var i = 0; i < elts.length; i++) {
                var field = elts[i];
                thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Init Text Editor Instance with ID [" + field.id + "]");
                var o = app.textEditor.get('#' + field.id);
                if (!o) {
                    o = thoriumCorePlugin.texteditor.setup(field);
                }
            }
        },
        setup: function (elt) {
            elt.setAttribute("texteditor-initialized", true);
            return app.textEditor.create({
                el: '#' + elt.id,
                buttons: kTextEditorButtons,
                on: {
                    change: function (e) {
                        var shadow = e.el.id + "-shadow";
                        var v = e.value || '';
                        v = v.replaceAll("<p></p>", "").trim();
                        var its = document.getElementById(shadow);
                        if (its) {
                            its.value = v;
                        }
                    },
                    init: function (e) {
                        var shadow = e.el.id + "-shadow";
                        var v = e.value || '';
                        v = v.replaceAll("<p></p>", "").trim();
                        var its = document.getElementById(shadow);
                        if (its) {
                            its.value = v;
                        }
                    }
                },
            })
        },
    },
    photobrowser: {
        getnavbar: function () {
            return '<div class="navbar navbar-photo-browser"> \
                    <div class="navbar-bg"></div> \
                    <div class="navbar-inner navbar-inner-centered-title"> \
                      <div class="left"> \
                        <a class="link webshare" data-webshare-textref="" data-webshare-imgref=".swiper-slide-active .swiper-zoom-container img" > \
                            <i class="fa material-icons">share</i> \
                        </a> \
                      </div> \
                      <div class="title"> \
                        <span class="photo-browser-current"></span> \
                        <span class="photo-browser-of">/</span> \
                        <span class="photo-browser-total"></span> \
                      </div> \
                      <div class="right"> \
                        <a class="link popup-close" data-popup=".photo-browser-popup"> \
                          <span>' + kPhotoBrowserBackLinkText + '</span> \
                        </a> \
                      </div> \
                    </div> \
                  </div>';
        },
        show: function (arr, idx) {
            thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Show Photo Browser");
            idx = idx || 0;
            var showToolbar = (arr.length > 1);
            var renderFunction = null;
            if (kWebShareApi == true) {
                renderFunction = thoriumCorePlugin.photobrowser.getnavbar;
            }
            var photoBrowser = app.photoBrowser.create({
                swipeToClose: true,
                photos: arr,
                thumbs: arr,
                exposition: true,
                expositionHideCaptions: false,
                backLinkText: kPhotoBrowserBackLinkText,
                navbarOfText: '/',
                theme: kPhotoBrowserTheme,
                captionsTheme: kPhotoBrowserTheme,
                navbar: true,
                toolbar: showToolbar,
                type: 'standalone',
                lazyLoading: true,
                captionTemplate: 'title',
                navbarTemplate: 'title',
                renderNavbar: renderFunction,
                swiper: {
                    effect: kPhotoBrowserEffect,
                    kPhotoBrowserAutoPlay,
                    grabCursor: true,
                    spaceBetween: 20,
                    speed: 300,
                    loop: false,
                    preuploadFileReads: false,
                    zoom: {
                        enabled: true,
                        maxRatio: 3,
                        minRatio: 1,
                    },
                    lazy: {
                        enabled: false,
                    },
                },
            });
            photoBrowser.open(idx);
            app.emit('photobrowserDisplayed', photoBrowser);
        },
        showImageZoom: function (e) {
            e.preventDefault;
            thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Show Photo Browser");
            var target = e.target;
            if (target) {
                var photos = [];
                var s = target.getAttribute("src");
                var t = target.getAttribute("title");
                if (!(s)) {
                    s = target.style.backgroundImage;
                    var pos = s.indexOf(",");
                    if (pos > -1) {
                        var r = s.substring(0, s.indexOf(","));
                        s = r;
                    }
                    s = s.replace('url(', '');
                    s = s.replace(/\"/gi, "");
                    var lastChar = s.substr(s.length - 1);
                    if (lastChar == ")") {
                        s = s.substr(0, s.length - 1);
                    }
                }
                var p = {};
                p.url = s;
                p.caption = t;
                photos.push(p);
                thoriumCorePlugin.photobrowser.show(photos, 0);
            }
        },
        showMedialistZoom: function (t, e) {
            thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Show Medialist Photo Browser");
            var parentVL = t.closest(".virtual-list");
            if (!parentVL) {
                thoriumapi.logEvent(1, "[com.thoriumbuilder.core] Medialist Item Parent VL not found");
                return;
            }
            var vlRef = app.virtualList.get(parentVL);
            if (!vlRef) {
                thoriumapi.logEvent(1, "[com.thoriumbuilder.core] Medialist Item Parent VL not found");
                return;
            }
            var photos = [];
            var index = t.getAttribute("data-index") || 0;
            for (var i = 0; i < vlRef.items.length; i++) {
                var p = {};
                p.url = vlRef.items[i].imagesrc;
                p.caption = vlRef.items[i].title;
                photos.push(p);
            }
            thoriumCorePlugin.photobrowser.show(photos, parseInt(index));
        },
        showCollectionZoom: function (item, e) {
            thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Show Collection Photo Browser");
            var selImg;
            var photos = [];
            var index = 0;
            var c = item.querySelector('.collection-item-image');
            if (c) {
                selImg = c.getAttribute('data-file');
            }
            var coll = item.closest('.collection');
            if (coll) {
                var x = coll.querySelectorAll(".collection-item:not(.templaterow)");
                for (var i = 0; i < x.length; i++) {
                    var elt = x[i];
                    var img = elt.querySelector('.collection-item-image');
                    if (img) {
                        var p = {};
                        var path = img.getAttribute('data-file');
                        p.url = path;
                        p.caption = img.getAttribute('title');
                        photos.push(p);
                        if (path == selImg) {
                            index = i;
                        }
                    }
                }
            }
            thoriumCorePlugin.photobrowser.show(photos, index);
        },
    },
    effects: {
        init: function () {
            var elts = document.querySelectorAll("* [data-transform-trigger='pageload']");
            for (var i = 0; i < elts.length; i++) {
                var elt = elts[i];
                thoriumCorePlugin.effects.execute(elt);
            }
        },
        execute: function (elt) {
            var done = elt.getAttribute('data-effect-loaded') || 'false';
            if (done == 'false') {
                var effect = elt.getAttribute('data-transform-effect') || '';
                elt.style.transform = effect;
                var opacity = elt.getAttribute('data-transform-opacity') || '';
                elt.style.opacity = opacity;
                thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Apply Effect to element " + elt.id + " [" + effect + "]");
                elt.setAttribute('data-effect-loaded', 'true');
            }
        },
        reset: function (elt) {
            thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Remove Effect from element " + elt.id);
            var effect = elt.getAttribute('data-stored-state') || '';
            elt.style.transform = effect;
            var opacity = elt.getAttribute('data-initial-opacity') || '';
            elt.style.opacity = opacity;
        },
        scrolleffect: function (e) {
            var elts = document.querySelectorAll("* [data-transform-trigger='viewport']:not(.activeeffect)");
            for (var i = 0; i < elts.length; i++) {
                var elt = elts[i];
                var p2 = thoriumCorePlugin.getObjectViewPortPercent(elt, e.scrollTop);
                var p1 = elt.getAttribute('data-transform-viewport') || 0;
                if ((p2 > p1)) {
                    elt.classList.add("activeeffect");
                    var effect = elt.getAttribute('data-transform-effect') || '';
                    elt.style.transform = effect;
                    var opacity = elt.getAttribute('data-transform-opacity') || '';
                    elt.style.opacity = opacity;
                    console.log(elt.id + " loaded => " + p2)
                }
            }
        },
    },
    translations: {
        recursive: function (elt, att) {
            console.log(elt.id + " = " + elt.innerText + "  /// " + thoriumCorePlugin.getText(elt));
            if (elt.childElementCount == 0) {
                var t = elt.getAttribute(att);
                elt.innerText = t; //text
                elt.childNodes[0].nodeValue = t;
            } else {
                var x = elt.querySelectorAll("* [" + att + "]");
                for (var i = 0; i < x.length; i++) {
                    var subelt = x[i];
                    thoriumCorePlugin.translations.recursive(subelt, att);
                }
                var t = elt.getAttribute(att) || null;
                if (t) {
                    if (t.indexOf("<") > -1) {
                        elt.innerHTML = t;
                    } else {
                        elt.innerText = t;
                    }
                }
            }
        },
        translate: function (lg, ltr) {
            var currentLng = document.documentElement.lang || 'en';
            var currentDir = document.documentElement.dir || 'ltr';
            if (!lg) {
                lg = sessionStorage.getItem('thorium.language') || currentLng;
            }
            if (!ltr) {
                ltr = sessionStorage.getItem('thorium.direction');
                if (ltr == "undefined") {
                    ltr = thoriumCorePlugin.direction;
                }
            }
            if (lg == "undefined") {
                lg = currentLng;
            }
            ltr = ltr || 'ltr';
            document.documentElement.dir = ltr;
            thoriumCorePlugin.language = lg;
            thoriumCorePlugin.direction = ltr;
            sessionStorage.setItem('thorium.language', lg);
            sessionStorage.setItem('thorium.direction', ltr);
            thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Translate Screen to " + lg);
            thoriumCorePlugin.multilingual = true;
            var att = "data-translate-" + lg;
            var x = document.querySelectorAll("* [" + att + "]");
            for (var i = 0; i < x.length; i++) {
                var elt = x[i];
                thoriumCorePlugin.translations.recursive(elt, att);
            }
            //Placeholders
            att = "placeholder-" + lg;
            var x = document.querySelectorAll("* [" + att + "]");
            for (var i = 0; i < x.length; i++) {
                var elt = x[i];
                var translation = elt.getAttribute(att);
                elt.setAttribute("placeholder", translation);
            }
        },



    },
    geolocation: {
        locate: function (obj) {
            thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Trying to geolocate user...");
            if (navigator.geolocation) {
                try {
                    navigator.geolocation.getCurrentPosition(
                        function (position) {
                            if (!obj.value.includes("{")) {
                                obj.value = "{"+position.coords.latitude + "," + position.coords.longitude+"}";
                                $('#' + obj.id).data('pos', position);
                                thoriumapi.logEvent(0, "[com.thoriumbuilder.core] User Geolocated " + position.coords.latitude + "," + position.coords.longitude);
                                obj.classList.remove("disabled");
                            }
                        },
                        function (err) {
                            thoriumapi.logEvent(1, "[com.thoriumbuilder.core] " + err.message);
                            if (!obj.value.includes("{")) {
                                obj.value = err.message;
                                obj.classList.add("disabled");
                            }
                        }
                    );
                } catch (error) {
                    thoriumapi.logEvent(1, "[com.thoriumbuilder.core] " + error.message);
                    app.dialog.alert(error.message);
                }
            } else {
                thoriumapi.logEvent(1, "[com.thoriumbuilder.core] Geolocation is not supported.");
                if (!obj.value.includes("{")) { obj.value = "Geolocation is not supported.";}
            }
        },
    },
    forms: {
        init: function (e) {
            e = e || null;
            var x = document.querySelectorAll(".form-ajax-submit");
            for (var i = 0; i < x.length; i++) {
                var form = x[i];
                var loaded = form.getAttribute("data-loaded") || "false";
                if ((loaded != "true") &&
                    (form.id != "shoppingcartform") &&
                    (form.id != "form-orderprocess") &&
                    (form.id != "dbexpress-signin-form") &&
                    (form.id != "resetpw-form") &&
                    (form.id != "register-form") &&
                    (form.id != "dbexpress-profile-form")
                ) {
                    if (e) {
                        var p = e.detail;
                        if (p) {
                            var key = p.route.query.key || null;
                            if (key) {
                                form.setAttribute("data-parent-key", key);
                            }
                        }
                    }
                    thoriumCorePlugin.forms.setup(form);
                }
            }
        },
        setup: function (o) {
            thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Initializing Form with id [" + o.id + "]");
            var x = o.querySelectorAll("input[data-autofill='true']");
            for (var i = 0; i < x.length; i++) {
                var elt = x[i];
                var t = elt.getAttribute("type");
                var dateType = elt.getAttribute("data-date-type") || null;
                var v = elt.value;
                if ((!v) || (v.length == 0)) {
                    if (t == 'date') {
                        elt.value = thoriumCorePlugin.ISO8601(new Date());
                    }
                    if (t == 'time') {
                        elt.value = thoriumCorePlugin.getTime(new Date());
                    }
                    if (t == 'datetime-local') {
                        elt.value = thoriumCorePlugin.ISO8601(new Date()) + ' ' + thoriumCorePlugin.getTime(new Date());
                    }
                    if (t == 'geo') {
                            thoriumCorePlugin.geolocation.locate(elt);
                    }
                    if ((dateType == "datetime-local") || (dateType == "date")) {
                        v = thoriumapi.date.getTimeStamp();
                        elt.setAttribute("data-timestamp", v);
                        var niceDate = thoriumapi.date.getDateTimeFromTimeStamp(v, dateType);
                        elt.setAttribute("value", niceDate);
                        elt.value = niceDate;
                    }
                    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Form Input [" + elt.name + "] with attribute AutoFill initialized with value [" + elt.value + "]");
                }
            }

            //Dynamic Variables
            var x = o.querySelectorAll("input");
            for (var i = 0; i < x.length; i++) {
                var elt = x[i];
                var v = elt.value;
                if ((v) && (v.length > 0)) {
                    var str = v,
                        rg = /[^{\}]+(?=})/g,
                        match;
                    while (match = rg.exec(str)) { // Iterate matches
                        var varValue = "";
                        var varname = match[0];
                        try {
                            varValue = window[varname];
                            if (varValue) { elt.value = varValue; } else { elt.value = ""; }
                        } catch (err) {
                            thoriumapi.logEvent(1, "[com.thoriumbuilder.core] Unable to find variable " + varname + "]");
                        }
                    }
                }
            }


            $("#" + o.id + " select[data-api-mode]").each(function (obj) {
                var api = obj.getAttribute("data-api") || null;
                var fld = obj.getAttribute("data-api-field") || null;
                var coll = obj.getAttribute("data-select-collection") || null;
                var fldid = obj.getAttribute("data-api-fieldid") || null;
                var func = obj.getAttribute("data-function") || null;
                var mode = 0;
                mode = obj.getAttribute("data-api-mode") || '0';
                if ((api) && (mode == '0')) {
                    mode = 1;
                } //Fallback version 1.x
                if ((mode == '1') && (api) && (fld)) {
                    thoriumCorePlugin.forms.fillSelect(obj, api, fld, mode, fldid, func);
                    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Form Input [" + obj.name + "] of type Select initialized from api [" + api + "]");
                } else if ((mode == '2') && (coll) && (fld)) {
                    setTimeout(function () {
                        thoriumCorePlugin.forms.fillSelect(obj, coll, fld, mode, fldid, func);
                    }, 100);
                    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Form Input [" + obj.name + "] of type Select initialized from api [" + api + "]");
                }
            });
            //V3
            if (typeof dbExpressData == "object") {
                var x = document.querySelectorAll("#" + o.id + " select[data-service]");
                for (var i = 0; i < x.length; i++) {
                    var obj = x[i];
                    //Get Input Select Service
                    var service = obj.getAttribute("data-service") || null;
                    //First set Input Select Foreign Key with current Record Value
                    var currentPage = document.querySelector('.page-current');
                    if (currentPage) {
                        var parentService = currentPage.getAttribute("data-record-service") || null;
                        var parentPK = currentPage.getAttribute("data-record-pk") || null;
                        if ((parentService) && (parentPK)) {
                            if (service == parentService) {
                                obj.setAttribute("data-value", parentPK);
                            }
                        }
                    }
                    //3.4.1
                    var formMode = o.getAttribute("data-init-mode") || 0;
                    if ((service) && (formMode != 1)) {
                        dbExpressData.fillSmartSelect(obj, service);
                    }
                    //3.4.1
                }
            }
        },
        fillSelect: function (obj, api, fld, mode, idfld, func) {
            if (obj.getAttribute("data-loaded") == 'true') {
                return;
            }
            $("#" + obj.id).addClass("disabled");
            app.preloader.show();
            if (mode == '2') { //Firebase
                if (typeof firestorePlugin == "object") {
                    firestorePlugin.initFormSelectWithCollection(obj, api, fld);
                }
            } else {
                var options = {};
                var headers = {};
                var hasheader = false;
                options["method"] = "GET";
                if (kMode.length > 0) {
                    options["mode"] = kMode;
                }
                if (kCache.length > 0) {
                    options["cache"] = kCache;
                }
                if (kCredentials.length > 0) {
                    options["credentials"] = kCredentials;
                }
                /* header */
                if (kContenttype.length > 0) {
                    headers["Content-Type"] = kContenttype;
                    hasheader = true;
                }
                if (kRedirect.length > 0) {
                    headers["redirect"] = kRedirect;
                    hasheader = true;
                }
                if (kReferrerpolicy.length > 0) {
                    headers["referrerpolicy"] = kReferrerpolicy;
                    hasheader = true;
                }
                if (kAuthorization.length > 0) {
                    headers["Authorization"] = kAuthorization;
                    hasheader = true;
                }
                if (hasheader == true) {
                    options["headers"] = headers;
                }

                /* -- new callback before call --*/
                if (func) {
                    var fn = window[func];
                    var fnparams = [options];
                    if (typeof fn === "function") {
                        options = fn.apply(this, fnparams);
                    }
                }
                /*-- Replace Dynamic Vars --*/
                var parentform = obj.closest(".form");
                if (parentform) {
                    var key = parentform.getAttribute("data-parent-key") || "";
                    api = api.replace("{key}", encodeURIComponent(key));
                }
                //Dynamic variables
                var str = api,
                    rg = /[^{\}]+(?=})/g,
                    match;
                while (match = rg.exec(str)) { // Iterate matches
                    var varname = match[0];
                    if ((varname) && (varname.length > 0)) {
                        var varValue = "";
                        try {
                            varValue = window[varname];
                        } catch (err) {
                            app.preloader.hide();
                            var m = "[com.thoriumbuilder.core] Unable to find variable " + varname + "] " + err;
                            app.dialog.alert(m);
                            return;
                        }
                        if (varValue) {
                            api = api.replace("{" + varname + "}", encodeURIComponent(varValue));
                        } else {
                            app.preloader.hide();
                            var m = "[com.thoriumbuilder.core] variable [" + varname + "] is undefined";
                            app.dialog.alert(m);
                            return;
                        }

                    }
                }
                thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Calling Api  [" + api + "]");
                fetch(api, options).then(
                    function (response) {
                        response.json()
                            .then(data => {
                                thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Form Api received Response from url [" + api + "]");
                                app.emit('dataloadSuccess', obj);
                                var h = '<option value="" disabled></option>';
                                idfld = idfld || fld;
                                for (var prop in data) {
                                    h = h + '<option value="' + data[prop][idfld] + '">' + data[prop][fld] + '</option>';
                                }
                                obj.innerHTML = h;
                                //Current Value
                                var v = obj.getAttribute('data-value') || '';
                                if (v.length > 0) {
                                    var fp = $(obj).closest('.smart-select');
                                    if (fp) {
                                        var sm = app.smartSelect.get(fp);
                                        if (sm) {
                                            sm.setValue(v);
                                        }
                                    } else {
                                        obj.value = v;
                                    }
                                }
                                $("#" + obj.id).removeClass("disabled");
                                obj.setAttribute("data-loaded", "true");
                                setTimeout(function () {
                                    app.preloader.hide();
                                }, 1000);
                            })
                            .catch(function (err) {
                                app.preloader.hide();
                                thoriumapi.logEvent(2, "[com.thoriumbuilder.core] Error [" + err.message + "] While loading data from [" + api + "]");
                                app.dialog.alert(err.message);
                            });
                    }).catch(function (err) {
                        app.preloader.hide();
                        thoriumapi.logEvent(2, "[com.thoriumbuilder.core] Error [" + err.message + "]");
                        if (app.online == false) {
                            thoriumapi.showNotification("Internet connection lost");
                        } else {
                            app.dialog.alert(err.message);
                        }
                    });
            }
        },
        getformdata: function(form,dataMode) {
            var formData = new FormData();
            for (var i = 0; i < form.length; i++) {
                var x = "_alias";
                var reservedWords = ["name" + x, "id" + x, "description" + x, "this" + x, "title" + x, "submit" + x];
                var fieldname = form[i].name;
                var alias = fieldname;
                if (reservedWords.includes(alias) === true) {
                    alias = alias.replaceAll(x, '');
                }
                var inputType = form[i].type;
                var dataDateType;
                var isGeoPoint=false;
                if (inputType == "text") {
                    dataDateType=form.querySelector("input[name=" + fieldname + "]").getAttribute("data-date-type");
                    isGeoPoint= (form.querySelector("input[name=" + fieldname + "]").getAttribute("type")=="geo");
                } else {
                    dataDateType="";
                }
                if (inputType == "radio") {
                    if (form[i].checked === true) {
                        formData.append(alias, form[i].value);
                    }
                } else if ((inputType == "geo") || (isGeoPoint==true)) {
                    var v=form[i].value;
                    if ((v) && (v.length>0)) {
                        v=v.replaceAll("{","").replaceAll("}","");
                        const arr = v.split(",");
                        if ((arr) && (arr.length==2)) {
                            const r="{"+arr[0]+","+arr[1]+"}";
                            formData.append(alias,r);
                        } else {
                            formData.append(alias,"");
                        }
                    } else {
                        formData.append(alias,"");
                    }
                } else if (inputType == "checkbox") {
                    formData.append(alias, form[i].checked);
                } else if ((inputType == "submit") || (inputType == "button")) {

                } else if (inputType == "file") {
                    var tt = form[i].id;
                    var ost = $("#" + tt);
                    if (ost) {
                        var blob = ost.data("data-blob");
                        if (blob) { //DbExpress
                            if (alias.indexOf("-fileinput") == -1) {
                                alias = alias + "-fileinput";
                            }
                            formData.append(alias, blob);
                        } else {
                            if (dataMode == 0) { //Form Data
                                var file = form[i].files[0];
                                if (file) {
                                    formData.append('file', file);
                                } else {
                                    thoriumapi.logEvent(1, "[com.thoriumbuilder.core] empty file for field " + fieldname + " for Form with ID [" + form.id + "]");
                                }
        
                            } else { //JSON
                                var ost = $("#" + tt);
                                if (ost) {
                                    var blob = ost.data("data-blob");
                                    if (!blob) {
                                        blob = "";
                                    }
                                    //var filename = ost.data("data-filename");
                                    formData.append(alias, blob);
                                }
                            }
                        }
 
                    }
                } else {
                    if ((dataDateType == "datetime-local") || (dataDateType == "date") ) {
                        var timestamp = form[i].getAttribute("data-timestamp") || null;
                        if (timestamp) {
                            formData.append(alias, timestamp);
                        } else {
                            formData.append(alias, "0");
                        }
                    } else {
                        var v=form[i].value;
                        if (v.length==0) {
                            if (inputType=="number") {
                                v="0";
                            } else {
                                v="null";
                            }
                        }
                        formData.append(alias,v);
                    }
                }
            }
            return formData;
        },
        setOptionsHeaders: function(form,dataMode,formData) {
            const plainFormData = Object.fromEntries(formData.entries());
            const formDataJsonString = JSON.stringify(plainFormData);
            var api_method = form.getAttribute("data-api-method") || 'post';
            var api_referrer = form.getAttribute("data-api_referrer") || '';
            var api_referrerpolicy = form.getAttribute("data-api-referrerpolicy") || '';
            var api_mode = form.getAttribute("data-api-mode") || '';
            var api_cache = form.getAttribute("data-api-cache") || '';
            var api_credentials = form.getAttribute("data-api-credentials") || '';
            var api_redirect = form.getAttribute("data-api-redirect") || '';
            //API Headers Stored Data
            var api_contenttype = form.getAttribute("data-api-contenttype") || 'application/json';
            var api_accept = form.getAttribute("data-api-accept") || 'application/json';
            var api_authorization = form.getAttribute("data-api-authorization") || '';
            if (api_authorization.length > 0) {
                api_authorization = thoriumapi.replaceDynamicVariables(api_authorization);
            }
            var api_header_key = form.getAttribute("data-api-header_key") || '';
            var api_header_value = form.getAttribute("data-api-header_value") || '';
            //Set Options & Headers
            var options = {};
            var headers = {};
            options["method"] = api_method;
            if (api_referrer.length > 0) { options["referrer"] = api_referrer; }
            if (api_referrerpolicy.length > 0) { options["referrerpolicy"] = api_referrerpolicy; }
            if (api_mode.length > 0) { options["mode"] = api_mode; }
            if (api_cache.length > 0) { options["cache"] = api_cache; }
            if (api_credentials.length > 0) { options["credentials"] = api_credentials; }
            if (api_redirect.length > 0) { options["redirect"] = api_redirect; }
            /* header */
            if (api_authorization.length > 0) {
                headers["Authorization"] = thoriumapi.replaceDynamicVariables(api_authorization);
            }
            if ((api_header_key.length > 0) && (api_header_value.length > 0)) {
                headers[api_header_key] = api_header_value;
            }
            if (api_accept.length > 0) {
                headers["accept"] = api_accept;
            }
            if (dataMode == 0) { //Form Data
                options["body"] = formData;
            } else { //JSON
                if (api_accept.length == 0) {
                    headers["accept"] = 'application/json';
                }
                var body;
                var root =form.getAttribute("data-api-root") || '';
                if ((root) && (root.length > 0)) {
                    body = '{"' + root + '": [' + formDataJsonString + ']}';
                } else {
                    body = formDataJsonString;
                }
                options["body"] = body;
                headers["Content-Type"] = 'application/json';

            }
            options["headers"] = headers;
            return options;
        },
        beforeSubmitFunction: function(form,options) {
            //Form Before Submit Function
            var beforeSubmitFunc = form.getAttribute("data-form-beforesubmit");
            if (beforeSubmitFunc) {
                thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Function [" + beforeSubmitFunc + "] Called for Form with id [" + form.id + "]");
                var fn = window[beforeSubmitFunc];
                var fnparams = [form, options];
                var result = thoriumapi.callFunctionByName(fn, fnparams);
                if (result == false) {
                    var err = '[com.thoriumbuilder.core] An error occured "' + beforeSubmitFunc + '" is not an existing function';
                    thoriumapi.logEvent(2, err);
                    return;
                } else {
                    options = result[1];
                }
            }
            return options;
        },
        afterSubmitFunction: function(form,data) {
            var func = form.getAttribute('data-return-function') || null;
            if (func) {
                thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Form with id [" + form.id + "] : Calling JS function [" + func + "]");
                var fn = window[func];
                var fnparams = [form, data];
                if (typeof fn === "function") {
                    var r = fn.apply(this, fnparams);
                    app.preloader.hide();
                    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] JS function [" + func + "] called successfully when submitting Form with id [" + form.id + "]");
                } else {
                    app.preloader.hide();
                    var err = 'An error occured "' + func + '" is not an existing function';
                    thoriumapi.logEvent(2, "[com.thoriumbuilder.core] JS function [" + func + "] called with Error: [" + err + "] when submitting Form with id [" + form.id + "]");
                    app.dialog.alert(err);
                    app.emit('userFormError', err, form);
                }
            }
        },

        submitForm: function (e, form) {
            e.preventDefault;
            var url = "";
            if (app.input.validateInputs(form) == false) {
                thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Missing Mandatory fields for Form with id [" + form.id + "]");
                return;
            }
              //Data Mode
            var dataMode =form.getAttribute("data-api-data-mode") || 0; //0=formData 1: JSON

            //Get Form Data
            thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Formating Data for Form with id [" + form.id + "]");
            var formData=thoriumCorePlugin.forms.getformdata(form,dataMode);

            //API Options & Headers
            var options=thoriumCorePlugin.forms.setOptionsHeaders(form,dataMode,formData)
            //Before Submit Function
            options=thoriumCorePlugin.forms.beforeSubmitFunction(form,options);

            
            //Check Action and submit
            thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Submiting Form with id [" + form.id + "]");
            var dm = form.getAttribute('data-mode') || null;
            var cm = form.getAttribute("data-form-message");
            if (dm == 0) { /* -- email php form --*/
                var em = form.getAttribute("data-email") || null;
                if (!em) {
                    var err = 'Recipient email not set for the form';
                    thoriumapi.logEvent(2, "[com.thoriumbuilder.core] Error: [" + err + "] when submitting Form with id [" + form.id + "]");
                    app.dialog.alert(err);
                    app.emit('userFormError', err, form, 0, null);
                    return;
                }
                if ((thoriumapi.isLocal() == true) && (dm == 0)) {
                    if (cm.length > 0) {
                        app.preloader.hide();
                        app.dialog.alert(cm);
                    }
                    thoriumapi.logEvent(1, "[com.thoriumbuilder.core] This Form can't send an email when running from the simulator");
                    thoriumapi.showNotification("This Form can't send an email when running from the simulator");
                    return;
                }
                url = thoriumCorePlugin.httpRoot + 'forms/form.php';
            } else if (dm == 2) { /* -- call js function --*/
                var func = form.getAttribute("data-form-function");
                thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Calling Function [" + func + "] for Form with id [" + form.id + "]");
                if (func) {
                    var fn = window[func];
                    var fnparams
                    if (dataMode = 0) { //Form Data
                        fnparams = [form, formData];
                    } else { //JSON
                        const plainFormData = Object.fromEntries(formData.entries());
                        const formDataJsonString = JSON.stringify(plainFormData);
                        fnparams = [form, formDataJsonString];
                    }
                    if (thoriumapi.callFunctionByName(fn, fnparams) == false) {
                        if (cm.length > 0) {
                            app.dialog.alert(cm);
                        }
                    }
                }
                return;
            } else if (dm == 1) { /* -- Call an API (Json Api or dbExpress) --*/
                url = form.getAttribute("data-api-url");
                if (!url) {
                    var err = 'form url not defined!';
                    thoriumapi.logEvent(2, "[com.thoriumbuilder.core] Error: [" + err + "] when submitting Form with id [" + form.id + "]");
                    app.dialog.alert(err);
                    app.emit('userFormError', err, form, 0, null);
                    return;
                }
                var key = form.getAttribute("data-parent-key") || null;
                if (key) {
                    url = url.replaceAll("{key}", key);
                }
            }          

            /* -- Call API --*/
            app.preloader.show();
            thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Sending Data to: [" + url + "] from Form with id [" + form.id + "]");
            fetch(url, options)
            .then(thoriumapi.status)
            .then(thoriumapi.json)
            .then(function (data) {
                app.preloader.hide();
                thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Form with id [" + form.id + "] Sent with Success");
                if (cm.length > 0) {
                    app.dialog.alert(cm)
                };
                app.emit('userFormSuccess', data, form);
                //Clear Form
                var clearData = form.getAttribute('data-reset-form') || 'false';
                if (clearData == 'true') {
                    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Form Reset");
                    e.target.reset();
                    $("#" + e.target.id + " .text-editor").each(function (field) {
                        var te = app.textEditor.get('#' + field.id);
                        if (te) {
                            te.setValue("");
                        }
                    });
                    $('.firebase_document_preview').css('background-image', '');
                    $('.input-filename span').html('');
                    $('.firebase_fileupload-rotate').hide();
                    $('.firebase_fileupload-remove').hide();
                    $('.input-filename i').hide();
                }

                 //After Submit Function
                options=thoriumCorePlugin.forms.afterSubmitFunction(form,data);

               
                //Post Action
                var pp = form.getAttribute('data-form-postprocess') || 0;
                if (pp == 0) {
                    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Form Sent");
                } else if (pp == 1) {
                    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Going Back to Previous Page");
                    thoriumapi.backToPreviousPage();
                } else if (pp == 2) {
                    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Reloading Home Page");
                    thoriumapi.reloadHomePage();
                }
            })
            .catch(function (error) {
                thoriumapi.logEvent(2, "[com.thoriumbuilder.core] Request failed " + error);
                app.preloader.hide();
                app.dialog.close();
                var erm = form.getAttribute("data-form-error-message") || '';
                var serverResponse = error.message;
                thoriumapi.logEvent(2, "[com.thoriumbuilder.core] Form Error: [" + error.message + "] when submitting Form with id [" + form.id + "]");
                if (erm.length > 0) {
                    app.dialog.alert(erm)
                }
                else {
                    app.dialog.alert('An error occured [' + serverResponse + "] when submitting Form with id [" + form.id + "]");
                }
                app.emit('userFormError', serverResponse, form);
            });
        },
    },
    virtuallist: {
        getVlTemplateHeight: function (vlref, colcount) {
            //Generic VL Function
            var tRef = vlref.querySelector('.virtuallist-template');
            var TemplateCode;
            if (!(tRef)) {
                TemplateCode = $('#' + vlref.id).data("template");
            } else {
                TemplateCode = tRef.innerHTML;
            }
            if ((!TemplateCode) || (TemplateCode.length == 0)) {
                thoriumapi.logEvent(2, "[com.thoriumbuilder.core] Unable to get Template for Repeater with id [" + vlref.id + "]");
                app.preloader.hide();
                return;
            }
            var vlWidth = vlref.offsetWidth;
            TemplateCode = TemplateCode.replace("url({{productimage}})", "url('./img/defaultimg.png')");
            var tmp = document.createElement("div");
            if (vlWidth > 0) {
                tmp.style.width = vlWidth + "px";
            }
            var vlclasses = vlref.classList.value;
            if (colcount > 1) {
                tmp.innerHTML = '<div class="' + vlclasses + ' row no-gap multicol w-100 temptemplate">' + TemplateCode + '</div>';
            } else {
                tmp.innerHTML = '<div class="' + vlclasses + ' w-100 temptemplate"><ul>' + TemplateCode + '</ul></div>';
            }
            document.getElementById('app').appendChild(tmp); //e.
            var height = tmp.offsetHeight;
            if ((!height) || (height == -5)) {
                height = 0;
            }
            tmp.remove();
            thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Calculating Template Line Height (" + colcount + " columns) for Repeater with ID: [" + vlref.id + "] => [" + height + "px]");
            //Set Height from Template
            var classID = "tmp-" + vlref.id;
            var cl = document.getElementById(classID);
            if (cl) {
                if (cl.parentNode) {
                    cl.parentNode.removeChild(cl);
                }
            }
            thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Template Line Height (" + colcount + " columns) Applied for Repeater with ID: [" + vlref.id + "] => [" + height + "px]");
            //Return Value
            return height || 0;
        },
        applyFormulaToField(templateField, templateHtml, key, value) {
            if (templateField.classList.contains("hideifempty")) {
                if ((value.length == 0) || (value == "0")) {
                    templateField.classList.add("hidden");
                    templateHtml = templateHtml.replaceAll(templateField.innerHTML, "");
                    return templateHtml;
                }
            }
            var fieldFunction = templateField.getAttribute("data-function");
            var dataDateType = templateField.getAttribute("data-date-type") || null;
            var dataType = templateField.getAttribute("data-dbexpress-type") || null;
            if ((fieldFunction) && (fieldFunction == "boolean")) {
                var dataIconFalse = templateField.getAttribute("data-icon-false") || '';
                var dataIconTrue = templateField.getAttribute("data-icon-true") || '';
                var dataColorFalse = templateField.getAttribute("data-color-false") || '';
                var dataColorTrue = templateField.getAttribute("data-color-true") || '';
                var dataIconSize = templateField.getAttribute("data-icon-size") || '16';

                var ElementHtmlTrue = '';
                if (dataIconTrue.length > 0) {
                    if (dataIconTrue.indexOf("md_") == 0) {
                        var icn = dataIconTrue.replace("md_", "");
                        ElementHtmlTrue = '<i class="' + dataColorTrue + ' fa material-icons" style="font-size:' + dataIconSize + 'px;" >' + icn + '</i>';
                    } else {
                        ElementHtmlTrue = '<i class="' + dataColorTrue + ' f7-icons" style="font-size:' + dataIconSize + 'px;" >' + dataIconTrue + '</i>';
                    }
                }

                var ElementHtmlFalse = '';
                if (dataIconFalse.length > 0) {
                    if (dataIconFalse.indexOf("md_") == 0) {
                        var icn = dataIconFalse.replace("md_", "");
                        ElementHtmlFalse = '<i class="' + dataColorTrue + ' fa material-icons" style="font-size:' + dataIconSize + 'px;" >' + icn + '</i>';
                    } else {
                        ElementHtmlFalse = '<i class="' + dataColorFalse + ' f7-icons" style="font-size:' + dataIconSize + 'px;" >' + dataIconFalse + '</i>';
                    }
                }

                if ((dataIconFalse.length > 0) || (dataIconTrue.length > 0)) {
                    if (value == '1') {
                        templateHtml = templateHtml.replaceAll("{{" + key + "}}", ElementHtmlTrue);
                    } else {
                        templateHtml = templateHtml.replaceAll("{{" + key + "}}", ElementHtmlFalse);
                    }
                }
            } else if ((fieldFunction) && (fieldFunction == "value")) {
                var dataColorFalse = templateField.getAttribute("data-color-false") || '';
                var dataColorTrue = templateField.getAttribute("data-color-true") || '';
                if ((dataColorFalse) && (dataColorTrue)) {
                    if (value >= 0) {
                        templateHtml = templateHtml.replaceAll("{{datacolorclass}}", dataColorTrue);
                    } else {
                        templateHtml = templateHtml.replaceAll("{{datacolorclass}}", dataColorFalse);
                    }
                }
            }
            //Dates-Time
            if (dataType == "time") { //Time Value Stored with number of minutes since 00:00
                if (parseInt(value) == value) { //Stored in Numeric Form
                    if (value=="0") {
                        templateHtml = templateHtml.replaceAll("{{" + key + "}}", "");
                    } else {
                        var hours = Math.floor(parseInt(value) / 60);
                        var dec = parseInt(value) / 60;
                        var minutes = parseInt(60 * (parseFloat(dec) - hours));
                        if (hours < 10) {
                            hours = "0" + hours;
                        }
                        if (minutes < 10) {
                            minutes = "0" + minutes;
                        }
                        templateHtml = templateHtml.replaceAll("{{" + key + "}}", hours + ':' + minutes);
                    } 
                } else {
                    templateHtml = templateHtml.replaceAll("{{" + key + "}}", value);
                }
            } else if ((dataDateType) && (value) && (value.toString().length > 0) && (value != 0) && ((dataDateType == "datetime-local") || (dataDateType == "date"))) {
                //For dbExpress dataType= datetime date ou time
                var fd="";
                var showHours = false;
                if (dataType) {
                    if ((dataType == "datetime") || (dataType == "time")) { showHours = true; }
                }
                if ( (value) && (value!="0") && (value.toString().length>0) ) {
                    fd = thoriumapi.date.getDateTimeFromTimeStamp(value, dataDateType, "", showHours);
                    templateHtml = templateHtml.replaceAll("{{" + key + "}}", fd);
                } else {
                    templateHtml = templateHtml.replaceAll("{{" + key + "}}", "");
                } 
            } else if ((!value) && ((dataDateType == "datetime-local") || (dataDateType == "date"))) {
                templateHtml = templateHtml.replaceAll("{{" + key + "}}", "");
            } else if ((value) && (value == 0) && ((dataDateType == "datetime-local") || (dataDateType == "date"))) {
                templateHtml = templateHtml.replaceAll("{{" + key + "}}", "");
            }
            //Decimals
            else if ((dataType) && (dataType == "decimal")) {
                if (!value) {
                    value = 0;
                }
                var decimals = templateField.getAttribute("data-decimals") || 2;
                var formattedValue = parseFloat(value).toFixed(decimals);
                if (value == Math.trunc(value)) {
                    // formattedValue=parseInt(value);
                }
                templateHtml = templateHtml.replaceAll("{{" + key + "}}", formattedValue);
            }
            else if ((dataType) && (dataType == "rating")) {  //Rating
                if (!value) {
                    value = 0;
                }
                var stars = '<i class="fa f7-icons s-3">'+kRatingIcon+'</i>';
                var ratingValue = parseInt(value) || 0;
                var rt = '';
                for (i = 0; i < ratingValue; i++) {
                    rt = rt + stars;
                }
                templateHtml = templateHtml.replaceAll("{{" + key + "}}", rt);
            }   else if ((dataType) && (dataType == "picture") && (value) && (value.length>0) ) {  
                var rnd=Math.floor(Date.now() / 1000); 
                templateHtml = templateHtml.replaceAll("{{" + key + "}}", value+"?rnd="+rnd);
            }
            return templateHtml;
        },
        renderVlItem: function (item, templateHtml, templateRef, height) {
            var parentRepeater = templateRef.closest(".virtual-list");
            for (const [key, value] of Object.entries(item)) {
                var currentValue = value;
                if ((currentValue == "null") || (currentValue == null)) {
                    currentValue = "";
                }
                //HTML Entities
                if ((currentValue) && (currentValue.length > 0) && (kDbHTMLRendering == false)) {
                    currentValue = currentValue.replace(/&/g, "&amp;").replace(/>/g, "&gt;").replace(/</g, "&lt;").replace(/"/g, "&quot;");
                }
                if ((key == "auth_photourl") && (currentValue.length == 0) && (templateHtml.indexOf("{{auth_photourl}}") >= 0)) {
                    //  alert(templateHtml);
                    templateHtml = templateHtml.replace("url(db/dbassets/{{auth_photourl}})", "url('./img/defaultavatar.png')");
                }
                //Images
                var alternateImage = "./img/defaultimg.png";
                if (parentRepeater) {
                    var alt = parentRepeater.getAttribute("data-alternate-img") || null;
                    if ((alt) && (alt.length > 0)) {
                        alternateImage = "./img/" + alt;
                    }
                }
                templateHtml = templateHtml.replace("url('" + kRemoteHost + "db/dbassets/" + "')", "url('" + alternateImage + "')");
                templateHtml = templateHtml.replace("url(" + kRemoteHost + "db/dbassets/" + ")", "url(" + alternateImage + ")");
                templateHtml = templateHtml.replace("url('" + "db/dbassets/" + "')", "url('" + alternateImage + "')");
                templateHtml = templateHtml.replace("url(" + "db/dbassets/" + ")", "url(" + alternateImage + ")");

                //Check if display functions
                if (templateRef) {
                    var templateField = templateRef.querySelector('*[data-dbexpress-field="' + key + '"]');
                    if (templateField) {
                        templateHtml = thoriumCorePlugin.virtuallist.applyFormulaToField(templateField, templateHtml, key, currentValue);
                    }
                }            

                //Replace Standard Values
                templateHtml = templateHtml.replaceAll("{{" + key + "}}", currentValue);
                templateHtml = templateHtml.replaceAll("{" + key + "}", currentValue);
                templateHtml = templateHtml.replaceAll("{{}}", "");
                templateHtml = templateHtml.replaceAll("{}", "");
            }
            templateHtml = templateHtml.replace(/>{{.*}}</, '>&nbsp;<');
            var rx = new RegExp("({{[\\d\\D]*?\}})", "g");
            templateHtml = templateHtml.replace(rx, "'img/defaultimg.png'");
            return templateHtml;
        },
        renderVirtualListFromData: function (vlref, items, template) {
            //Generic VL Constructor
            var colCount = 1;
            if (window.innerWidth >= 768) {
                colCount = vlref.getAttribute("data-col-count-tablet") || 1;
            } else {
                colCount = vlref.getAttribute("data-col-count") || 1;
            }
            if (!items) {
                thoriumapi.logEvent(1, "[com.thoriumbuilder.core] renderVirtualListFromData initialized with null items");
            }
            var dataLength = items.length || 0;
            thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Rendering Repeater Virtual List [" + dataLength + " rows]");
            var createUL = true;
            var height = thoriumCorePlugin.virtuallist.getVlTemplateHeight(vlref, colCount);
            //Destroy Existing Instance
            var virtualList = app.virtualList.get(vlref);
            if (virtualList) {
                app.virtualList.destroy(virtualList); //Destroy existing instance
            }
            if (!items.length) {
                items.length = 0;
            }
            var templateRef = vlref.querySelector('.templaterow');

            //NEW
            if (!templateRef) {
                templateRef = document.createElement("div");
                templateRef.innerHTML = template;
                document.getElementById('app').appendChild(templateRef);
            }

            var currentLng = document.documentElement.lang || 'en';
            var lg = sessionStorage.getItem('thorium.language') || currentLng;
            var emptyMessage = vlref.getAttribute("data-noresult-" + lg) || null;
            if ((!emptyMessage) || (emptyMessage.length == 0)) {
                emptyMessage = vlref.getAttribute("data-noresult") || 'no results found';
            }

            thoriumCorePlugin.emtyResultTemplate = '<p class="repeater-noresult">' + emptyMessage + '</p>';
            virtualList = app.virtualList.create({
                el: vlref,
                items: items,
                createUl: createUL,
                cols: colCount,
                setListHeight: true,
                height: height,
                // height: height,
                // rowsBefore: 10,
                // rowsAfter: 10,
                emptyTemplate: thoriumCorePlugin.emtyResultTemplate,
                searchAll: function (query, items) {
                    var found = [];
                    for (var i = 0; i < items.length; i++) {
                        var contents = JSON.stringify(items[i]);
                        if (contents.toLowerCase().indexOf(query.toLowerCase()) >= 0 || query.trim() === '') found.push(i);
                    }
                    return found; //return array with matched indexes
                },
                renderItem(item) {
                    return thoriumCorePlugin.virtuallist.renderVlItem(item, template, templateRef, height);
                },
                on: {
                    itemsAfterInsert: function (l, f) {
                        if (typeof dbExpressData == "object") {
                            if ((l) && (l.el)) {
                                dbExpressData.applyUserRolePermissions(l.el);
                            }
                        }
                    }
                },
            });
            var r = virtualList.$el[0];
            if (r) {
                r.setAttribute("data-loaded", true);
                r.setAttribute("data-count", items.length);
            };
            var virtualList = app.virtualList.get(vlref);
            if (virtualList) {

                var setHeight = virtualList.params.height;
                var firstElement = vlref.querySelector("ul li");
                if (firstElement) {
                    var eltHeight = firstElement.offsetHeight || 0;
                    if ((eltHeight > 0) && (eltHeight != setHeight)) {
                        //Reassign actual item height
                        virtualList.params.height = eltHeight;
                        virtualList.update();
                    }
                }
            }
            if (templateRef) { templateRef.remove(); } //NEW
            app.preloader.hide();
        },

    },
    gauge: {
        init: function () {
            var x = document.querySelectorAll(".gauge");
            for (var i = 0; i < x.length; i++) {
                var elt = x[i];
                elt.innerHTML = "";
                var inst = app.gauge.get(elt);
                if (inst) {
                    app.gauge.destroy(elt);
                }
                inst = app.gauge.create({
                    el: elt,
                    type: elt.getAttribute("data-gauge-type") || 'circle',
                    value: elt.getAttribute("data-gauge-value") || "0.5",
                    size: elt.getAttribute("data-gauge-size") || "250",
                    borderColor: elt.getAttribute("data-border-color") || '#2196f3',
                    borderWidth: elt.getAttribute("data-border-width") || 10,
                    valueText: elt.getAttribute("data-value-text") || '50%',
                    valueFontSize: elt.getAttribute("data-value-font-size") || 41,
                    borderBgColor: elt.getAttribute("data-border-bg-color") || '#2196f3',
                    valueFontWeight: elt.getAttribute("data-value-font-weight") || '500',
                    labelText: elt.getAttribute("data-label-text") || '',
                    valueTextColor: elt.getAttribute("data-value-text-color") || '#000000',
                    labelFontSize: elt.getAttribute("data-label-font-size") || '14',
                    labelFontWeight: elt.getAttribute("data-label-font-weight") || '400',
                    labelTextColor: elt.getAttribute(" data-label-text-color") || '#888888',
                })
            }
        }
    },
    actionSheet: {
        show: function (e) {
            var target = e.target;
            var items = target.getAttribute("data-actionsheet-items");
            var json = JSON.parse(items);
            var buttons = [];
            var group1 = [];
            var group2 = [];
            var h = '<div class="actions-group">';
            for (const [key, item] of Object.entries(json)) {
                var label = item.label;
                if (label.includes("{")) {
                    label = thoriumapi.setDynamicVariableContent(label);
                }
                const linktype = item["link-type"];
                const target = item.target;
                const islabel = item.islabel;
                const group = item.group;
                var button = {};
                button.text = label;
                button.label = (islabel == "true");
                button.bold = true,
                    button.targetelement = item.target;
                //button.color="red";
                button.onClick = function () { thoriumCorePlugin.actionSheet.callback(e, item); };
                h += '<div class="actions-button"><div class="actions-button-text">' + item.label + '</div></div>';

                if (group == 'true') {
                    group2.push(button);
                } else {
                    group1.push(button);
                }
            }
            buttons.push(group1);
            if (group2.length > 0) {
                buttons.push(group2);
            }


            h += '</div>';
            var ac3 = app.actions.create({
                buttons: buttons,
            });
            ac3.open(true);
        },
        callback: function (e, item) {
            e.preventDefault();
            if (item["link-type"] == 2) {
                window.open(item.target, "_blank"); //External Page
                if ((app.device.capacitor == false) && (thoriumapi.isSafari() == true)) {
                    if ((window) && (window.webkit)) {
                        var webKitRef = window.webkit;
                        if ((webKitRef) && (app.device.cordova == false) && (app.device.capacitor == false) && (window.webkit.messageHandlers)) {
                            try {
                                window.webkit.messageHandlers.thoriumHandler.postMessage(item.target);
                            }
                            catch (err) { }
                        }
                    }
                }
                e.stopImmediatePropagation();
            } else if (item["link-type"] == 1) { //internal page
                var transition = 'f7-cover-v';
                var link = item.target.replace(".html", "");
                thoriumapi.loadpage(link, {}, transition);
            } else if (item["link-type"] == 15) { //JS
                var options = [];
                if (item.target) {
                    var func = item.target.replace("javascript:", "").replace("()", "");
                    var fn = window[func];
                    var fnparams = [options];
                    if (typeof fn === "function") {
                        thoriumapi.logEvent(0, "[com.thoriumbuilder.core] call function " + func);
                        options = fn.apply(this, fnparams);
                    } else {
                        thoriumapi.logEvent(1, "[com.thoriumbuilder.core] function " + func + " does not exists");
                    }
                }
            } else if (parseInt(item["link-type"]) == 13) {//close CurrentPAge
                app.dialog.alert("close");
            } else if (parseInt(item["link-type"]) == 14) {//dbExpressLogout
                app.dialog.confirm(kLogout + '?', function () {
                    auth.logout();
                });
            } else if ((parseInt(item["link-type"])) < 10) {//Mail...
                window.location.href = item.target;
            } else { //NoThing
                return;
            }

        },
    },



    getMedialistLine: function (item, templateHtml) {
        for (const [key, value] of Object.entries(item)) {
            templateHtml = templateHtml.replaceAll("{{" + key + "}}", value);
        }
        return templateHtml;
    },
    renderVirtualMediaListData: function (field, items) {
        thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Rendering Virtual List Data");
        var template = field.getAttribute('data-template') || null;
        if (!(template)) {
            $("#" + field.id).hide();
            thoriumapi.logEvent(1, "[com.thoriumbuilder.core] No template defined");
            app.dialog.alert("[com.thoriumbuilder.core] No template defined");
            return;
        }
        var iosHeight = field.getAttribute('data-height-ios') || 104;
        var mdHeight = field.getAttribute('data-height-md') || 111;
        var virtualList = app.virtualList.create({
            el: field,
            items: items,
            createUl: true,
            setListHeight: true,
            searchAll: function (query, items) {
                var found = [];
                for (var i = 0; i < items.length; i++) {
                    var contents = JSON.stringify(items[i]);
                    if (contents.toLowerCase().indexOf(query.toLowerCase()) >= 0 || query.trim() === '') found.push(i);
                }
                return found; //return array with matched indexes
            },
            renderItem(item) {
                return thoriumCorePlugin.getMedialistLine(item, template);
            },
            height: app.theme === 'ios' ? iosHeight : (app.theme === 'md' ? mdHeight : 111),
        });
        var r = virtualList.$el[0];
        if (r) {
            r.setAttribute("data-loaded", true);
            r.setAttribute("data-count", items.length);
        };
    },

    getVirtualMediaListData: function (field, source) {
        var url = "";
        var items = [];
        var vl = $("#" + field.id);
        if (!(vl)) {
            return;
        }
        var ref = app.virtualList.get(vl);
        if (ref) {
            app.virtualList.destroy(ref);
        }
        var url = "js/json/" + source;
        thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Loading Virtual List Data from Json at [" + url + "]");
        app.preloader.show();
        fetch(url, {
            method: 'get',
            headers: {
                'Content-type': 'application/json'
            }
        }).then(
            function (response) {
                response.json()
                    .then(data => {
                        thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Received Data from [" + url + "]");
                        var i = 0;
                        var item = {};
                        for (var prop in data) {
                            item = data[prop];
                            item["dataindex"] = i;
                            i = i + 1;
                            items.push(item);
                        }
                        thoriumCorePlugin.renderVirtualMediaListData(field, items);
                        app.preloader.hide();
                    })
                    .catch(function (err) {
                        $("#" + field.id).hide();
                        thoriumapi.logEvent(2, "[com.thoriumbuilder.core] Error [" + err.message + "] While loading data from [" + url + "]");
                        app.dialog.alert(err.message);
                    });
            }).catch(function (err) {
                $("#" + field.id).hide();
                app.preloader.hide();
                thoriumapi.logEvent(2, "[com.thoriumbuilder.core] Error [" + err.message + "]");
                if (app.online == false) {
                    thoriumapi.showNotification("Internet connection lost");
                } else {
                    app.dialog.alert(err.message);
                }
            });
    },

    initVirtualMediaLists: function () {
        var elts = document.querySelectorAll(".virtual-media-list");
        for (var i = 0; i < elts.length; i++) {
            var field = elts[i];
            if ((field.getAttribute("data-loaded") != "true") && (field.getAttribute("data-virtual-list") == "true")) {
                app.preloader.show();
                var source = field.getAttribute('data-json-source') || null;
                if (source) {
                    thoriumCorePlugin.getVirtualMediaListData(field, source);
                }
            }
        }
    },










    appLoaded: function () {
        var event = new Event('appLoaded');
        var myTimer = setInterval(
            function () {
                if (app) {
                    clearInterval(myTimer);
                    document.dispatchEvent(event);
                }
            }
            , 100);
    },


    showPopover: function (e) {
        thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Show Popover");
        var s = e.getAttribute("data-content");
        if ((s)) {
            thoriumCorePlugin.currentPopover = app.popover.create({
                targetEl: e,
                arrow: true,
                verticalPosition: 'bottom',
                backdrop: true,
                closeByBackdropClick: true,
                closeByOutsideClick: true,
                closeOnEscape: true,
                animate: true,
                content: '<div class="popover">' +
                    '<div class="popover-inner">' +
                    s +
                    '</div>' +
                    '</div>',
            });
            thoriumCorePlugin.currentPopover.open();
        }
    },

    webShare: function (e, t) {
        e.preventDefault();
        if (navigator.share == undefined) {
            thoriumapi.logEvent(1, "[com.thoriumbuilder.core] Web Share only possible with https web sites");
            app.dialog.alert("Web Share only possible with https web sites");
        } else {
            var wstitle = t.attr('data-webshare-title') || app.name;
            var url = '';
            var imgref = t.attr('data-webshare-imgref');
            if (imgref) {
                url = $(imgref).attr('src') || '';
            }
            if (url.includes("blob:")==true) {
                return;
            }
            var wstext = "";
            if (!(url)) {
                wstext = t.attr('data-webshare-textref') || window.location.href;
            } else {
                wstext = wstitle;
            }
            thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Call Navigator.share");
            //3.4.1
            try {
                navigator.share({
                    title: wstitle,
                    url: url,
                    text: wstext
                })
            } catch (error) {
                app.dialog.alert("Web Share Error: " + error);
            }
            //3.4.1  
        }
    },


    getText: function (element) {
        if (element.childElementCount > 0) {
            var text = "";
            for (var i = 0; i < element.childNodes.length; ++i) {
                var selt = element.childNodes[i];
                if (selt.nodeType === Node.TEXT_NODE) {
                    text += element.childNodes[i].textContent;
                }
            }
            return text;
        } else {
            return element.textContent;
        }
    },


    initAllAutoComplete: function () {
        var x = document.querySelectorAll(".autocomplete");
        for (var i = 0; i < x.length; i++) {
            var elt = x[i];
            var autoCompleteList = elt.getAttribute("data-autocomplete") || null;
            if ((autoCompleteList) && (autoCompleteList.length > 0)) {
                var arr = autoCompleteList.split(',');
                autocompleteDropdownSimple = app.autocomplete.create({
                    inputEl: elt,
                    openIn: 'dropdown',
                    source: function (query, render) {
                        var results = [];
                        if (query.length === 0) {
                            render(results);
                            return;
                        }
                        // Find matched items
                        for (var i = 0; i < arr.length; i++) {
                            if (arr[i].toLowerCase().indexOf(query.toLowerCase()) >= 0) results.push(arr[i]);
                        }
                        // Render items by passing array with result items
                        render(results);
                    }
                });
            }
        }
    },


    //Forms Image & Files
    uploadFileRead: function (file, sender) {
        var resize = sender.getAttribute('data-resize') || 800;
        var postFile = (sender.getAttribute("data-postfile") == "true") || false;
        if (postFile == true) {
            if (file) {
                var accept = sender.accept.toLowerCase(); //eg audio/* video/* image/*
                var filetype = file.type.toLowerCase(); //eg: image/png  audio/mpeg
                var extension = file.name.split('.').pop().toLowerCase();
                if (accept.includes("audio") == true) {
                    if (filetype.includes("audio") == false) {
                        app.dialog.alert(file.name + " is not a valid Audio file");
                        return;
                    }
                } else if (accept.includes("video") == true) {
                    if (filetype.includes("video") == false) {
                        app.dialog.alert(file.name + " is not a valid Video file");
                        return;
                    }
                } else if (accept.includes("image") == true) {
                    if (filetype.includes("image") == false) {
                        app.dialog.alert(file.name + " is not a valid Image file");
                        return;
                    }
                } else if (accept.includes(extension) == false) {
                    app.dialog.alert(file.name + " is not a valid " + accept + " file");
                    return;
                }
                $('#' + sender.id).data("data-blob", file);
                $('#' + sender.id + "-filename").text(file.name);
                var id = sender.id.replace("-fileinput", "");
                if ($('#' + id + '-remove')) {
                    $('#' + id + '-remove').show();
                };
                $('#' + id + '-fileinput-filename').css("visibility", "visible");
                $('#' + id + '-fileinput-filename').html('<i class="fa f7-icons">paperclip</i><span>' + file.name + '</span>'); //+ " (size:" + files[0].size + " bytes - " + files[0].type + ")"
                $('#' + id + '-fileinput-filename').show();
                $('#' + id + '-fileinput').attr("data-modified", "true");
                return;
            }
        }
        //BlobMode
        var isImg = false;
        var accept = sender.accept.toLowerCase();
        if ((accept.includes("image/png") == true) || (accept.includes("image/jpeg") == true) || (accept.includes("image/gif") == true) || (accept.includes("image/*") == true)) {
            isImg = true;
        }
        var filesize = ((file.size / 1024) / 1024).toFixed(4); // MB
        if (((isImg == true) && (filesize > kImgMaxSize)) || ((isImg == false) && (filesize > kDocMaxSize))) {
            app.dialog.alert("File too large (Max Size Allowed: " + kImgMaxSize + "Mb)");
            return;
        }
        if (isImg) {
            if (!file.type.match(/image.*/)) {
                app.dialog.alert("The dropped file is not an image!");
                return;
            }
            var reader = new FileReader();
            reader.onload = (function (aImg) {
                return function (e) {
                    thoriumCorePlugin.resizeImage(file, resize)
                        .then(function (p) {
                            var id = sender.id.replace("-fileinput", "");
                            var blob = thoriumCorePlugin.dataURItoBlob(p);
                            blob.name = file.name;
                            thoriumCorePlugin.currentBlob = blob;
                            $('#' + id + '-fileinput').data("data-blob", p);
                            $('#' + id + '-fileinput').data('data-type', blob.type || 'image/jpg');
                            $('#' + id + '-fileinput').data("data-filename", file.name);
                            $('#' + id).css("background-image", "url('" + p + "')");
                            if ($('#' + id + '-rotate')) {
                                $('#' + id + '-rotate').css("display", "block");
                            };
                            if ($('#' + id + '-remove')) {
                                $('#' + id + '-remove').css("display", "block");
                            };
                            $('#' + id + '-fileinput').attr("data-modified", "true");
                        });

                };
            })();
            reader.readAsDataURL(file);
        } else {
            var reader = new FileReader();
            reader.onload = (function (f) {
                return function (e) {
                    var id = sender.id.replace("-fileinput", "");
                    var blob = reader.result;
                    $('#' + id + '-fileinput').data("data-blob", blob);
                    $('#' + id + '-fileinput').data('data-type', sender.type);
                    $('#' + id + '-fileinput').data("data-filename", file.name);
                    if ($('#' + id + '-remove')) {
                        $('#' + id + '-remove').show();
                    };
                    $('#' + id + '-fileinput-filename').css("visibility", "visible");
                    $('#' + id + '-fileinput-filename span').text(file.name); //+ " (size:" + files[0].size + " bytes - " + files[0].type + ")"
                    $('#' + id + '-fileinput-filename i').show();
                    $('#' + id + '-fileinput-filename').show();
                    $('#' + id + '-fileinput').attr("data-modified", "true");
                };
            })();
            reader.readAsDataURL(file);
        }
    },

    resizeImage: function (file, maxsize) {
        return new Promise(function (resolve, reject) {
            var dataURL = "x";
            var reader = new FileReader();
            reader.onloadend = function () {
                var buffer = new Image();
                buffer.src = reader.result;
                buffer.onload = function () {
                    var w = buffer.width;
                    var h = buffer.height;
                    if (w > h) {
                        if (w > maxsize) {
                            h *= maxsize / w;
                            w = maxsize;
                        }
                    } else {
                        if (h > maxsize) {
                            w *= maxsize / h;
                            h = maxsize;
                        }
                    }
                    var canvas = document.createElement('canvas');
                    canvas.width = w;
                    canvas.height = h;
                    var ctx = canvas.getContext("2d");
                    ctx.drawImage(this, 0, 0, w, h);
                    var t = file.type || "image/jpg";
                    dataURL = canvas.toDataURL(t);
                    resolve(dataURL);
                };
                buffer.onerror = function (err) {
                    reject("Image Resize Error " + err);
                }
            };
            reader.readAsDataURL(file);
        });
    },

    rotateImage: function (blob, type) {
        return new Promise(function (resolve, reject) {
            var buffer = new Image();
            buffer.src = blob;
            buffer.onload = function () {
                var tempW = buffer.width;
                var tempH = buffer.height;
                var canvas = document.createElement('canvas');
                canvas.width = tempW;
                canvas.height = tempH;
                var ctx = canvas.getContext("2d");
                canvas.width = tempH;
                canvas.height = tempW;
                var ctx = canvas.getContext("2d");
                ctx.translate(tempH, 0);
                ctx.rotate(90 * Math.PI / 180);
                ctx.drawImage(this, 0, 0, tempW, tempH);
                type = type || "image/jpg";
                dataURL = canvas.toDataURL(type);
                resolve(dataURL);
            };
            buffer.onerror = function (err) {
                reject("Unable to rotate Image " + err);
            }
        });
    },

    dataURItoBlob: function (dataURI) {
        var byteString = atob(dataURI.split(',')[1]);
        var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
        var ab = new ArrayBuffer(byteString.length);
        var ia = new Uint8Array(ab);
        for (var i = 0; i < byteString.length; i++) {
            ia[i] = byteString.charCodeAt(i);
        }
        return new Blob([ab], {
            type: mimeString
        });
    },

    /* -- Firebase Upload --*/
    uploadFile: function (f, filename, collection, uid, fieldName, currentRecord) {
        thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Uploading file " + filename + " to Firebase");
        app.preloader.hide();
        var progressBar = app.dialog.progress('');
        filename = filename.replace('C:\\fakepath\\', '');
        var extension = filename.substr((filename.lastIndexOf('.') + 1));
        var storage = firebase.storage();
        var storageRef = storage.ref().child(collection + '/');
        filename = fieldName + "_" + uid + "." + extension;

        var file = thoriumCorePlugin.dataURItoBlob(f);
        var uploadTask = storageRef.child(filename).put(file)
            .catch(function (error) {
                app.emit('onFirestoreUploadError', error, f, filename, collection, uid, fieldName, currentRecord);
                progressBar.close();
                thoriumapi.logEvent(2, "[com.thoriumbuilder.core] Uploading file " + filename + " to Firebase failed: " + error.message);
                app.preloader.hide();
                app.dialog.alert(error.message);
            })
            .then(function (e) {
                e.task.on('state_changed',
                    (snapshot) => {
                        var progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
                        console.log('Upload is ' + progress + '% done');
                        thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Upload is " + progress + '% done');
                        progressBar.setText(progress + '%');
                        progressBar.setProgress(progress);
                    },
                    (error) => {
                        app.emit('onFirestoreUploadError', error, f, filename, collection, uid, fieldName, currentRecord);
                        app.preloader.hide();
                        progressBar.close();
                        thoriumapi.logEvent(2, "[com.thoriumbuilder.firestore] Error while uploading file: " + error.message);
                        app.dialog.alert(error.message);
                    },
                    () => {
                        thoriumapi.logEvent(0, "[com.thoriumbuilder.firestore] file uploaded, retriveing information...: ");
                        var fileRef = storageRef.child(filename);
                        fileRef.getDownloadURL()
                            .then(function (downloadURL) {
                                thoriumapi.logEvent(0, "[com.thoriumbuilder.firestore] updating file information...");
                                currentRecord.update({
                                    [fieldName]: downloadURL
                                })
                                    .catch(function (error) {
                                        app.emit('onFirestoreUploadError', error, f, filename, collection, uid, fieldName, currentRecord);
                                        app.preloader.hide();
                                        thoriumapi.logEvent(2, "[com.thoriumbuilder.firestore] Error while uploading file: " + error.message);
                                        app.dialog.alert("Error when updating Document: " + error.message);
                                        return;
                                    });
                                setTimeout(function () {
                                    app.emit('onFirestoreUploadSuccess', f, filename, collection, uid, fieldName, currentRecord);
                                    progressBar.close();
                                    thoriumapi.logEvent(0, "[com.thoriumbuilder.firestore] file uploaded succesfully");
                                }, 1000);
                            })
                            .catch(function (error) {
                                app.emit('onFirestoreUploadError', error, f, filename, collection, uid, fieldName, currentRecord);
                                progressBar.close();
                                app.preloader.hide();
                                thoriumapi.logEvent(2, "[com.thoriumbuilder.firestore] Error when updating Document: " + error.message);
                                app.dialog.alert("Error when updating Document: " + error.message);
                            })
                    });
            });
    },

} //End object

/*-- E V E N T S --*/

/*-- Forms--*/
$(document).on('mousedown', 'input[readonly]', function (e) {
    e.preventDefault();
});

$(document).on('submit', '.form', function (e) {
    //Data Mode
    var dataMode = e.target.getAttribute("data-mode"); //0=formData 1: JSON 2
    if ((dataMode=="0") || (dataMode=="1") || (dataMode=="2") ) {
        try {
            thoriumCorePlugin.forms.submitForm(e,e.target);
        } catch (error) {
            app.preloader.hide();
            thoriumapi.logEvent(2, "[com.thoriumbuilder.core] Form error: " + error);
            app.dialog.alert("Form error: " + error);
        }
    }
});

$(document).on('click', ".form-submit-button", function (e) { //-- Submit button detached --
    e.preventDefault();
    var id = this.getAttribute('data-form') || '';
    var form = document.getElementById(id);
    if ((form) && (form.length > 0)) {
        form.dispatchEvent(new Event('submit', { 'bubbles': true }));
    }
});
// -- geoloc input type
$(document).on("click", "input[type='geo']", function (e) {
    e.preventDefault();
    var g = $("#" + e.target.id).data('pos');
    if (g) {
        var url = "https://www.google.com/maps/search/?api=1&query=" + g.coords.latitude + "," + g.coords.longitude
        var win = window.location = url;
    }
});
/*-- Forms--*/

/*-- Action Sheet--*/
$(document).on('click', '.dynamic-action-sheet', function (e) {
    e.preventDefault;
    thoriumCorePlugin.actionSheet.show(e);
});


/*-- patch isos12.2 WebApp link issue --*/
$(document).on('click', 'a[href^=mailto],a[href^=tel],a[href^=sms],a[href^=skype],a[href^=facetime]', function (e) {
    if ((app.device.webView == true) && (app.device.ios == true)) {
        e.preventDefault();
        var href = this.getAttribute("href");
        if (href) {
            window.location = href;
        }
        return false;
    }
});



$(document).on('click', '.virtual-list .link', function (e) {
    //Links within Repeater
    e.stopPropagation();
    e.stopImmediatePropagation();
});

/*-- File Upload Management--*/
$(document).on('dragenter', '.firebase_document_preview , .firebase_fileupload-dropzone ', function (e) {
    e.preventDefault();
    this.style.opacity = 0.5;
});
$(document).on('dragover', '.firebase_document_preview , .firebase_fileupload-dropzone', function (e) {
    e.preventDefault();
    this.style.opacity = 0.5;
    return false;
});
$(document).on('dragover', '.firebase_document_input', function (e) {
    e.preventDefault();
    var x = document.querySelectorAll(".firebase_fileupload-dropzone");
    for (var i = 0; i < x.length; i++) {
        x[i].style.opacity = 0.5;
    }
});
$(document).on('dragleave', '.firebase_document_preview , .firebase_fileupload-dropzone', function (e) {
    e.preventDefault();
    var x = document.querySelectorAll(".firebase_fileupload-dropzone");
    for (var i = 0; i < x.length; i++) {
        x[i].style.opacity = 1;
    }
});
$(document).on('click', '.firebase_document_preview, .firebase_fileupload-dropzone', function (e) {
    e.preventDefault();
    app.tooltip.hide(".firebase_document_input");
});
$(document).on('click', '.form-img-add-btn', function (e) {
    if ((thoriumapi.isLocal() == true) && ((app.device.ios) || (app.device.android))) {
        app.dialog.alert("Function not available in simulator, use drag&drop instead")
    }
});
$(document).on('click', '.firebase_document_input', function (e) {
    app.tooltip.hide(".firebase_document_input");
    if ((thoriumapi.isLocal() == true) && ((app.device.ios) || (app.device.android))) {
        var iconTooltip = app.tooltip.create({
            targetEl: this,
            text: 'Drop your file here',
        });
        iconTooltip.show();
    }
});
$(document).on('click', '.firebase_fileupload-rotate', function (e) {
    e.preventDefault();
    var target = $(this).attr("data-fileinput-ref");
    var o = $('#' + target);
    var f = $('#' + target + '-fileinput');
    if (!(o) || !(f)) {
        return;
    }
    var blob = f.data("data-blob");
    var type = f.data("data-type");
    app.preloader.show();
    setTimeout(function () {
        thoriumCorePlugin.rotateImage(blob, type)
            .then(function (p) {
                app.preloader.hide();
                o.css('background-image', 'url(' + p + ')');
                f.data("data-blob", p);
            })
            .catch(function (error) {
                app.preloader.hide();
                app.dialog.alert("Error image: " + error.message);
            });
    }, 500);
});
$(document).on('drop', '.firebase_document_preview , .firebase_fileupload-dropzone', function (event) {
    event.preventDefault();
    this.style.opacity = 1;
    var input = document.getElementById(event.target.id + "-fileinput");
    thoriumCorePlugin.uploadFileRead(event.dataTransfer.files[0], input);
    event.stopPropagation();
});
$(document).on('drop', '.firebase_document_input', function (event) {
    event.preventDefault();
    var dropzone = document.querySelector(".firebase_fileupload-dropzone");
    if (dropzone) { dropzone.style.opacity = 1; }
    thoriumCorePlugin.uploadFileRead(event.dataTransfer.files[0], this);
});
$(document).on('change', '.firebase_document_input', function (event) {
    event.preventDefault();
    thoriumCorePlugin.uploadFileRead(event.target.files[0], event.target);
});
$(document).on('click', '.firebase_fileupload-remove', function (e) {
    e.preventDefault();
    var me = $(this);
    var di = me.attr('data-fileinput-ref');
    var o = $("#" + di);
    if (o) {
        app.dialog.confirm('Remove file?', function () {
            $('#' + di + '-fileinput').data("data-blob", null);
            $('#' + di + '-fileinput').data("data-type", null);
            $('#' + di).attr('data-clear', true);
            $('#' + di + '-fileinput').data("data-filename", null);
            $('#' + di + '-fileinput-filename').css("visibility", "hidden");
            $('#' + di + '-fileinput-filename span').text("");
            $('#' + di + '-fileinput-filename i').hide();
            $('#' + di).css("background-image", "");
            if ($('#' + di + '-rotate')) {
                $('#' + di + '-rotate').hide();
            };
            $('#' + di + '-fileinput').attr("data-modified", "true");
            me.hide();
        });
    }
});
/*-- File Management--*/


//debugLog
$(document).on("click", "#tb_debug_log", function (e) {
    e.preventDefault();
    var debugPopup = app.sheet.get('.debug-popup');
    if (debugPopup) {
        if (debugPopup.opened == true) {
            debugPopup.close();
        } else {
            debugPopup.open();
        }

    } else {
        debugPopup = app.sheet.create({
            el: '.debug-popup',
            swipeToClose: false,
            backdrop: true,
            closeByBackdropClick: true,
            closeByOutsideClick: false,
            closeOnEscape: true,
            animate: true,
        });
        debugPopup.open(true);
        var node = document.querySelector(".debug-popup .debug-log");
        if (node) {
            node.scrollTop = node.scrollHeight;
        }

    }
});


$(document).on("click", ".btncloseinfowindow", function (e) {
    e.preventDefault();
    element = document.querySelector(".custominfowindow");
    if (element) {
        element.style.display = "none";
    }
});

$(document).on("click", ".webshare", function (e) {
    thoriumCorePlugin.webShare(e, $(this));
});

$(document).on('click', '.img-zoom', function (e) {
    if (this.classList.contains("dbexpress-image") == true) {
        if (typeof dbExpressData == "object") {
            var vl = this.closest(".dbexpress-virtual-list-content");
            if (vl) {
                dbExpressData.showVerticalRepeaterZoom(vl, this);
                return;
            }
        }
    }
    thoriumCorePlugin.photobrowser.showImageZoom(e);
    e.stopPropagation();
    e.stopImmediatePropagation();
});

$(document).on('click', '.collection-item-zoom', function (e) {
    thoriumCorePlugin.photobrowser.showCollectionZoom(this, e);
});

$(document).on('click', '.medialist-zoom', function (e) {
    thoriumCorePlugin.photobrowser.showMedialistZoom(this, e);
});

$(document).on('click', '.card-opened', function (e) {
    e.preventDefault;
    app.card.close($(this), true);
});

$(document).on('click', '.dynamic-popover', function (e) {
    e.preventDefault;
    thoriumCorePlugin.showPopover(this);
});

$(document).on('click', '.popover li a', function (e) {
    if (thoriumCorePlugin.currentPopover != null) {
        app.popover.close(thoriumCorePlugin.currentPopover.el, true);
    }
});


$(document).on("click", ".segmented .button", function (e) {
    var parent = $(this).closest(".segmented").children('.button-active').removeClass('button-active');
    $(this).addClass('button-active');
});

$(document).on('mouseover', "* [data-transform-trigger='mouseover']", function (e) {
    thoriumCorePlugin.effects.execute(this);
});
$(document).on('mouseout', "* [data-transform-trigger='mouseover']", function (e) {
    thoriumCorePlugin.effects.reset(this);
});

$(document).on('click', "* [data-transform-trigger='mouseclick']", function (e) {
    var effect = $(this).attr('data-transform-effect') || '';
    this.style.transform = effect;
});

document.addEventListener('scroll', function (event) {
    var elt = event.target;
    thoriumCorePlugin.effects.scrolleffect(elt);
}, true);
/* -- Effects --*/

$(document).on('click', "* [data-translate-language]", function (e) {
    var lg = e.target.getAttribute("data-translate-language") || null;
    var dir = e.target.getAttribute("data-translate-direction") || null;
    if (kMultilingual == true) {
        thoriumCorePlugin.translations.translate(lg, dir);
    }
});

app.on('offline', function () {
    thoriumapi.logEvent(1, "[com.thoriumbuilder.core] Internet connection lost");
});

app.on('online', function () {
    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Internet connection back");
});

$(document).on("change", ".capture_button input", function (e) {
    e.preventDefault();
    if (e.target.files) {
        thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Media Capture in Progress...");
        var file = e.target.files[0];
        var url = URL.createObjectURL(file);
        var mediaType = this.getAttribute("accept");
        if (mediaType == "image/*") {
            var reader = new FileReader();
            reader.addEventListener("load", function () {
                // convert image file to base64 string
                var parent = e.target.closest(".capture_button");
                app.emit('captureSuccess', parent, reader.result);
            }, false);
            if (file) {
                reader.readAsDataURL(file);
            }
        } else if (mediaType == "video/*") {
            var reader = new FileReader();
            reader.onload = function (event) {
                // The file reader returns an ArrayBuffer:
                var buffer = event.target.result;
                // We convert the buffer to a blob:
                var videoBlob = new Blob([new Uint8Array(buffer)], {
                    type: 'video/mp4'
                });
                // The blob gives us a URL to the video file:
                var url = window.URL.createObjectURL(videoBlob);
                var parent = e.target.closest(".capture_button");
                app.emit('captureSuccess', parent, url);
            }
            if (file) {
                reader.readAsArrayBuffer(file);
            }
        } else {
            thoriumapi.logEvent(1, "[com.thoriumbuilder.core] Capture Button Media Type not set");
        }
    }
});

$(document).on('click', '.capture_button', function (e) {
    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Capture Button Click event");
    if ((thoriumapi.isLocal() == true) && ((app.device.ios) || (app.device.android))) {
        var iconTooltip = app.tooltip.create({
            targetEl: this,
            text: 'Function not available with the Simulator',
        });
        iconTooltip.show();
        thoriumapi.logEvent(1, "[com.thoriumbuilder.core] Capture Buttons doesn't work in Simulator");
    }
});

$(document).on('click', '.container-link', function (e, page) {
    e.preventDefault;
    e.stopImmediatePropagation();
    var link = this.getAttribute("data-container-link");
    var linktype = this.getAttribute("data-container-link-type");
    if (link) {
        if (linktype == 2) {
            var target = this.getAttribute("data-container-target") || "_system";
            window.open(link, "_blank"); //External Page
            if ((app.device.capacitor == false) && (thoriumapi.isSafari() == true)) {
                if ((window) && (window.webkit)) {
                    var webKitRef = window.webkit;
                    if ((webKitRef) && (app.device.cordova == false) && (app.device.capacitor == false) && (window.webkit.messageHandlers)) {
                        try {
                            window.webkit.messageHandlers.thoriumHandler.postMessage(link);
                        }
                        catch (err) { }
                    }
                }
            }
            e.stopImmediatePropagation();
        } else if (linktype == 1) { //internal page
            var transition = this.getAttribute("data-container-transition") || '';
            link = link.replace(".html", "");
            thoriumapi.loadpage(link, {}, transition);
        } else { //Mail...
            window.location.href = link;
        }
    }
});

$(document).on('click', '.container-link a', function (e, page) {
    e.preventDefault;
    e.stopImmediatePropagation();
});

$(document).on("tab:show", ".tab", function (e) {
    app.emit('tabShow', this);
    //Refresh hidden OSM maps
    if (typeof openStreetMapPlugin != 'undefined') {
        var x = this.querySelectorAll(".openstreetmap-api");
        for (var i = 0; i < x.length; i++) {
            var elt = x[i];
            var mo = document.getElementById(elt.id);
            if ((mo) && (mo.data)) {
                mo.data.invalidateSize();
                thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Refresh OSM Map with ID [" + elt.id + "]");
            }
        }
    }
    var x = this.querySelectorAll(".virtual-list");
    for (var i = 0; i < x.length; i++) {
        var elt = x[i];
        var virtualList = app.virtualList.get(elt);
        if (virtualList) {
            var setHeight = virtualList.params.height;
            var firstElement = elt.querySelector("li");
            if (firstElement) {
                var eltHeight = firstElement.offsetHeight || 0;
                if ((eltHeight > 0) && (eltHeight != setHeight)) {
                    //Reassign actual item height
                    virtualList.params.height = eltHeight;
                    virtualList.update();
                }
            }
        }
    }


});

$(window).on('resize', function (e) {
    thoriumCorePlugin.app.resize();
});

/*-- TEXT ZOOM --*/
function showTextZoom(e) {
    e.style.whiteSpace = "break-spaces";
    var t = e.innerText;
    var elt = document.querySelector(".text-viewer-popup .block-text");
    if (elt) { elt.innerText = t; }
    var textPopup = app.sheet.create({
        el: '.text-viewer-popup',
        swipeToClose: false,
        backdrop: true,
        closeByBackdropClick: true,
        closeByOutsideClick: true,
        closeOnEscape: true,
        animate: true,
    });
    textPopup.open(true);
    e.style.whiteSpace = "normal";

}
$(document).on("click", ".text-viewer", function (e) {
    e.preventDefault();
    showTextZoom(this);
});
$(document).on("click", ".text-viewer-popup .text-viewer-bigger", function (e) {
    e.preventDefault();
    var elt = document.querySelector(".text-viewer-popup .block-text");
    if (elt) {
        var currentSize = parseInt(elt.style.fontSize) || 22;
        currentSize = currentSize + 1;
        elt.style.fontSize = currentSize + "px";
    }
});

$(document).on("click", ".text-viewer-popup .text-viewer-smaller", function (e) {
    e.preventDefault();
    var elt = document.querySelector(".text-viewer-popup .block-text");
    if (elt) {
        var currentSize = parseInt(elt.style.fontSize) || 17;
        currentSize = currentSize - 1;
        if (currentSize <= 8) { currentSize = 8; }
        elt.style.fontSize = currentSize + "px";
    }
});
/*-- TEXT ZOOM --*/

/* FORMS */
$(document).on("click", "input[data-number-input-type='2']", function (e) {
	e.preventDefault();
  var kp=app.keypad.create({
    inputEl: e.target,
	backdrop: true,
    type: 'calculator',
    toolbar: false,
  });
  kp.open();
});

$(document).on("click", "input[data-number-input-type='1']", function (e) {
	e.preventDefault();
  var kp=app.keypad.create({
    inputEl: e.target,
	backdrop: true,
    type: 'numpad',
    toolbar: false,
  });
  kp.open();
});


/* -- PAGE EVENTS-- */
$(document).on('page:afterin', '.page', function (e, page) {
    if ((page.name != "index") && (page.direction != "backward")) {
        thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Page " + page.name + " is loaded");
        if ((app.device.cordova == true) || (app.device.capacitor == true)) {
            $('.webshare').hide();
        }
        //Hack for iOS bug with scrollbar position with WebView
        if (((app.device.cordova == true) || (app.device.capacitor == true)) && (app.device.ios == true)) {
            $(".page-current .page-content").css("overflow-y", "hidden");
            setTimeout(function () {
                $(".page-current .page-content").css("overflow-y", "auto");
            }, 200);
        }
        thoriumCorePlugin.effects.init();
    }
    if (page.name == "index") {
        var element = document.querySelector(".page-content");
        if (element) {
            setTimeout(function () {
                if (page.direction == "backward") {
                    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] back to Index");
                }
                element.style.overflowY = "scroll";
                document.documentElement.style.overflowY = "hidden";
                document.body.overflowY = "hidden";
                app.preloader.hide();
            }, 200);
        }
    }
});

$(document).on('page:beforeout', function (e) {
    var page = e.detail;
    $('.custominfowindow').hide();
    if (page.direction == "backward") {
        return;
    }
    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Page " + page.name + " will close (beforeout)");
    var x = document.querySelectorAll(".audio");
    for (var i = 0; i < x.length; i++) {
        var audio = x[i];
        if (audio) {
            audio.pause();
        }
    }
})

$(document).on('page:beforein', function (e) {
    var page = e.detail;
    if (page.direction == "backward") {
        return;
    }
    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Page " + page.name + " will open (beforein)");
    $('.custominfowindow').hide();
    if (kAutoClosePanels == true) {
        var pl = app.panel.get('#panel-left');
        if (pl) {
            pl.close(true);
        }
        var pr = app.panel.get('#panel-right');
        if (pr) {
            pr.close(true);
        }
    }
    var x = document.querySelectorAll(".audio");
    for (var i = 0; i < x.length; i++) {
        var audio = x[i];
        if (audio) {
            audio.pause();
        }
    }
})

$(document).on('page:mounted', function (e) {
    var page = e.detail;
    if (page.name != "index") {
        thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Page " + page.name + " is mounted");
        if (kMultilingual == true) {
            thoriumCorePlugin.translations.translate();
        }
        thoriumCorePlugin.swiper.init();
        thoriumCorePlugin.calendars.init();
        thoriumCorePlugin.timepicker.init();
        thoriumCorePlugin.texteditor.init();
        thoriumCorePlugin.app.pullToRefreshHandle(e);
        thoriumCorePlugin.forms.init(e);
        thoriumCorePlugin.initVirtualMediaLists();
        thoriumCorePlugin.gauge.init();
        thoriumCorePlugin.initAllAutoComplete();
    }
});
app.on('pageInit', function (e) {
    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] page:init event");
});
app.on('pageReinit', function (e) {
    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] page:reinit event");
    thoriumCorePlugin.calendars.init();
})
app.on('pageBeforein', function (e) {
    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] page:beforein event");
})
app.on('pageAfterin', function (e) {
    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] page:afterin event");
})
app.on('pageBeforeout', function (e) {
    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] page:beforeout event");
})
app.on('pageAfterout', function (e) {
    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] page:afterout event");
})
app.on('pageBeforeunmount', function (e) {
    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] page:beforeunmount event");
})
app.on('pageBeforeremove', function (e) {
    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] page:beforeremove event");
})
/* -- PAGE EVENTS-- */


window.onerror = function myErrorHandler(errorMsg, url, lineNumber, ColumnNumber) {
    thoriumapi.handleError(errorMsg, url, lineNumber, ColumnNumber);
    return false;
}

document.addEventListener('deviceready', thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Device Ready"));
document.addEventListener('DOMContentLoaded', thoriumapi.logEvent(0, "[com.thoriumbuilder.core] DOM Content Loaded"));
document.addEventListener('appLoaded', function (e) {
    thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Application dependencies Loaded");
    thoriumCorePlugin.app.init();
    app.emit('appLoaded', e);
    setTimeout( function(){ thoriumCorePlugin.app.showAppLoader(false); } , 1000);
}, false);