/*!
 * Thorium dbExpress
 * API version : 4.0.3
 * Thorium version 4.0.3 june 2024
 * framework7 v8.x (https://framework7.io) MIT Licensed
 * Thorium builder© Copyright 2018-2024 Nymphide Lab, All Rights Reserved.
*/

var auth = {
    name: "dbExpress Authentification",
    bundleid: "com.thoriumbuilder.dbexpress.auth",
    version: "4.0.3",
    allowRegister: true,
    currentUser: {
        uid: '',
        displayName: '',
        email: '',
        emailVerified: '',
        isAnonymous: '',
        creationTime: '',
        modifyTime: '',
        lastSignInTime: '',
        language: '',
        phoneNumber: '',
        photoURL: '',
        token: '',
        headimage: '',
        group: 0,
    },
    loginScreen: null,
    twoFactorsScreen: null,
    registerScreen: null,
    profileScreen: null,
    privacyScreen: null,
    termsScreen: null,
    resetPassworScreen: null,
    pinKeyPad: null,
    httpMediaRoot: "",

    //Get the current Token, generic function
    getToken: function () {
        var storage = localStorage.getItem(app.id);
        var token;
        if (storage) {
            var session = JSON.parse(storage);
            if (session.token) {
                token = session.token;
                return token;
            }
        }
    },
    //Return the correct path url for db assets
    getDbAssetsUrlRoot: function () {
        if ((app.device.cordova == true) || (app.device.capacitor == true)) { //Native App
            return kRemoteHost + "db/dbassets/";
        } else if (thoriumapi.isLocal() == true) {
            return "db/dbassets/";
        } else { //PWA
            return "db/dbassets/";
        }
    },

    showResetPasswordScreen: function (e) {
        app.preloader.hide();
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress.auth] Loading Reset Password Popup");
        auth.resetPassworScreen = app.sheet.create({
            el: '.dbexpress-resetpassword',
            swipeToClose: false,
            backdrop: true,
            closeByBackdropClick: false,
            closeByOutsideClick: false,
            closeOnEscape: false,
            animate: true,
        });
        auth.resetPassworScreen.open(true);
        app.emit('onShowResetPasswordScreen', e);
    },

    showSigninPopup: function (e) {
        app.preloader.hide();
        thoriumCorePlugin.app.showAppLoader(true);
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress.auth] Loading Login Popup");
        if (!auth.loginScreen) {
            auth.loginScreen = app.loginScreen.create({
                el: '.dbexpress-signin',
            });
        }
        auth.loginScreen.open(false);
        app.emit('onShowLoginScreen', e);
        var emailInput = document.querySelector(".dbexpress-signin .dbexpress-signin-email");
        if (emailInput) {
            //emailInput.focus();
        }
        setTimeout(function () {
            thoriumCorePlugin.app.showAppLoader(false);
        }, 1000);
    },

    showTermsPopup: function (e) {
        app.preloader.hide();
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress.auth] Loading Terms of Service Popup");
        auth.termsScreen = app.popup.create({
            el: '.dbexpress-termsofuse',
            swipeToClose: false,
            backdrop: true,
            closeByBackdropClick: false,
            closeByOutsideClick: false,
            closeOnEscape: false,
            animate: true,
        });
        auth.termsScreen.open(true);
        app.emit('onShowTermsOfService', this);
    },

    showPrivacyPopup: function (e) {
        app.preloader.hide();
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress.auth] Loading Privacy Policy Popup");
        auth.privacyScreen = app.popup.create({
            el: '.dbexpress-privacypolicy',
            swipeToClose: false,
            inputReadOnly: true,
            backdrop: false,
            closeByBackdropClick: false,
            closeByOutsideClick: false,
            closeOnEscape: false,
            animate: true,
        });
        auth.privacyScreen.open(true);
        app.emit('onShowPrivacyPolicy', this);
    },

    setKeypad: function (e) {
        auth.pinKeyPad = app.keypad.create({
            inputEl: '#dbexpress-twofactors-pin',
            containerEl: '.numpad-inline-container',
            toolbar: false,
            backdrop: true,
            closeByBackdropClick: false,
            closeByOutsideClick: false,
            valueMaxLength: 4,
            dotButton: false,
            dotCharacter: ".",
            formatValue: function (value) {
                value = value.toString();
                return ('...').substring(0, value.length) + ('    ').substring(0, 4 - value.length);
            },
            on: {
                change(keypad, value) {
                    value = value.toString();
                    if (value.length === 4) {
                        if ((parseInt(value) > 0) && (value.length == 4)) {
                            var email = $('#dbexpress-signin-email').val();
                            var password = $('#dbexpress-signin-password').val();
                            auth.signIn(email, password, auth.currentUser.token, parseInt(value));
                        }
                    }
                }
            }
        });
        auth.pinKeyPad.open();
    },

    showTwoFactorsScreen: function (animate) {
        animate = animate || true;
        app.preloader.hide();
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress.auth] Loading two Factors  Popup");
        auth.twoFactorsScreen = app.popup.create({
            el: '.dbexpress-twofactors',
            swipeToClose: false,
            backdrop: true,
            closeByBackdropClick: false,
            closeByOutsideClick: false,
            closeOnEscape: false,
            animate: animate,
            on: {
                opened() {
                    auth.setKeypad();
                }
            }
        });
        auth.twoFactorsScreen.open(true);
        app.emit('onShow2FactorsScreen', this);
    },

    showProfileScreen: function (e) {
        app.preloader.hide();
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress.auth] Loading User Profile Popup");
        auth.profileScreen = app.popup.create({
            el: '.dbexpress-profile',
        });
        auth.profileScreen.open(true);
        app.emit('onShowProfileScreen', e);
    },

    showRegisterPopup: function (e) {
        app.preloader.hide();
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress.auth] Loading Register Popup");
        auth.registerScreen = app.popup.create({ el: '.dbexpress-register' });
        auth.registerScreen.open(true);
        app.emit('onShowRegisterScreen', e);
    },

    initialize: function () {
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress.auth] dbExpress Authentification initialization");
        thoriumCorePlugin.app.showAppLoader(true);
        var token = auth.getToken();
        if (token) {
            auth.checkCurrentToken(token);
            return;
        } else {
            auth.signIn(null, null, null, null);
        }
    },

    checkCurrentToken: function (token) {
        //Check if current session token stored in local storage is still valid 
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress.auth] Checking token...");
        thoriumCorePlugin.app.showAppLoader(true);
        var url = thoriumCorePlugin.httpRoot + kApiRoot + kAuthManagerApi;
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress.auth] calling " + url);
        var formData = new FormData();
        formData.append("token", token);
        fetch(url, {
            method: 'post',
            body: formData,
            contentType: 'multipart/form-data',
        })
        .then(thoriumapi.status)
        .then(thoriumapi.json)
        .then(function (data) {
            if (data) {
                var result = data.code;
                if (result == -1012) {
                    thoriumapi.logEvent(1, "Invalid token: " + data.message + " will try to Signin");
                    auth.signIn(null, null, null, null);
                } else if (result == -1022) { //Not and Administrator
                    auth.showSigninPopup();
                    thoriumapi.logEvent(1, "Connection refused: " + data.message);
                    return;
                } else if (result == -1019) { //connection with token but with invalid PIN
                    thoriumapi.logEvent(1, "Connection Error: " + data.message + " connecting as Anonymous...");
                    auth.signIn(null, null, null, null);
                    return;
                } else if (result < 0) {
                    auth.showSigninPopup();
                    thoriumapi.logEvent(2, "Connection Error: " + data.message);
                }
                else if (result == 0) {
                    //Connection OK 
                    var item = {};
                    item.token = data.token;
                    auth.currentUser.uid = data.uid;
                    auth.currentUser.displayName = data.displayName;
                    auth.currentUser.email = data.email;
                    auth.currentUser.emailVerified = (data.emailVerified == 1);;
                    auth.currentUser.isAnonymous = (data.isAnonymous == 1);
                    auth.currentUser.creationTime = data.creationTime;
                    auth.currentUser.modifyTime = data.modifyTime;
                    auth.currentUser.lastSignInTime = data.lastSignInTime;
                    auth.currentUser.language = data.language;
                    auth.currentUser.phoneNumber = data.phoneNumber;
                    auth.currentUser.photoURL = data.photoURL;
                    auth.currentUser.token = data.token;
                    auth.currentUser.headimage = data.headimage;
                    auth.currentUser.group = data.group || 0;
                    if ((data.photoURL) && (data.photoURL.length > 0)) {
                        var $ta = auth.getDbAssetsUrlRoot() + data.photoURL + "?rnd=" + Math.floor(Date.now() / 1000);
                        $('.dbexpress_avatar').attr('src', $ta);
                    } else {
                        $('.dbexpress_avatar').attr('src', 'img/defaultavatar.png');
                    }
                    $('.dbexpress_avatar').attr('title', auth.currentUser.displayName);
                    $('.user-popover-name').text(auth.currentUser.displayName);

                    try {
                        localStorage.setItem(app.id, JSON.stringify(item));
                    } catch (error) {
                        app.dialog.alert(error);
                    }
                    thoriumCorePlugin.app.showAppLoader(false);
                    app.emit("onAuthStateChanged", auth.currentUser);
                }
                dbExpressData.applyUserRolePermissions();
            } else {
                app.preloader.hide();
                thoriumCorePlugin.app.showAppLoader(false);
                thoriumapi.logError(true, "Invalid result");
                auth.showSigninPopup();
            }
        }).catch(function (error) {
            app.preloader.hide();
            thoriumCorePlugin.app.showAppLoader(false);
            thoriumapi.logError(true, "Invalid result " + error.message);
            auth.showSigninPopup();
        });
    },

    getCountryList: function () {
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress.auth] loading countries list...");
        var formData = new FormData();
        formData.append("service", "_countries");
        formData.append("token", auth.currentUser.token);
        var url = thoriumCorePlugin.httpRoot + kApiRoot + kGetDataApi + "?rnd=" + Math.floor(Date.now() / 1000);
        fetch(url, {
            method: 'post',
            body: formData,
            contentType: 'multipart/form-data',
        })
            .then(thoriumapi.status)
            .then(thoriumapi.json)
            .then(function (data) {
                if (thoriumapi.checkResult(data) == true) {
                    var r = data.result;
                    var select = document.getElementById('dbexpress-profile-country');
                    if (r) {
                        r.forEach(function (item) {
                            var option = document.createElement('option');
                            option.value = item.iso;
                            option.text = item.nicename;
                            select.appendChild(option);
                        });
                    }
                    thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress.auth] countries list loaded");
                }
            }).catch(function (error) {
                thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress.auth] Request failed " + error);
                app.preloader.hide();
                app.dialog.alert(error.message);
            });
    },


    registerFormPost: function () {
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress.auth] Calling Register web service");
        var formData = new FormData();
        var pw = $(".dbexpress-register-password").val();
        var email = $(".dbexpress-register-email").val();
        var displayname = $(".dbexpress-register-displayname").val();
        formData.append("email", email);
        formData.append("password", pw);
        formData.append("displayname", displayname);
        if (auth.currentUser.token.length > 0) {
            formData.append("token", auth.currentUser.token);
        }
        var url = thoriumCorePlugin.httpRoot + kApiRoot + kRegisterApi;
        fetch(url, {
            method: 'post',
            body: formData,
            contentType: 'multipart/form-data',
        })
            .then(thoriumapi.status)
            .then(thoriumapi.json)
            .then(function (data) {
                if (!data) {
                    thoriumapi.logError(true, "Invalid Server Response (empty)");
                    localStorage.clear();
                    return;
                } else if (data.code < 0) {
                    thoriumapi.logError(true, data.message); //-1030=>User already exists
                    localStorage.clear();
                    return;
                } else if (data.code == 1) { //Register success but requires PIN
                    $('#dbexpress-signin-email').val(email);
                    $('#dbexpress-signin-password').val(pw);
                    app.preloader.hide();
                    auth.showSigninPopup();
                    if (auth.registerScreen) { auth.registerScreen.close(); }
                } else if (data.code == 0) {
                    //Save to Local Storage
                    var item = {};
                    item.token = data.token;
                    localStorage.setItem(app.id, JSON.stringify(item));
                    //Set currentUser Object
                    auth.currentUser.uid = data.uid;
                    auth.currentUser.displayName = data.displayName;
                    auth.currentUser.email = data.email;
                    auth.currentUser.emailVerified = (data.emailVerified == 1);
                    auth.currentUser.isAnonymous = (data.isAnonymous == 1);
                    auth.currentUser.creationTime = data.creationTime;
                    auth.currentUser.modifyTime = data.modifyTime;
                    auth.currentUser.lastSignInTime = data.lastSignInTime;
                    auth.currentUser.language = data.language;
                    auth.currentUser.phoneNumber = data.phoneNumber;
                    auth.currentUser.photoURL = data.photoURL;
                    auth.currentUser.headimage = data.headimage;
                    auth.currentUser.group = data.group || 0;
                    if (auth.registerScreen) { auth.registerScreen.close(); }
                    $('#dbexpress-signin-email').val(email);
                    $('#dbexpress-signin-password').val(pw);
                    thoriumCorePlugin.app.showAppLoader(false);
                    app.preloader.hide();
                    auth.showSigninPopup();
                }
            }).catch(function (error) {
                thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress.auth] Request failed " + error);
                app.preloader.hide();
                app.dialog.alert(error.message);
            });
    },

    resetPasswordSubmitPost: function (form) {
        var felt = document.querySelector('.dbexpress-resetpassword-form #dbexpress-resetpassword-email');
        var em = null;
        if (felt) {
            var em = felt.value;
        }
        var formData = new FormData();
        formData.append("email", em);
        var url = thoriumCorePlugin.httpRoot + kApiRoot + kresetPassword;
        fetch(url, {
            method: 'post',
            body: formData,
            contentType: 'multipart/form-data',
        })
            .then(thoriumapi.status)
            .then(thoriumapi.json)
            .then(function (data) {
                app.preloader.hide();
                if (!data) {
                    thoriumapi.logError(true, "Invalid Server Response (empty)");
                    localStorage.clear();
                    return;
                } else if (data.code < 0) {
                    thoriumapi.logError(true, "Error " + data.message);
                } else if (data.code == 0) {
                    if (auth.resetPassworScreen) {
                        auth.resetPassworScreen.close();
                    }
                    thoriumapi.showToast(kEmailSent, true, "bottom", 4000, null);
                }
            }).catch(function (error) {
                thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress.auth] Request failed " + error);
                app.preloader.hide();
                app.dialog.alert(error.message);
            });
    },

    signInFormPost: function (form) {
        /*-- Called by SigninForm Submit --*/
        var email;
        var emailInput = form.querySelector('input[name="dbexpress-signin-email"]');
        if (emailInput) {
            email = emailInput.value;
            emailInput.blur();
        }
        var pw = form.querySelector('input[name="dbexpress-signin-password"]').value;
        var token = "";
        auth.signIn(email, pw, token, null);
    },

    signIn: function (email, pw, token, pin) {
        if (kShowPreloader == true) { app.preloader.show(); }
        var formData = new FormData();
        if (email) { formData.append("email", email); }
        if (pw) { formData.append("password", pw); }
        if (pin) { formData.append("pin", pin); }
        var url = thoriumCorePlugin.httpRoot + kApiRoot + kAuthManagerApi;
        fetch(url, {
            method: 'post',
            body: formData,
            contentType: 'multipart/form-data',
        })
            .then(thoriumapi.status)
            .then(thoriumapi.json)
            .then(function (data) {
                document.getElementById('dbexpress-twofactors-pin').value = "    ";
                if (auth.pinKeyPad) {
                    auth.pinKeyPad.setValue('');
                }
                if (!data) {
                    thoriumapi.logError(true, err.message);
                    auth.showSigninPopup();
                } else if (data.code == -1020) {//2-Factors Invalid PIN
                    localStorage.clear();
                    auth.currentUser.token = data.token;
                    if (auth.pinKeyPad) {
                        thoriumapi.alert(data.message);
                    } else {
                        auth.showTwoFactorsScreen();
                    }
                } else if (data.code == -1021) { //Account Disabled
                    thoriumapi.alert(data.message);
                } else if (data.code == -1009) { //Invalid parameters, unable to solve 
                    auth.showSigninPopup();
                    thoriumapi.logEvent(1, data.message);
                } else if (data.code == -1019) { //Invalid PIN
                    if (auth.pinKeyPad) {
                        app.preloader.hide();
                        app.dialog.alert(data.message);
                    } else {
                        auth.showTwoFactorsScreen();
                    }
                } else if (data.code == -1022) { //Not and Administrator
                    thoriumapi.alert(data.message);
                } else if (data.code == -1022) { //Not and Administrator
                    thoriumapi.alert(data.message);
                } else if (data.code < 0) { //other errors
                    thoriumapi.alert(data.message);
                } else if (data.code == 0) {
                    //Save to Local Storage
                    var item = {};
                    item.token = data.token;
                    try {
                        localStorage.setItem(app.id, JSON.stringify(item));
                    } catch (error) {
                        app.dialog.alert(error);
                    }
                    //Set currentUser Object
                    auth.currentUser.uid = data.uid;
                    auth.currentUser.displayName = data.displayName;
                    auth.currentUser.email = data.email;
                    auth.currentUser.emailVerified = (data.emailVerified == 1);
                    auth.currentUser.isAnonymous = (data.isAnonymous == 1);
                    auth.currentUser.creationTime = data.creationTime;
                    auth.currentUser.modifyTime = data.modifyTime;
                    auth.currentUser.lastSignInTime = data.lastSignInTime;
                    auth.currentUser.language = data.language;
                    auth.currentUser.phoneNumber = data.phoneNumber;
                    auth.currentUser.photoURL = data.photoURL;
                    auth.currentUser.headimage = data.headimage;
                    auth.currentUser.token = data.token;
                    auth.currentUser.group = data.group || 0;
                    $('.dbexpress_avatar').attr('title', auth.currentUser.displayName);
                    if ((data.photoURL) && (data.photoURL.length > 0)) {
                        var $ta = auth.getDbAssetsUrlRoot() + data.photoURL + "?rnd=" + Math.floor(Date.now() / 1000);
                        $('.dbexpress_avatar').attr('src', $ta);
                    } else {
                        $('.dbexpress_avatar').attr('src', 'img/defaultavatar.png');
                    }
                    $('.user-popover-name').text(auth.currentUser.displayName);
                    if (auth.loginScreen) { auth.loginScreen.close(); }
                    if (auth.twoFactorsScreen) { auth.twoFactorsScreen.close(); }
                    if (auth.registerScreen) { auth.registerScreen.close(); }
                    thoriumCorePlugin.app.showAppLoader(false);
                    app.preloader.hide();
                    app.emit("onAuthStateChanged", auth.currentUser);
                }
            }).catch(function (error) {
                thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress.auth] Request failed " + error);
                thoriumapi.logError(true, error.message);
                auth.showSigninPopup();
            });
    },

    saveProfile: function () {
        var form = document.getElementById("dbexpress-profile-form");
        var elements = document.getElementById("dbexpress-profile-form").elements;

        for (var i = 0, element; element = elements[i++];) {
            if (!element.checkValidity()) {
                app.preloader.hide();
                if (typeof element.reportValidity == 'function') {
                    element.reportValidity();
                };
                thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress.auth] Missing Mandatory fields for Form with id [" + form.id + "]");
                return;
            }
        }
        var token = auth.getToken();
        var formData = new FormData();
        formData.append("token", token);
        formData.append("uid", auth.currentUser.uid);
        //Get User Values       
        var elements = document.getElementById("dbexpress-profile-form").elements;
        for (var i = 0, element; element = elements[i++];) {
            var name = element.name.replace('dbexpress-profile-', '');
            if ((name != "photourl") && (name != "headimage") && (element.type != 'file')) {
                formData.append(name, element.value);
            } else if (element.type == 'file') {
                var tt = element.id;
                var ost = $("#" + tt);
                if (ost) {
                    var blob = ost.data("data-blob") || '';
                    var n = name.replace('-fileinput', '');
                    if (blob.length > 0) {
                        formData.append(n, blob);
                    }
                }
            }

        }
        //*-- Call API --*/   
        var url = thoriumCorePlugin.httpRoot + kApiRoot + kSetProfileApi;
        fetch(url, {
            method: 'post',
            body: formData,
            contentType: 'multipart/form-data',
        })
            .then(thoriumapi.status)
            .then(thoriumapi.json)
            .then(function (data) {
                if (!data) {
                    thoriumapi.logError(true, err.message);
                    auth.showSigninPopup();
                } else if (data.code == -23000) { //Unique Constraint
                    thoriumapi.logError(true, "User already exists (" + email + ")");
                } else if (data.code < 0) {
                    thoriumapi.logError(true, data.message);
                } else if (data.code == 0) {
                    //Set currentUser Object
                    auth.currentUser.displayName = data.displayName
                    auth.currentUser.email = data.email;
                    auth.currentUser.emailVerified = data.emailVerified;
                    auth.currentUser.isAnonymous = data.isAnonymous;
                    auth.currentUser.creationTime = data.creationTime;
                    auth.currentUser.modifyTime = data.modifyTime;
                    auth.currentUser.lastSignInTime = data.lastSignInTime;
                    auth.currentUser.language = data.language;
                    auth.currentUser.phoneNumber = data.phoneNumber;
                    auth.currentUser.photoURL = data.photoURL;
                    auth.currentUser.token = data.token;
                    auth.currentUser.headimage = data.headimage;

                    $('.dbexpress_avatar').attr('title', auth.currentUser.displayName);
                    if ((data.photoURL) && (data.photoURL.length > 0)) {
                        var $ta = auth.getDbAssetsUrlRoot() + data.photoURL + "?rnd=" + Math.floor(Date.now() / 1000);
                        $('.dbexpress_avatar').attr('src', $ta);
                    }
                    $('.user-popover-name').text(auth.currentUser.displayName);

                    app.preloader.hide();
                    auth.profileScreen.close();
                }

            }).catch(function (error) {
                thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress.auth] Request failed " + error);
                app.preloader.hide();
                app.dialog.alert(error.message);
            });
    },


    loadProfileScreen: function () {
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress.auth] Loading User Profile");
        if (kShowPreloader == true) { app.preloader.show(); }
        var token = auth.getToken();
        var formData = new FormData();
        if (token) { formData.append("token", token); } else {
            thoriumapi.logError(true, "Invalid Token");
            return;
        }
        if (auth.currentUser.email) {
            formData.append("email", auth.currentUser.email);
        } else {
            thoriumapi.logError(true, "Invalid email");
            return;
        }

        var url = thoriumCorePlugin.httpRoot + kApiRoot + kGetProfileApi;
        fetch(url, {
            method: 'post',
            body: formData,
            contentType: 'multipart/form-data',
        }).then(thoriumapi.status)
            .then(thoriumapi.json)
            .then(function (data) {
                if (!(data)) {
                    thoriumapi.logError(true, result);
                } else if (data.code < 0) {
                    thoriumapi.logError(true, "error " + data.code + ": " + data.message);
                } else if (data.code == 0) {
                    //Set currentUser Object
                    auth.currentUser.uid = data.uid;
                    auth.currentUser.displayName = data.displayName;
                    auth.currentUser.email = data.email;
                    auth.currentUser.emailVerified = (data.emailVerified == 1);
                    auth.currentUser.isAnonymous = (data.isAnonymous == 1);
                    auth.currentUser.creationTime = data.creationTime;
                    auth.currentUser.modifyTime = data.modifyTime;
                    auth.currentUser.lastSignInTime = data.lastSignInTime;
                    auth.currentUser.language = data.language;
                    auth.currentUser.phoneNumber = data.phoneNumber;
                    auth.currentUser.photoURL = data.photoURL;

                    /*-Read Only Infos--*/
                    $('.profile-displayname').text(data.displayName);
                    $('.profile-shortdesc').text(data.shortdesc);
                    if ((data.country) && (data.country.length > 0)) {
                        $(".dbexpress-profile-flag").css("background-image", "url(" + thoriumCorePlugin.httpRoot + kApiRoot + "flags/4x3/" + data.country.toLowerCase() + ".svg)");
                    } else {
                        $(".dbexpress-profile-flag").css("background-image", "");
                    }
                    if ((data.photoURL) && (data.photoURL.length > 0)) {
                        var $ta = auth.getDbAssetsUrlRoot() + data.photoURL + "?rnd=" + Math.floor(Date.now() / 1000);
                        $('#dbexpress-profile-photourl').css('background-image', 'url(' + $ta + ')');
                    } else {
                        $('#dbexpress-profile-photourl').css('background-image', 'url(./img/defaultavatar.png)');
                    }
                    if ((data.headimage) && (data.headimage.length > 0)) {
                        var $ta = auth.getDbAssetsUrlRoot() + data.headimage + "?rnd=" + Math.floor(Date.now() / 1000);
                        $('#dbexpress-profile-headimage').css('background-image', 'url(' + $ta + ')');
                    } else {
                        $('#dbexpress-profile-headimage').css('background-image', 'url(./img/defaultimg.png)');
                    }

                    /*-- Clear Blob Images--*/
                    $('#dbexpress-profile-photourl-fileinput').data("data-blob", '');
                    $('#dbexpress-profile-photourl-fileinput').data('data-type', '');
                    $('#dbexpress-profile-photourl-fileinput').data("data-filename", '');
                    if ($('#dbexpress-profile-photourl-rotate')) { $('#dbexpress-profile-photourl-rotate').hide(); };
                    if ($('#dbexpress-profile-photourl-remove')) { $('#dbexpress-profile-photourl-remove').hide(); };

                    /*-- Clear Blob A FAIRE POUR BACKGROUND--*/

                    /*-- fill Form A COMPLETER--*/
                    $('.dbexpress-profile-email').val(data.email);
                    $('.dbexpress-profile-displayname').val(data.displayName);
                    $('.dbexpress-profile-phonenumber').val(data.phoneNumber);
                    $('.dbexpress-profile-recoveryemail').val(data.recoveryemail);
                    $('.dbexpress-profile-bio').val(data.bio);
                    $('.dbexpress-profile-firstname').val(data.firstname);
                    $('.dbexpress-profile-lastname').val(data.lastname);
                    $('.dbexpress-profile-gender').val(data.gender);
                    $('.dbexpress-profile-address').val(data.address);
                    $('.dbexpress-profile-city').val(data.city);
                    $('.dbexpress-profile-country').val(data.country);
                    $('.dbexpress-profile-zip').val(data.zip);
                    $('.dbexpress-profile-shortdesc').val(data.shortdesc);
                    $('.dbexpress-profile-birthday').val(data.birthday);
                    $('.dbexpress-profile-state').val(data.state);

                    $('.dbexpress_avatar').attr('title', auth.currentUser.displayName);
                    $('.user-popover-name').text(auth.currentUser.displayName);
                    thoriumCorePlugin.app.showAppLoader(false);
                    app.preloader.hide();
                    auth.showProfileScreen();
                }

            }).catch(function (error) {
                thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress.auth] Request failed " + error);
                app.preloader.hide();
                app.dialog.alert(error.message);
            });
    },

    handleAvatarPopup: function (e, sender) {
        e.preventDefault();
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress.auth] Click/Touch on User Monitor");
        var signintext = document.querySelector('.dbexpress-signin .toolbar .title').textContent || 'Sign In';
        var regtext = document.querySelector('.dbexpress-register .navbar .title').textContent || 'Register';
        var editProfile = document.querySelector('.dbexpress-profile .navbar .title').textContent || 'Profile';
        var user = auth.currentUser;
        if ((!user) || (user.isAnonymous == true)) {
            var b = auth.allowRegister || false;
            if (b == true) {
                var actions = app.actions.create({
                    buttons: [
                        [
                            {
                                text: regtext,
                                bold: true,
                                onClick: function (e) {
                                    auth.showRegisterPopup(e);
                                }
                            },
                        ],
                        [
                            {
                                text: signintext,
                                bold: true,
                                onClick: function (e) {
                                    auth.showSigninPopup(e);
                                }
                            },
                        ],
                    ]
                }

                );
                actions.open(true);
            }
        } else {
            var actions = app.actions.create({
                buttons: [
                    [
                        {
                            text: editProfile,
                            bold: true,
                            onClick: function (e) {
                                auth.loadProfileScreen();
                            }
                        },
                    ],
                    [
                        {
                            text: kLogout,
                            color: 'red',
                            onClick: function (e) {
                                app.dialog.confirm(kLogout + '?', function () {
                                    auth.logout();
                                });
                            }
                        }
                    ]
                ]
            }

            );
            actions.open(true);
        }
    },

    logout: function () {
        if (kShowPreloader == true) { app.preloader.show(); }
        var token = auth.getToken();
        var formData = new FormData();
        if (token) { formData.append("token", token); }
        var url = thoriumCorePlugin.httpRoot + kApiRoot + kSignOutApi;

        fetch(url, {
            method: 'post',
            body: formData,
            contentType: 'multipart/form-data',
        })
            .then(thoriumapi.status)
            .then(thoriumapi.json)
            .then(function (data) {
                if (thoriumapi.checkResult(data) == true) {
                    if (data.code < 0) {
                        thoriumapi.logError(true, "error " + data.code + ": " + data.message);
                    } else if (data.code == 0) {
                        localStorage.clear();
                        location.reload();
                    }
                }
            }).catch(function (error) {
                thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress.auth] Request failed " + error);
                app.preloader.hide();
                app.dialog.alert(error.message);
            });
    },

}

var dbExpressMaps = {
    name: "Thorium Builder JSON API Plugin",
    bundleid: "com.thoriumbuilder.dbexpress.map",
    version: "3.0.0",

    initAllopenStreetMaps: function (force) {
        var x = document.querySelectorAll(".openstreetmap-api");
        for (var i = 0; i < x.length; i++) {
            var mapObject = x[i];
            var dataLoaded = mapObject.getAttribute("data-loaded") || "false";
            var markerSource = mapObject.getAttribute("data-dbsource") || null;
            if ((dataLoaded == "false") || (force == true)) {
                openStreetMapPlugin.showPreloader();
                var map = openStreetMapPlugin.initMap(mapObject.id);
                if (map) {
                    if (markerSource == "sqlite") {
                        if (typeof dbExpressMaps !== 'undefined') {
                            thoriumapi.logEvent(0, "[com.thoriumbuilder.leaflet] Get Markers from Sqlite for OSM Map with id:" + mapObject.id);
                            dbExpressMaps.loadPlaces(mapObject, 'osm');
                        }
                    }
                }
            } else {
                thoriumapi.logEvent(0, "[com.thoriumbuilder.leaflet] OSM Map with ID [" + mapObject.id + "] already initialized");
            }
        }
    },

    initAllGoogleMaps: function (force) {
        var x = document.querySelectorAll(".googlemapAPI");
        for (var i = 0; i < x.length; i++) {
            var mapObject = x[i];
            var dataLoaded = mapObject.getAttribute("data-loaded") || "false";
            var markerSource = mapObject.getAttribute("data-dbsource") || null;
            if (((dataLoaded == "false") || (force == true)) && (markerSource == "sqlite")) {
                if (kShowPreloader == true) { app.preloader.show(); }
                thoriumapi.logEvent(0, "[com.thoriumbuilder.leaflet] Get Markers from Sqlite for OSM Map with id:" + mapObject.id);
                dbExpressMaps.loadPlaces(mapObject, 'googlemap');
            } else {
                thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress.maps] Google Map with ID [" + mapObject.id + "] already initialized");
            }
        }
    },

    loadPlaces: function (mapContainer, map, engine) {
        engine = engine || 'googlemap';
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress.maps] map engine initialization [" + engine + "]");
        var currentPage = document.querySelector('.page-current');
        var parentpk;
        if (currentPage) {
            parentpk = currentPage.getAttribute("data-record-pk") || null;
        }
        if (kShowPreloader == true) { app.preloader.show(); }
        dbExpressData.getRepeaterData(mapContainer, parentpk, false, "");
    },

    addMarkersToMap: function (mapContainer, items, maptype) {
        //Callback called from getRepeaterData
        if ((typeof openStreetMapPlugin == 'undefined') && (maptype == "osm")) {
            app.preloader.hide();
            return;
        }
        if ((typeof googleMapPlugin == 'undefined') && (maptype == "googlemap")) {
            app.preloader.hide();
            return;
        }
        if (kShowPreloader == true) { app.preloader.show(); }
        var parentPage = mapContainer.closest(".page-content");
        if (parentPage) { app.preloader.showIn(parentPage); }
        var target = mapContainer.getAttribute('data-target') || '';
        var count = 0;
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress.maps] adding items to OSM map");
        var imgField = mapContainer.getAttribute('data-img-field') || null;
        var titleField = mapContainer.getAttribute('data-name-field') || null;
        var addressField = mapContainer.getAttribute('data-address-field') || null;
        var descriptionField = mapContainer.getAttribute('data-description-field') || null;
        var subtitleField = mapContainer.getAttribute('data-subtitle-field') || null;
        var badgeField = mapContainer.getAttribute('data-badge-field') || null;
        var latitudeField = mapContainer.getAttribute('data-latitude-field') || null;
        var longitudeField = mapContainer.getAttribute('data-longitude-field') || null;
        var ratingField = mapContainer.getAttribute('data-rating-field') || null;
        var service = mapContainer.getAttribute('data-service') || null;
        var maxRows = mapContainer.getAttribute("data-json-maxrows") || 10000;
        if (maxRows == 0) { maxRows = 10000; }

        var myCluster;
        if (maptype == "osm") {
            myCluster = openStreetMapPlugin.addMapCluster(mapContainer.map);
            if (!(myCluster)) {
                app.preloader.hide();
                var m = "[com.thoriumbuilder.dbexpress.maps] map engine initialization failed, unable to create cluster [" + engine + "]";
                thoriumapi.logEvent(2, m);
                app.dialog.alert(m);
                return;
            }
        }

        for (var prop in items) {
            var row = items[prop];
            row['target'] = target;
            row['uid'] = row["uid"];
            row['pk'] = row["pk"];
            row['service'] = service;
            if (titleField) { row['name'] = row[titleField]; }
            if (addressField) { row['address'] = row[addressField]; }
            if (descriptionField) { row['desc'] = row[descriptionField]; }
            if (subtitleField) { row['subtitle'] = row[subtitleField]; }
            if (badgeField) { row['badge'] = row[badgeField]; }
            if (latitudeField) { row['latitude'] = row[latitudeField]; }
            if (longitudeField) { row['longitude'] = row[longitudeField]; }
            if (ratingField) { row['rating'] = row[ratingField]; }
            if (imgField) {
                var rImg = row[imgField];
                if ((rImg) && (rImg.length > 0)) {
                    row['locimage'] = './db/dbassets/' + rImg;
                }
            }
            var rUid = row['uid'];
            var rLat = parseFloat(row['latitude']);
            var rLng = parseFloat(row['longitude']);
            row['uid'] = rUid;
            if ((rLat) && (rLng)) {
                if (maptype == "osm") {
                    var osm = mapContainer.data;
                    //openStreetMapPlugin.addMarkerAt(osm, rLat, rLng, row);
                    openStreetMapPlugin.addMarkertoClusterAt(myCluster, rLat, rLng, row);
                } else if (maptype == "googlemap") {
                    var objectPosition = new google.maps.LatLng(rLat, rLng);
                    googleMapPlugin.addMarker(mapContainer.map, objectPosition, row['name'], row['uid'], row['address'], row['locimage'], row['desc'], row['subtitle'], row['badge'], row['rating'], row['target'], row, row['pk'], row['service']);
                }
            }
            count = count + 1;
            if (count > maxRows) { break; }
        }
        if (maptype == "osm") {
            var osm = mapContainer.data;
            thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] " + count + " markers added to Map Cluster");
            openStreetMapPlugin.addClusterToMap(osm, myCluster);
        }

        app.preloader.hide();
        if (parentPage) { app.preloader.hideIn(parentPage); }
    },

    loadPlacesCallback: function (map, data) {
        if (typeof googleMapPlugin !== 'undefined') {
            var mapObject = map.getDiv();
            var objectId;
            var target;
            if (mapObject) {
                objectId = mapObject.getAttribute('id') || '';
                target = mapObject.getAttribute('data-target') || '';
            }
            for (var prop in data) {
                var row = data[prop];
                var rlat = parseFloat(row['latitude']);
                var rlng = parseFloat(row['longitude']);
                var objectPosition = new google.maps.LatLng(rlat, rlng);
                var objectName = row['name'] || '';
                var objectIdent = hash_encode(row['pk']);
                var objectAddress = row['address'];
                var objectImg = row['img_name'];
                var objectDesc = row['description'];
                var objectSubtitle = row['subtitle'];
                var objectBadge = row['badge'];
                var objectRating = row['rating'];
                if ((rlat != 0) && (rlng != 0)) {
                    googleMapPlugin.addMarker(map, objectPosition, objectName, objectIdent, objectAddress, objectImg, objectDesc, objectSubtitle, objectBadge, objectRating, target);
                }
            };
        }
    },

    initDisplayerMap: function (field, lat, lng) {
        if ((!lat) || (!lng)) {
            field.style.display = 'none';
            return;
        }
        var engine = field.getAttribute("data-engine") || null;
        if ((!engine) && (typeof openStreetMapPlugin !== 'undefined')) {
            engine = "osm";
        } else if ((!engine) && (typeof googleMapPlugin !== 'undefined')) {
            engine = "googlemap";
        }
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress.maps] Loading Displayer Map with Engine [" + engine + "]");
        if ((engine == 'osm') && (typeof openStreetMapPlugin !== 'undefined')) {
            var map = openStreetMapPlugin.initMap(field.id);
            if (map) {
                var item = {};
                item.latitude = lat;
                item.longitude = lng;
                item.name = lat + ";" + lng;
                openStreetMapPlugin.addMarkerAt(map, lat, lng, item);
                map.panTo(new L.LatLng(lat, lng));
            }
        } else if ((engine == 'googlemap') && (typeof googleMapPlugin !== 'undefined')) {
            googleMapPlugin.initializeMap(field.id);
            var LatLng = new google.maps.LatLng(parseFloat(lat), parseFloat(lng));
            googleMapPlugin.addMarker(field.map, LatLng, lat + ";" + lng);
            field.map.setCenter(LatLng);
        }
    }
}

var dbExpressData = {
    name: "Thorium Builder JSON API Plugin",
    bundleid: "com.thoriumbuilder.dbexpress.data",
    version: "3.0.0",
    allowInfinite: true,
    currentRecord: {},
    cache: {},
    onAuthStateChanged: function () { //OK 10-12-2020
        //The user has changed, we reload all data
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Initializing dbExpress API");
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Checking Repeaters State");
        var preloaders = document.querySelectorAll(".infinite-scroll-preloader");
        for (var i = 0; i < preloaders.length; i++) {
            var elt = preloaders[i];
            elt.style.visibility = "hidden";
        }
        //Show/Hide UI elements
        dbExpressData.applyUserRolePermissions();
        //Initialize Displayers
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Checking DbExpress Displayers State");
        var displayers = document.querySelectorAll(".dbexpress-displayer");
        for (var i = 0; i < displayers.length; i++) {
            var displayer = displayers[i];
            if (displayer.getAttribute("data-loaded") != "true") {
                displayer.style.visibility = "hidden";
                var service = displayer.getAttribute("data-service") || null;
                var currentPage = document.querySelector('.page-current');
                if ((service) && (currentPage)) {
                    dbExpressData.loadRecord(currentPage, service);
                }
            }
        }
        dbExpressData.initAllRepeaters(true);
        if (typeof openStreetMapPlugin !== 'undefined') {
            dbExpressMaps.initAllopenStreetMaps(true);
        }
        if (typeof googleMapPlugin !== 'undefined') {
            dbExpressMaps.initAllGoogleMaps(true);
        }
        thoriumCorePlugin.forms.init();
    },

    executeCustomSelectService: function (service, callback, field1, operator1, value1, field2, operator2, value2) {
        if ((!auth.currentUser.token) || (auth.currentUser.token.length == 0)) {
            thoriumapi.logEvent(1, "[com.thoriumbuilder.dbexpress] Unable to load data, No Active User");
            app.preloader.hide();
            return;
        }
        if (typeof callback != "function") {
            thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] Unable to execute Service, Callback function is undefined");
            app.preloader.hide();
            return;
        }
        var options = {};
        options["method"] = "post";
        var formData = new FormData();
        formData.append("service", service);
        formData.append("token", auth.currentUser.token);
        //Dynamic Parameters
        if ((field1) && (operator1) && (value1)) {
            formData.append("field1", field1);
            formData.append("operator1", operator1);
            formData.append("value1", value1);
            thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Adding Filter [" + field1 + operator1 + value1 + "]");
        }
        if ((field2) && (operator2) && (value2)) {
            formData.append("field2", field2);
            formData.append("operator2", operator2);
            formData.append("value2", value2);
            thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Adding Filter [" + field2 + operator2 + value2 + "]");
        }

        if (kShowPreloader == true) { app.preloader.show(); }
        options["body"] = formData;
        var url = thoriumCorePlugin.httpRoot + kApiRoot + kGetDataApi + "?rnd=" + Math.floor(Date.now() / 1000);
        fetch(url, options)
            .then(
                function (response) {
                    response.text()
                        .then(result => {
                            thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Got Response from url [" + url + "]");
                            var data;
                            try {
                                data = JSON.parse(result);
                            } catch (error) {
                                thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] " + error.message);
                                app.emit('dbExpressDataLoadError', $(this), error.message);
                                app.preloader.hide();
                                callback(-1, error.message, 0, null);
                                return;
                            }
                            if (((data) && (data.code < 0)) || (!data)) {
                                var m = "Unable to execute Service [" + service + "], error " + data.code + ": " + data.message;
                                thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] " + m);
                                app.emit('dbExpressDataLoadError', $(this), m);
                                app.preloader.hide();
                                callback(data.code, data.message, 0, null);
                                return;
                            }
                            //-- OK we display data
                            var i = 0;
                            var count = 0;
                            var items = data.result;
                            if (items) {
                                for (var prop in items) {
                                    items[i]["dataindex"] = i;
                                    items[i]["service"] = service;
                                    i = i + 1;
                                    count = count + 1;
                                }
                            }
                            if (!items) { items = {}; }
                            //Return result to Callback
                            callback(data.code, data.message, data.count, data.result);
                            app.preloader.hide();
                        })
                        .catch(function (error) {
                            app.preloader.hide();
                            thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] " + error);
                            app.emit('dbExpressDataLoadError', $(this), error);
                            callback(-1, error, 0, null);
                        });
                }).catch(function (error) {
                    app.preloader.hide();
                    if ((thoriumapi.isLocal() == true)) { //&& (error.message.toLowerCase().indexOf("network connection") != -1)
                        thoriumapi.logEvent(1, "[com.thoriumbuilder.dbexpress] - server busy [" + error.message + "], relaunch service [" + service + "]");
                        dbExpressData.executeCustomSelectService(service, callback, field1, operator1, value1, field2, operator2, value2);
                        return;
                    }
                    thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] - service [" + service + "] " + error.message);
                    app.emit('dbExpressDataLoadError', $(this), error);
                    if (app.online == false) {
                        // thoriumapi.showNotification("Internet connection lost");
                    } else {
                        callback(-2, error, 0, null);
                    }
                });
    },

    getParentRecord: function (service, fk, targetPage, transition) {
        //Get the parent Record from service with fk
        if ((!auth.currentUser.token) || (auth.currentUser.token.length == 0)) {
            thoriumapi.logEvent(1, "[com.thoriumbuilder.dbexpress] Unable to load data, No Active User");
            app.preloader.hide();
            return;
        }
        var options = {};
        options["method"] = "post";
        var formData = new FormData();
        formData.append("service", service);
        formData.append("token", auth.currentUser.token);
        formData.append("field1", "a.pk");
        formData.append("operator1", "=");
        formData.append("value1", fk);
        if (kShowPreloader == true) { app.preloader.show(); }
        options["body"] = formData;
        var url = thoriumCorePlugin.httpRoot + kApiRoot + kGetDataApi + "?rnd=" + Math.floor(Date.now() / 1000);
        fetch(url, options)
            .then(
                function (response) {
                    response.text()
                        .then(result => {
                            thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Got Response from url [" + url + "]");
                            var data;
                            try {
                                data = JSON.parse(result);
                            } catch (error) {
                                thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] " + error.message);
                                app.emit('dbExpressDataLoadError', $(this), error.message);
                                app.preloader.hide();
                                app.dialog.alert(error.message);
                                return;
                            }
                            if (((data) && (data.code < 0)) || (!data)) {
                                var m = "Unable to execute Service [" + service + "], error " + data.code + ": " + data.message;
                                thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] " + m);
                                app.emit('dbExpressDataLoadError', $(this), m);
                                app.preloader.hide();
                                app.dialog.alert(data.message);
                                return;
                            }
                            if (data.count = 1) {
                                var view = app.view;
                                var uid = data.result[0].uid;
                                if ((view) && (view.length > 0)) {
                                    var url='/' + targetPage + "/?uid=" + uid + "&pk=" + pk + "&service=" + service;
                                    var props={};
                                    props.rowindex=0;
                                    props.uid=uid;
                                    props.pk=fk;
                                    props.service=service;
                                    thoriumapi.loadpage(url,props,transition);
                                }
                            }
                            app.preloader.hide();
                        })
                        .catch(function (error) {
                            app.preloader.hide();
                            thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] " + error);
                            app.emit('dbExpressDataLoadError', $(this), error);
                            app.dialog.alert(error);
                        });
                }).catch(function (error) {
                    app.preloader.hide();
                    if ((thoriumapi.isLocal() == true)) { //&& (error.message.toLowerCase().indexOf("network connection") != -1)
                        thoriumapi.logEvent(1, "[com.thoriumbuilder.dbexpress] - server busy [" + error.message + "], relaunch service [" + service + "]");
                        dbExpressData.getParentRecord(service, fk, targetPage, transition);
                        return;
                    }
                    thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] - service [" + service + "] " + error.message);
                    app.emit('dbExpressDataLoadError', $(this), error);
                    if (app.online == false) {
                        thoriumapi.showNotification("Internet connection lost");
                    } else {
                        app.dialog.alert(error);
                    }
                });
    },

    initAllRepeaters: function (force) {
        //Initialize Repeaters
        var repeaters = document.querySelectorAll(".dbexpress-virtual-list-content");
        for (var i = 0; i < repeaters.length; i++) {
            var repeater = repeaters[i];
            if ((repeater.getAttribute("data-loaded") != "true") || (force == true)) {
                var service = repeater.getAttribute("data-service") || null;
                if (service) {
                    var noloadData = repeater.getAttribute("data-noload") || null;
                    if ((noloadData) && (noloadData == "true")) {
                        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Repeater with ID [" + repeater.id + "] is set to noload-data");
                    } else {
                        dbExpressData.getRepeaterData(repeater);
                    }
                } else {
                    thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Service is not defined for Repeater with ID [" + repeater.id + "]");
                }
            }
        }
    },

    applyUserRolePermissions: function (parent) {
        parent = parent || document;
        if (parent) {
            var elts = parent.querySelectorAll("* [data-permission]");
            for (var i = 0; i < elts.length; i++) {
                var elt = elts[i];
                var role = elt.getAttribute('data-permission') || 0;
                if (role > auth.currentUser.group) {
                    var d = elt.style.display;
                    if (d != "none") {
                        elt.setAttribute("data-display", d);
                    }
                    elt.style.visibility = "hidden";
                    elt.style.display = "none";
                } else {
                    elt.style.visibility = "visible";
                    var d = elt.getAttribute("data-display") || "block";
                    if (d == "none") { d = "block" };
                    elt.style.display = d;
                }
            }
        }
    },

    addItemToAllRepeaters: function (service, item) {
        //Update all UI elements when a new record is created from a Form
        //This is called from thoriumCorePlugin
        item.service = service;
        var elts = document.querySelectorAll(".dbexpress-virtual-list-content[data-service='" + service + "']");
        for (var i = 0; i < elts.length; i++) {
            var elt = elts[i];
            var virtualList = app.virtualList.get(elt);
            if (virtualList) {
                if (virtualList.items.length) {
                    item.dataindex = virtualList.items.length;
                    virtualList.prependItem(item);
                } else {
                    item.dataindex = 0;
                    virtualList.items = [];
                    virtualList.prependItem(item);
                }
                virtualList.update();
            } else { //Process Horizontal Repeater without VirtualList
                if (elt.classList.contains("dbexpress-horizontal-list") == true) {
                    dbExpressData.getRepeaterData(elt);
                }
            }
        }
    },

    replaceItemInAllRepeaters: function (service, item) {
        //Virtual List Repeater
        item.service = service;
        var elts = document.querySelectorAll(".dbexpress-virtual-list-content[data-service='" + service + "']");
        for (var i = 0; i < elts.length; i++) {
            var elt = elts[i];
            var virtualList = app.virtualList.get(elt);
            if (virtualList) {
                for (var j = 0; j < virtualList.items.length; j++) {
                    if (virtualList.items[j].uid == item.uid) {
                        item.dataindex = j;
                        virtualList.replaceItem(j, item);
                    }
                }
                virtualList.update();
            } else { //Process Horizontal Repeater without VirtualList
                if (elt.classList.contains("dbexpress-horizontal-list") == true) {
                    dbExpressData.getRepeaterData(elt);
                }
            }
        }

        //Refresh Context
        if (item.uid) {
            //Repeater VirtualList Images
            var elt2s = document.querySelectorAll(".dbexpress-virtual-list-content .virtual-list-media[data-img-name='" + item.uid + "-1.jpg']");
            for (var i = 0; i < elt2s.length; i++) {
                var elt = elt2s[i];
                var img = elt.getAttribute("data-img-name");
                elt.style.backgroundImage = "url('db/dbassets/" + img + "?rnd=" + Math.floor(Date.now() / 1000) + "')";
            }
            //Displayers
            var displayers = document.querySelectorAll(".dbexpress-displayer[data-service='" + service + "']");
            for (var i = 0; i < displayers.length; i++) {
                var elt = displayers[i];
                dbExpressData.fillDisplayerFromMemoryData(elt, item);
            }
            //dbExpress Image Fields
            var elts = document.querySelectorAll(".dbexpress-image-content");
            for (var i = 0; i < elts.length; i++) {
                var elt = elts[i];
                var img = elt.getAttribute("data-img-name");
                //3.4.1
                if (img.length > 0) {
                    elt.style.backgroundImage = "url('db/dbassets/" + img + "?rnd=" + Math.floor(Date.now() / 1000) + "')";
                } else {
                    elt.style.backgroundImage = "url(./img/defaultimg.png)";
                }
                //3.4.1
                if ((img) && (img.length > 0)) {
                    var pObject = elt.closest(".dbexpress-image");
                    if (pObject) { pObject.style.display = "block"; }
                }
            }

        }
    },

    showVerticalRepeaterZoom: function (vl, sender) {
        thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Show Horizontal Repeater Photo Browser");
        var selImg;
        var photos = [];
        var index = 0;
        var j = 0;
        var senderImg = sender.querySelector(".dbexpress-image-content");
        if (senderImg) {
            var currimage = senderImg.getAttribute("data-img-name");
        }
        var x = vl.querySelectorAll(".item-dbexpress"); //dbexpress-image-content
        for (var i = 0; i < x.length; i++) {
            var elt = x[i];
            if (elt.classList.contains("templaterow") == false) {
                var subElt = elt.querySelector(".dbexpress-image-content");
                if (subElt) {
                    var img = subElt.getAttribute("data-img-name");
                    if (img == currimage) {
                        index = j;
                    }
                    var p = {};
                    p.url = auth.getDbAssetsUrlRoot() + img;
                    photos.push(p);
                    j = j + 1;
                }
            }
        }
        thoriumCorePlugin.photobrowser.show(photos, index);
    },

    fillSmartSelect: function (obj, service) {
        //Called from thoriumCorePlugin
        //Fill Smart Select for Foreign Keys with data
        if (obj.getAttribute("data-loaded") == 'true') {
            return;
        }
        var parentPage = obj[0].closest(".page-content");
        if (parentPage) {
            app.preloader.showIn(parentPage);
        }
        var serviceFields = obj.getAttribute("data-service-field") || null;
        if (!serviceFields) {
            thoriumapi.logEvent(1, "[com.thoriumbuilder.dbexpress] Service Field is not defined for Form Input with ID [" + obj.id + "] ");
            return;
        }
        var h = '<option value="" selected disabled class="disabled"></option>';
        var url = thoriumCorePlugin.httpRoot + kApiRoot + kGetDataApi + "?rnd=" + Math.floor(Date.now() / 1000);
        var options = {};
        var headers = {};
        options["method"] = "post";
        var formData = new FormData();
        formData.append("service", service);
        formData.append("token", auth.currentUser.token);
        var field = serviceFields.replace("{{", "").replace("}}", "").trim();
        // if (field) {formData.append("orderby", field);}
        formData.append("maxrows", 10000); //Hard Coded

        options["body"] = formData;
        fetch(url, options)
            .then(thoriumapi.status)
            .then(thoriumapi.json)
            .then(function (data) {
                if ((data) && (data.code >= 0)) {
                    var i = 0;
                    var v = obj.getAttribute('data-value') || null; //Current value
                    var items = data.result;
                    var templateLine = '<option value="{{pk}}" {{selected}} >' + serviceFields + '</option>';
                    var i = 0;
                    var parentPK = obj.getAttribute("data-parent-pk") || null;
                    //Fill Select
                    if (items) {
                        var dataServiceField = obj.getAttribute("data-service-field") || null;
                        for (var prop in items) {
                            items[prop]["dataindex"] = i;
                            i = i + 1;
                            var t = templateLine;
                            var dataValue = serviceFields;
                            // if ((v) && (v == items[prop]['pk'])) {
                            //     t = t.replace("{{selected}}", "selected"); //Select line
                            // } else {
                            //     t = t.replace("{{selected}}", "");
                            // }
                            //Set the parent Key
                            var isCurrentLine = false;

                            for (var field in items[prop]) {
                                if ((parentPK) && (field == "pk") && (items[prop][field] == parentPK)) {
                                    t = t.replace("{{selected}}", "selected");
                                    isCurrentLine = true;
                                } else {
                                    t = t.replace("{{selected}}", "");
                                }
                                t = t.replace("{{" + field + "}}", items[prop][field]);
                                dataValue = dataValue.replace("{{" + field + "}}", items[prop][field]);
                                if ((isCurrentLine == true) && (dataServiceField == "{{" + field + "}}")) {
                                   // app.dialog.alert(field);
                                    //smartLabelElt.innerHTML=items[prop][field];
                                    // v=items[prop][field];
                                }

                            }
                            h = h + t;
                        }
                        obj.innerHTML = h;
                        //Set Current Value
                        if (parentPK) {
                            var smartSelect = obj.closest(".smart-select");
                            var smarSelectInstance = app.smartSelect.get(smartSelect);
                            if (smarSelectInstance) {
                                smarSelectInstance.setValue(parentPK);
                            }
                            if (obj.tagName.toLowerCase() == "select") {
                                obj.value = parentPK;
                            }
                        }


                        if (v) {
                            //Set the label
                            var smartSelect = obj.closest(".smart-select");
                            if (smartSelect) {
                                var smarSelectInstance = app.smartSelect.get(smartSelect);
                                if (smarSelectInstance) {
                                    smarSelectInstance.setValue(v);
                                }
                            }
                            if (obj.tagName.toLowerCase() == "select") {
                                obj.value = v;
                            }

                        }
                        obj.setAttribute("data-loaded", "true");
                        if (parentPage) {
                            app.preloader.hideIn(parentPage);
                        }
                    }
                }
            }).catch(function (error) {
                app.emit('dbExpressDataLoadError', $(this), error);
                thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress.data] Request failed " + error);
                app.preloader.hide();
                app.dialog.alert(error.message);
            });
    },

    sendData: function (service, formData, callback) {
        if (typeof callback != "function") {
            thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] Unable to execute Service, Callback function is undefined");
            return;
        }
        if (!formData) {
            thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] Invalid Form Data");
            return;
        }
        if ((!service) || (service.length == 0)) {
            thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] Invalid Service Name");
            return;
        }
        //Get URL for API
        var url = thoriumCorePlugin.httpRoot + kApiRoot + kSetDataApi;
        formData.append("service", service.toLowerCase());
        formData.append("token", auth.currentUser.token); //User Token
        //HEADER      
        var headers = '';
        var contentType = ''; //multipart/form-data
        var mode = '';
        var cache = '';
        var credentials = '';
        var headers = ''; //'{"Content-Type": "multipart/form-data"}';
        var redirect = '';
        var referrerPolicy = '';
       
        var options = {};
        options["method"] = "post";
        options["body"] = formData;
        if (headers.length > 0) {
            options["headers"] = headers;
        }
        if (contentType.length > 0) {
            options["contentType"] = contentType;
        }
        if (mode.length > 0) {
            options["mode"] = mode; //  no-cors, cors, same-origin
        }
        if (cache.length > 0) {
            options["cache"] = cache; // *default, no-cache, reload, force-cache, only-if-cached
        }
        if (credentials.length > 0) {
            options["credentials"] = credentials; // include, same-origin, omit
        }
        if (redirect.length > 0) {
            options["redirect"] = redirect; // manual, follow, error
        }
        if (referrerPolicy.length > 0) {
            options["referrerPolicy"] = referrerPolicy; // no-referrer, no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
        }  
        if (kShowPreloader == true) { app.preloader.show(); }
        //Send Request
        thoriumapi.logEvent(0, "Sending Data to: [" + url + "] from function calendarInsertDateSync");
        fetch(url, options)
        .then(thoriumapi.status)
        .then(thoriumapi.json)
        .then(function (data) {
            app.preloader.hide();
            if ((data) && (data.code < 0)) {
                app.emit('userFormError', "Error " + data.code + " " + data.message, this);
                thoriumapi.logEvent(2, "Error " + data.code + " " + data.message);
                app.dialog.alert(data.message);
                return;
            }
            if (!data) {
                app.emit('userFormError', "Error, invalid result", this);
                thoriumapi.logEvent(2, "Error, invalid result");
                app.dialog.alert("Error, invalid result");
                return;
            }
            thoriumapi.logEvent(0, "[com.thoriumbuilder.core] data Sent with Success");
  
            if (typeof callback != "function") {
                thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] Unable to execute Service, Callback function is undefined");
                return;
            }
            callback(service, data);


        }).catch(function (error) {
            thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] Request failed " + error);
            app.preloader.hide();
            var serverResponse = "Function failed: " + error.message;
            thoriumapi.logEvent(2, "[com.thoriumbuilder.core] Form Error: [" + serverResponse + "] when submitting data");
            if (serverResponse.length > 0) {
                app.dialog.alert(serverResponse)
            }
            else {
                app.dialog.alert('An error occured [' + serverResponse + "] when submitting data");
            }
            app.emit('userFormError', serverResponse, this);
        });       
    },

    submitForm: function (e, form, attempt) {
        attempt = attempt || 0;
        //Check Requierd Fields
        if (app.input.validateInputs(form) == false) {
            thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Missing Mandatory fields for Form with id [" + form.id + "]");
            return;
        }
        var formType = form.getAttribute('data-mode') || null;
        if ((formType != 3) && (formType != 4) && (formType != 5)) { return; } //3: Insert 4:Update 5:Single Record Form
        if (form.classList.contains("dbexpress-form") == false) { return; } //Not a dbExpress Form
        e.preventDefault;
        thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Submiting Form with id [" + form.id + "]");

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

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


        //Get URL for API dbExpress
        var url = thoriumCorePlugin.httpRoot + kApiRoot + kSetDataApi;
        //Get UID For update form
        var currentPage = document.querySelector('.page-current');
        if ((formType == 4) || (formType == 5)) { //update or single Form , we get the primary key
            var uid = currentPage.getAttribute("data-record-uid") || null;
            formData.append("uid", uid);
        } else if (formType == 3) { //Insert we get the foreign key
            var paramPK = currentPage.getAttribute("data-record-pk") || null;
            if (paramPK == "null") { paramPK = null; }
            var paramService = currentPage.getAttribute("data-record-service") || null;
            if ((paramService != service) && (paramService) && (paramPK)) {
                //We pass the parent id when the form was opened from a record
                formData.append("fk_" + paramService, paramPK);
            }
        }
        //Get Service dbExpress
        var service = form.getAttribute("data-service") || null;
        if (service) {
            formData.append("service", service);
        }
        //User Token dbExpress
        formData.append("token", auth.currentUser.token);

        if (kShowPreloader == true) { app.preloader.show(); }
        //Send Request
        thoriumapi.logEvent(0, "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();
            //Post Action dbExpress (v3) -- Update Displayers and Repeaters
            if ((data) && (data.code < 0)) {
                if (data.message.toLowerCase().includes("locked") && (attempt <= 5)) {
                    app.preloader.show();
                    thoriumapi.logEvent(1, "Database is locked, retrying...");
                    dbExpressData.submitForm(e, form, attempt + 1);
                    return;
                }
                thoriumapi.logEvent(2, "Error " + data.code + " " + data.message);
                app.dialog.alert(data.message);
                return;
            }
            if (!data) {
                thoriumapi.logEvent(2, "Error, invalid result");
                app.dialog.alert("Error, invalid result");
                return;
            }
            var item = {};
            if ((data.result) && (data.result.length > 0)) {
                item = data.result[0];
            }
            var inserted = (data.inserted == true);
            var service = form.getAttribute("data-service") || null;
            if ((inserted == true) && (service) && (formType == 3)) { //CallBack Insert VirtualList
                dbExpressData.addItemToAllRepeaters(service, item);
            } else if ((inserted == false) && (service) && (formType != 3)) { //CallBack Update VirtualList
                dbExpressData.replaceItemInAllRepeaters(service, item);
            }

            thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Form with id [" + form.id + "] Sent with Success");
            var cm =form.getAttribute("data-form-message") || null;
            if ((cm) && (cm.length > 0)) { app.dialog.alert(cm) };
            // app.emit('userFormSuccess', data, form);
            app.emit('formSuccess[#'+form.id+']',form,data);

            //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(""); }
                });
                var x = form.querySelectorAll("select");
                for (var i = 0; i < x.length; i++) {
                    var elt = x[i];
                    elt.value = "";
                    var itemAfter = form.querySelector(".smart-select .item-after");
                    if (itemAfter) { itemAfter.innerText = ""; }
                }
                $('.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.dbexpress.auth] Request failed " + error);
            app.preloader.hide();
            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('formError[#'+form.id+']',form,error);
        });
    },

    executeRepeaterFullText: function (sender) { //OK
        var search = sender.value;
        var form = sender.closest("form");
        if (form) {
            var customSearch = (form.getAttribute("data-custom-search") == 'true') || false;
            var searchmode = form.getAttribute("data-search-mode") || 0;
            var soundex = (form.getAttribute("data-soundex") == 'true') || false;
            var ftfield = form.getAttribute("data-search-field") || null;
            if  (customSearch == true) {
                sender.blur();
                var dsc = form.getAttribute("data-search-container");
                var repeaterInstance = document.querySelector(dsc);
                if (repeaterInstance) {
                    repeaterInstance.setAttribute('data-loaded', false);
                    var parentuid = repeaterInstance.getAttribute("data-parentuid") || null;
                    if (searchmode == 0) { //Equal to
                    } else if (searchmode == 1) { //Begins with
                        search = search + "*";
                    } else if (searchmode == 2) { //Contains
                        search = "*" + search + "*";
                    }
                    if (kShowPreloader == true) { app.preloader.show(); }
                    dbExpressData.getRepeaterData(repeaterInstance, parentuid, false, search, null, null, soundex, ftfield);
                }
            }
        }
    },

    deleteRecord: function (sender, service, uid, pk, closePage) {
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress.data] delete record with id [" + pk + "] from service [" + service + "]");
        if (kShowPreloader == true) { app.preloader.show(); }
        var formData = new FormData();
        formData.append("token", auth.currentUser.token);
        formData.append("service", service);
        formData.append("uid", uid);
        formData.append("pk", pk);
        var url = thoriumCorePlugin.httpRoot + kApiRoot + kDeleteDataApi;
        fetch(url, {
            method: 'post',
            body: formData,
            contentType: 'multipart/form-data',
        })
            .then(thoriumapi.status)
            .then(thoriumapi.json)
            .then(function (data) {
                if ((data) && (data.code < 0)) {
                    app.preloader.hide();
                    app.dialog.alert(data.message);
                } else if ((data) && (data.code == 0)) {
                    thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress.data] record with id [" + pk + "] from service [" + service + "] deleted");
                    var elts = document.querySelectorAll(".dbexpress-virtual-list-content[data-service='" + service + "']");
                    for (var i = elts.length - 1; i >= 0; i--) {
                        var elt = elts[i];
                        var virtualList = app.virtualList.get(elt);
                        if (virtualList) {
                            for (var j = 0; j < virtualList.items.length; j++) {
                                if (virtualList.items[j].uid == uid) {
                                    virtualList.deleteItem(j);
                                }
                            }
                            virtualList.update();
                        } else { //Case of horizontal Repeater without VL
                            //Loop Over Items MAnually
                            var vlItems = elt.querySelectorAll(".item-dbexpress[data-uid='" + uid + "']");
                            for (var x = 0; x < vlItems.length; x++) {
                                var vlItem = vlItems[x];
                                if (vlItem) { vlItem.remove(); }
                            }
                        }
                    }
                    app.preloader.hide();
                    //When it is called from a Displayer=>We close the page
                    if (closePage == true) { thoriumapi.backToPreviousPage(); }
                }
            }).catch(function (error) {
                thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress.data] Request failed " + error);
                app.preloader.hide();
                app.dialog.alert(error.message);
            });
    },

    handleDelete: function (sender) {
        var dataMessage = sender.getAttribute("data-message") || "Delete?";
        app.dialog.confirm(dataMessage, function () {
            var closePage = true;
            var parentRepeater = sender.closest(".dbexpress-virtual-list-content");
            if (parentRepeater) {
                closePage = false;
            }
            var parentDisplayer = sender.closest(".dbexpress-displayer");
            if (parentDisplayer) {
                closePage = true;
            }
            var recordPK = sender.getAttribute("data-pk") || null;
            var recordUID = sender.getAttribute("data-uid") || null;
            var recordService = sender.getAttribute("data-service") || null;
            if (recordPK == "null") { recordPK = null; }
            if (recordUID == "null") { recordUID = null; }
            if (recordService == "null") { recordService = null; }
            if ((recordService) && (recordPK) && (recordUID)) {
                dbExpressData.deleteRecord(sender, recordService, recordUID, recordPK, closePage);
            }

        });
    },

    hideInfinitePreloaders: function () {
        var preloaders = document.querySelectorAll(".infinite-scroll-preloader");
        if (preloaders) {
            for (var i = 0; i < preloaders.length; i++) {
                var elt = preloaders[i];
                elt.style.visibility = "hidden";
            }
        }
    },
    getRepeaterData: function (repeaterObject, parentpk, fetchMore, search, extraForeignKey, extraForeignKeyValue, soundex, ftfield) { //OK 10-12-2020
        fetchMore = fetchMore || false;
        search = search || null;
        extraForeignKey = extraForeignKey || null;
        extraForeignKeyValue = extraForeignKeyValue || null;
        soundex = soundex || false;
        ftfield = ftfield || null;
        var service = repeaterObject.getAttribute("data-service") || null;
        if (!service) {
            dbExpressData.hideInfinitePreloaders();
            thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] Service not defined for Repeater with ID [" + repeaterObject.id + "]");
            app.preloader.hide();
            return;
        }
        if ((!auth.currentUser.token) || (auth.currentUser.token.length == 0)) {
            dbExpressData.hideInfinitePreloaders();
            thoriumapi.logEvent(1, "[com.thoriumbuilder.dbexpress] Unable to load data, No Active User");
            app.preloader.hide();
            return;
        }

        if (fetchMore == true) {
            var done = (repeaterObject.getAttribute("data-fetched") == 'true') || false;
            if (done == true) { dbExpressData.allowInfinite = true; dbExpressData.hideInfinitePreloaders(); return; } //All data fetched
        } else {
            repeaterObject.setAttribute("data-offset", 0);
            repeaterObject.setAttribute("data-fetched", false);
            dbExpressData.allowInfinite = true;
        }
        if (ftfield) {
            repeaterObject.setAttribute("data-fulltext-field", ftfield);
        } else {
            ftfield = repeaterObject.getAttribute("data-fulltext-field") || null;
        }


        var offset = parseInt(repeaterObject.getAttribute("data-offset") || 0);
        if (offset==0) {dbExpressData.hideInfinitePreloaders();}
        var vl = $("#" + repeaterObject.id);
        if (!(vl)) {
            thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] Virtual List not found for Repeater with ID [" + repeaterObject.id + "]");
            dbExpressData.allowInfinite = true;
            app.preloader.hide();
            dbExpressData.hideInfinitePreloaders();
            return;
        }
        var virtualList = app.virtualList.get(vl);
        var url = thoriumCorePlugin.httpRoot + kApiRoot + kGetDataApi + "?rnd=" + Math.floor(Date.now() / 1000);
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Get data for Repeater with ID [" + repeaterObject.id + "] from url [" + url + "]");


        var options = {};
        options["method"] = "post";

        var formData = new FormData();
        // if (token) { formData.append("token", token); }
        formData.append("service", service);
        formData.append("token", auth.currentUser.token);
        //Parent Key
        if ((parentpk) && (parentpk.length > 0)) {
            var foreignKey = repeaterObject.getAttribute("data-foreign-key") || null;
            if (foreignKey) {
                formData.append("parentpk", parentpk);
                formData.append("foreignkey", foreignKey);
                repeaterObject.setAttribute("data-parent-pk", parentpk);
                thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Adding Filter [Parent key " + foreignKey + "=" + parentpk + "]");
            }
        } else { //Check if Parent PK stored
            var foreignKey = repeaterObject.getAttribute("data-foreign-key") || null;
            var foreignKeyValue = repeaterObject.getAttribute("data-parent-pk") || null;
            if ((foreignKey) && (foreignKeyValue)) {
                formData.append("parentpk", foreignKeyValue);
                formData.append("foreignkey", foreignKey);
            }
        }
        //Extra Foreign Key
        if ((extraForeignKey) && (extraForeignKeyValue)) {
            formData.append("extraparentpk", extraForeignKeyValue);
            formData.append("extraforeignkey", extraForeignKey);
            repeaterObject.setAttribute("data-parent-fk", extraForeignKey);
            repeaterObject.setAttribute("data-parent-fk-value", extraForeignKeyValue);
        } else { //Check if value is stored
            var extraForeignKey = repeaterObject.getAttribute("data-parent-fk") || null;
            var extraForeignKeyValue = repeaterObject.getAttribute("data-parent-fk-value") || null;
            if ((extraForeignKey) && (extraForeignKeyValue)) {
                formData.append("extraparentpk", extraForeignKeyValue);
                formData.append("extraforeignkey", extraForeignKey);
            }
        }


        //Full text
        if ((search) && (search.length > 0)) {
            formData.append("search", search);
            repeaterObject.setAttribute("data-search", search);
            if (ftfield) {
                formData.append("fulltext-field", ftfield);
            }
            thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Adding Filter [ full text search with value " + search + "]");
        }
        //Additional Criterians
        var f = repeaterObject.getAttribute('data-filter-field');
        var o = repeaterObject.getAttribute('data-filter-operator');
        var v = repeaterObject.getAttribute('data-filter-value');
        var f2 = repeaterObject.getAttribute('data-filter-field2');
        var o2 = repeaterObject.getAttribute('data-filter-operator2');
        var v2 = repeaterObject.getAttribute('data-filter-value2');
        if ((v.indexOf("{") == 0) && (v.indexOf("}") > -1)) {
            var tmp = v.replace("{", "");
            tmp = tmp.replace("}", "");
            try {
                v = window[tmp];
            }
            catch (err) {
                dbExpressData.hideInfinitePreloaders();
                app.preloader.hide();
                var m = "[com.thoriumbuilder.dbexpress] Unable to find variable " + tmp + "] " + err;
                thoriumapi.logEvent(2, m);
                app.dialog.alert(m);
                return;
            }
        }
        if ((v2.indexOf("{") == 0) && (v2.indexOf("}") > -1)) {
            var tmp = v2.replace("{", "");
            tmp = tmp.replace("}", "");
            try {
                v2 = window[tmp];
            }
            catch (err) {
                dbExpressData.hideInfinitePreloaders();
                app.preloader.hide();
                var m = "[com.thoriumbuilder.dbexpress] Unable to find variable " + tmp + "] " + err;
                thoriumapi.logEvent(2, m);
                app.dialog.alert(m);
                return;
            }
        }

        if ((f) && (o) && (v)) {
            formData.append("field1", f);
            formData.append("operator1", o);
            formData.append("value1", v);
            thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Adding Filter [" + f + o + v + "]");
        }
        if ((f2) && (o2) && (v2)) {
            formData.append("field2", f2);
            formData.append("operator2", o2);
            formData.append("value2", v2);
            thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Adding Filter [" + f2 + o2 + v2 + "]");
        }
        //User Data
        var usronly = (repeaterObject.getAttribute('data-user-data') == "1") || false;
        if (usronly == true) {
            formData.append("userdata", 1);
            thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Adding Filter [user data]");
        }

        //MaxRows & offset
        var maxrows = repeaterObject.getAttribute("data-maxrows") || 0;
        if (maxrows > 0) {
            formData.append("offset", offset);
            formData.append("maxrows", maxrows);
        }

        //Spatial Search Values
        var spatialLat = repeaterObject.getAttribute("data-filter-spatial-lat") || null;
        var spatialLng = repeaterObject.getAttribute("data-filter-spatial-lng") || null;
        var spatialDistanceField = repeaterObject.getAttribute("data-filter-spatial-distance") || null;
        var spatialGeopointField = repeaterObject.getAttribute("data-filter-spatial-geopoint-field") || null;
        if ((spatialLat) && (spatialLng) && (spatialDistanceField) && (spatialGeopointField)) {
            var spatialGeo = null;
            try {
                spatialGeo = window[spatialGeopointField];
            }
            catch (err) {
                thoriumapi.logEvent(1, "[com.thoriumbuilder.dbexpress] Spatial Field [" + spatialGeopointField + "] is undefined");
            }
            var spatialDist = null;
            try {
                spatialDist = window[spatialDistanceField];
            }
            catch (err) {
                thoriumapi.logEvent(1, "[com.thoriumbuilder.dbexpress] Spatial Field [" + spatialDistanceField + "] is undefined");
            }
            if ((spatialGeo) && (spatialDist)) {
                formData.append("spatial_lat", spatialLat);
                formData.append("spatial_lng", spatialLng);
                formData.append("spatial_geopoint", spatialGeo.lat + ";" + spatialGeo.lng);
                formData.append("spatial_distance", spatialDist);
            }
        }

        //Order
        var sort = repeaterObject.getAttribute("data-order-sort") || "asc";
        //Soundex
        if (soundex == true) {
            formData.append("soundex", soundex);
        }

        if (kShowPreloader == true) { 
            app.preloader.show(); 
        }
        formData.append("sort", sort);
        var orderby = repeaterObject.getAttribute("data-orderby") || null;
        if (orderby) {
            formData.append("orderby", orderby);
        }
        options["body"] = formData;
        fetch(url, options)
        .then(
            function (response) {
                response.text()
                    .then(result => {
                        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Got Response from url [" + url + "]");
                        var data;
                        try {
                            data = JSON.parse(result);
                        } catch (error) {
                            dbExpressData.hideInfinitePreloaders();
                            thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] " + error.message);
                            app.emit('dbExpressDataLoadError', $(this), error.message);
                            app.preloader.hide();
                            app.dialog.alert(error.message);
                            dbExpressData.allowInfinite = true;
                            return;
                        }
                        if (((data) && (data.code < 0)) || (!data)) {
                            dbExpressData.hideInfinitePreloaders();
                            var m = "Unable to load repeater with id [" + repeaterObject.id + "], error " + data.code + ": " + data.message;
                            thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] " + m);
                            app.emit('dbExpressDataLoadError', $(this), m);
                            app.preloader.hide();
                            app.dialog.alert(m);
                            dbExpressData.allowInfinite = true;
                            return;
                        }
                        //-- OK we display data

                        var i = 0;
                        var count = 0;
                        var items = data.result;
                        if (items) {
                            for (var prop in items) {
                                items[i]["dataindex"] = i + offset;
                                items[i]["service"] = service;
                                i = i + 1;
                                count = count + 1;
                            }
                        }
                        if (!items) { items = {}; }

                        //Case OSM data
                        if (repeaterObject.classList.contains("openstreetmap-api") == true) {
                            dbExpressData.hideInfinitePreloaders();
                            dbExpressMaps.addMarkersToMap(repeaterObject, items, "osm");
                            repeaterObject.setAttribute("data-loaded", true);
                            app.preloader.hide();
                            return;
                        }
                        //Case GoogleMap data
                        if (repeaterObject.classList.contains("googlemapAPI") == true) {
                            dbExpressData.hideInfinitePreloaders();
                            dbExpressMaps.addMarkersToMap(repeaterObject, items, "googlemap");
                            app.preloader.hide();
                            return;
                        }
                        //Case Classic Repeater
                        if (repeaterObject.classList.contains("dbexpress-virtual-list-content") == true) {
                            if (fetchMore == true) { //Fetch next rows (Infinite Scroll)
                                if ((count == 0) || (count < parseInt(maxrows))) {
                                    repeaterObject.setAttribute("data-fetched", true);
                                    var preloaders = document.querySelectorAll(".infinite-scroll-preloader");
                                    for (var i = 0; i < preloaders.length; i++) {
                                        var elt = preloaders[i];
                                        elt.style.visibility = "hidden";
                                    }
                                }
                                var parentPageContent = repeaterObject.closest('.page-content');
                                var scrollPos = 0;
                                if (parentPageContent) {
                                    scrollPos = parentPageContent.scrollTop || 0;
                                }
                                repeaterObject.setAttribute("data-offset", parseInt(offset) + parseInt(maxrows));
                                virtualList.appendItems(items);
                                virtualList.update();

                                if ((parentPageContent) && (scrollPos > 0)) {
                                    parentPageContent.scrollTo({
                                        top: scrollPos,
                                        behavior: 'smooth'
                                    });
                                }
                            } else { //Initial load 
                                repeaterObject.setAttribute("data-fetched", false);
                                if (virtualList) {
                                    app.virtualList.destroy(virtualList);
                                }
                                repeaterObject.style.visibility = "visible";
                                repeaterObject.style.opacity = 1;
                                repeaterObject.setAttribute("data-offset", i);
                                dbExpressData.setRepeaterDataContent(repeaterObject, items);
                                app.emit('dbExpressDataLoadSuccess', $(this), repeaterObject, items);
                                const parent=repeaterObject.closest(".dbexpress-virtual-list");
                                if (parent) {
                                    app.emit('repeaterDataLoaded[#'+parent.id+']', parent,items);
                                }
                                
                            }
                            dbExpressData.allowInfinite = true;
                        }
                        dbExpressData.hideInfinitePreloaders();
                        //Fuzzy Soundex
                        if (soundex == true) {
                            if ((data) && (data.count == 0) && (data.soundex) && (data.soundex.length > 0)) {
                                var notfoundblock = repeaterObject.querySelector(".repeater-noresult");
                                if (notfoundblock) {
                                    notfoundblock.innerHTML = '<div class="fuzzy">Did you mean <a class="fuzzy-link" href="#">' + data.soundex + '</a>?</div>';
                                }
                            }
                        }
                        app.preloader.hide();
                        app.dialog.close(); 
                    }).catch(function (error) {
                        //possibly DOM issue (PAge closed by user for example)
                        dbExpressData.hideInfinitePreloaders();
                        app.preloader.hide();
                        app.dialog.close(); 
                        thoriumapi.logEvent(1, "[com.thoriumbuilder.dbexpress] " + error);
                        app.emit('dbExpressDataLoadError', $(this), error);
                        //app.dialog.alert(error.message);
                    });
            })
        .catch(function (error) {
            dbExpressData.hideInfinitePreloaders();
            app.preloader.hide();
            app.dialog.close(); 
            if ((thoriumapi.isLocal() == true)) { //&& (error.message.toLowerCase().indexOf("network connection") != -1)
                thoriumapi.logEvent(1, "[com.thoriumbuilder.dbexpress] - server busy [" + error.message + "], relaunch service [" + service + "]");
                var attempt=repeaterObject.getAttribute("data-attempt")||0;
                repeaterObject.setAttribute("data-attempt",(attempt+1));
                if (attempt<3) {
                    dbExpressData.getRepeaterData(repeaterObject, parentpk, fetchMore, search, extraForeignKey, extraForeignKeyValue);
                } else {
                    var msg="Unable to call url "+url+" - the Server did not respond - "+error.message
                    thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] - service [" + service + "] "+msg);
                    app.emit('dbExpressDataLoadError', $(this), msg);
                    app.dialog.alert(msg);
                }               
                return;
            }
            thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] - service [" + service + "] " + error.message);
            app.emit('dbExpressDataLoadError', $(this), error);
            if (app.online == false) {
                // thoriumapi.showNotification("Internet connection lost");
            } else {
                app.dialog.alert(error.message);
            }
        });
    },
    loadRecord: function (page, service,uid,pk) { //newPage,paramService,paramUID,paramPK
        if (!page) {
            thoriumapi.logEvent(1, "[com.thoriumbuilder.dbexpress] no page specified");
            return;
        }
        var pagename=page.getAttribute("data-name");
        var formData = new FormData();
        formData.append("service", service);
        if (uid) {
            formData.append("uid", uid);
        } else {
            var m = "No UID parameter found for calling Service " + service;
            thoriumapi.logEvent(1, "[com.thoriumbuilder.dbexpress] " + m);
        }
        formData.append("token", auth.currentUser.token);
        var options = {};
        options["method"] = "post";
        options["body"] = formData;
        var pageContentDisplayer = document.querySelector(".page[data-name='" + pagename + "'] .dbexpress-displayer") || null;
        if (pageContentDisplayer) {
            pageContentDisplayer.style.display = "none";
        }
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress.data] loading record with uid [" + uid + "] from service [" + service + "]");
        if (kShowPreloader==true) {
            app.preloader.show(); 
        }
        var url = thoriumCorePlugin.httpRoot + kApiRoot + kGetDataApi + "?rnd=" + Math.floor(Date.now() / 1000);
        fetch(url, options)
        .then(
            function (response) {
                response.text()
                    .then(result => {
                        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Got Response from url [" + url + "]");
                        var data;
                        try {
                            data = JSON.parse(result);
                        } catch (error) {
                            thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] " + error.message);
                            app.preloader.hide();
                            return;
                        }
                        if (((data) && (data.code < 0)) || (!data)) {
                            app.preloader.hide();
                            var m = "Unable to load data for page [" + page.name + "] with service [" + service + "] : invalid result: " + data.message;
                            thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] " + m);
                            app.dialog.alert(m);
                            return;
                        }
                        if ((!data.result) || (data.result.length == 0)) {
                            app.preloader.hide();
                            var m = "Unable to load data for page [" + page.name + "] with service [" + service + "] : invalid result";
                            thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] " + m);
                            app.dialog.alert(m);
                            return;
                        }
                        //-- OK we display data
                        var item = data.result[0];
                        if (!item) { item = {}; }
                        //Data are loaded, we set the object contents with data
                        //Fill Displayers from current Data
                        var displayers = document.querySelectorAll(".page[data-name='" + pagename + "'] .dbexpress-displayer");
                        for (var i = 0; i < displayers.length; i++) {
                            var displayerService = displayers[i].getAttribute("data-service") || null;
                            if ((displayerService) && (displayerService == service)) {
                                dbExpressData.fillDisplayerFromMemoryData(displayers[i], item);
                                displayers[i].style.visibility = "visible";
                                displayers[i].style.display = "block";
                                displayers[i].style.opacity = 1;
                            }
                        }
                        //Fill Forms from current Data
                        var dbExpressForms = document.querySelectorAll(".page[data-name='" + pagename+ "'] .dbexpress-form[data-from-repeater='true']");
                        for (var i = 0; i < dbExpressForms.length; i++) {
                            var formService = dbExpressForms[i].getAttribute("data-service") || null;
                            if ((formService) && (formService == service)) {
                                dbExpressData.fillFormFromData(dbExpressForms[i], item);
                            }
                        }
                        app.preloader.hide();
                        if (page) { app.preloader.hideIn(page); }
                        app.dialog.close(); 
                    })
                    .catch(function (error) {
                        app.dialog.close(); 
                        app.preloader.hide();
                        if (page) { app.preloader.hideIn(page); }
                        thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] - service [" + service + "]  " + error);
                        app.emit('dbExpressDataLoadError', this, error);
                        app.dialog.alert(error.message);
                    });
            })
        .catch(function (error) {
            app.dialog.close(); 
            app.preloader.hide();
            if (page) { app.preloader.hideIn(page); }
            if ((thoriumapi.isLocal() == true)) { // && (error.message.toLowerCase().indexOf("network connection") != -1)
                thoriumapi.logEvent(1, "[com.thoriumbuilder.dbexpress] - server busy [" + error.message + "], relaunch service [" + service + "]");
                dbExpressData.loadRecord(page, service,uid,pk);
                return;
            }
            thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] - service [" + service + "] " + error.message);
            app.emit('dbExpressDataLoadError', $(this), error);
            if (app.online == false) {
                thoriumapi.showNotification("Internet connection lost");
            } else {
                app.dialog.alert(error.message);
            }
        });
    },
    onRepeaterLineChange: function (event, sender) { //OK 11-12-2020
        //Called when user selected a line in a Repeater
        var rowindex = sender.getAttribute('data-index') || null;
        if (rowindex) {
            thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] click/touch on line [" + rowindex + "]");

            var uid = sender.getAttribute('data-uid') || null;
            var pk = sender.getAttribute('data-pk') || null;
            var parentVL = sender.closest(".dbexpress-virtual-list-content");
            if (parentVL) {
                var parentRepeater = sender.closest(".dbexpress-virtual-list");
                if (parentRepeater) {
                    app.emit('dbExpressRepeaterLineChange', parentRepeater, rowindex);
                    var vl = thoriumapi.repeaters.getVirtualList(parentRepeater.id);
                    if (vl) {
                       app.emit('lineChange[#'+parentRepeater.id+']',event, parentRepeater,rowindex,vl.items[rowindex]);
                    }
                } else {
                    app.emit('dbExpressRepeaterLineChange', parentVL, rowindex);
                    var vl = thoriumapi.repeaters.getVirtualList(parentVL.id);
                    if (vl) {
                        app.emit('lineChange["#'+parentVL.id+'"]',event,parentVL, rowindex,vl.items[rowindex]);
                    }
                }
                var senderIsActionButton = sender.classList.contains("dbexpress-action-button"); //Triggered by dbexpress-action-button
                if (senderIsActionButton == true) {
                    var targetPage = sender.getAttribute('data-target') || null;
                    var transition = sender.getAttribute('data-transition') || "f7-push";
                    var service = sender.getAttribute('data-service') || null;
                    event.preventDefault();
                    var view = app.view;
                    if ((view) && (view.length > 0)) {
                        var url='/' + targetPage + '/?rowindex=' + rowindex + "&uid=" + uid + "&pk=" + pk + "&service=" + service + "&parentvlid=" + parentVL.id;
                        var props={};
                        props.rowindex=rowindex;
                        props.uid=uid;
                        props.pk=pk;
                        props.service=service;
                        props.parentvlid=parentVL.id;
                        thoriumapi.loadpage(url,props,transition);        

                       // app.view[0].router.navigate('/' + targetPage + '/?rowindex=' + rowindex + "&uid=" + uid + "&pk=" + pk + "&service=" + service + "&parentvlid=" + parentVL.id, { animate: true, transition: transition, reloadAll: false });
                    }
                } else { //Standard Click on Repeater Line 
                    var targetPage = parentVL.getAttribute('data-detail') || null;
                    var targetRepeaterId = parentVL.getAttribute('data-target-repeater') || null;
                    var targetForeignKey = parentVL.getAttribute('data-foreignkey-link') || null;
                    var service = parentVL.getAttribute('data-service') || null;
                    var transition = parentVL.getAttribute('data-transition') || "f7-push";
                    if (targetPage) {
                        event.preventDefault();
                        var view = app.view;
                        if ((view) && (view.length > 0)) {
                            var url='/' + targetPage + '/?rowindex=' + rowindex + "&uid=" + uid + "&pk=" + pk + "&service=" + service + "&parentvlid=" + parentVL.id;
                            var props={};
                            props.rowindex=rowindex;
                            props.uid=uid;
                            props.pk=pk;
                            props.service=service;
                            props.parentvlid=parentVL.id;
                            thoriumapi.loadpage(url,props,transition);               
                        }
                    } else if (targetRepeaterId) {
                        var currentPage = document.querySelector('.page-current');
                        var parentpk = currentPage.getAttribute("data-record-pk") || null;
                        var targetRepeaterInstance = document.getElementById(targetRepeaterId);
                        if ((targetRepeaterInstance) && (targetForeignKey)) {
                            var resetTargetFilters = (parentVL.getAttribute('data-target-reset-filters') == 'true') || false;
                            if (resetTargetFilters == true) {
                                targetRepeaterInstance.setAttribute('data-filter-field', '');
                                targetRepeaterInstance.setAttribute('data-filter-operator', '');
                                targetRepeaterInstance.setAttribute('data-filter-value', '');
                                targetRepeaterInstance.setAttribute('data-filter-field2', '');
                                targetRepeaterInstance.setAttribute('data-filter-operator2', '');
                                targetRepeaterInstance.setAttribute('data-filter-value2', '');
                            }
                            targetRepeaterInstance.setAttribute('data-loaded', false);
                            dbExpressData.getRepeaterData(targetRepeaterInstance, parentpk, false, null, targetForeignKey, pk);
                        }
                    }
                }
            }
        }
    },
    calendarInsertDateSync: function (service, date, dateField, parentpkfield, parentpk, attempt) {
        attempt = attempt || 0;
        //Header && Form Variables
        var url = thoriumCorePlugin.httpRoot + kApiRoot + kSetDataApi;
        var formData = new FormData();
        formData.append(dateField, date);
        formData.append("service", service);
        formData.append("token", auth.currentUser.token);
        if ((parentpkfield) && (parentpkfield.length > 0)) {
            formData.append(parentpkfield, parentpk);
        }
        //HEADER      
        var headers = '';
        var contentType = ''; //multipart/form-data
        var mode = '';
        var cache = '';
        var credentials = '';
        var headers = ''; //'{"Content-Type": "multipart/form-data"}';
        var redirect = '';
        var referrerPolicy = '';
       
        var options = {};
        options["method"] = "post";
        options["body"] = formData;
        if (headers.length > 0) {
            options["headers"] = headers;
        }
        if (contentType.length > 0) {
            options["contentType"] = contentType;
        }
        if (mode.length > 0) {
            options["mode"] = mode; //  no-cors, cors, same-origin
        }
        if (cache.length > 0) {
            options["cache"] = cache; // *default, no-cache, reload, force-cache, only-if-cached
        }
        if (credentials.length > 0) {
            options["credentials"] = credentials; // include, same-origin, omit
        }
        if (redirect.length > 0) {
            options["redirect"] = redirect; // manual, follow, error
        }
        if (referrerPolicy.length > 0) {
            options["referrerPolicy"] = referrerPolicy; // no-referrer, no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
        }
        if (kShowPreloader == true) { app.preloader.show(); }
        //Send Request
        thoriumapi.logEvent(0, "Sending Data to: [" + url + "] from function calendarInsertDateSync");
        fetch(url, options)
        .then(thoriumapi.status)
        .then(thoriumapi.json)
        .then(function (data) {
            app.preloader.hide();
            if ((data) && (data.code < 0)) {
                if (data.message.toLowerCase().includes("locked") && (attempt <= 5)) {
                    app.preloader.show();
                    thoriumapi.logEvent(1, "Database is locked, retrying...");
                    dbExpressData.calendarInsertDateSync(service, date, dateField, parentpkfield, parentpk, attempt + 1);
                    return;
                }
                thoriumapi.logEvent(2, "Error " + data.code + " " + data.message + " after " + attempt + " attempts");
                app.dialog.alert(data.message);
                return;
            }
            if (!data) {
                thoriumapi.logEvent(2, "Error, invalid result");
                app.dialog.alert("Error, invalid result");
                return;
            }
            thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Calendar data Sent with Success");

        }).catch(function (error) {
            thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] Request failed " + error);
            app.preloader.hide();
            var serverResponse = "Function calendarInsertDateSync failed: " + error.message;
            thoriumapi.logEvent(2, "[com.thoriumbuilder.core] Form Error: [" + serverResponse + "] when submitting data from function calendarInsertDateSync");
            if (serverResponse.length > 0) {
                app.dialog.alert(serverResponse)
            }
            else {
                app.dialog.alert('An error occured [' + serverResponse + "] when submitting data from function calendarInsertDateSync");
            }
            app.emit('userFormError', serverResponse, this);
        });
    },
    savecalendar: function (id) {
        var x = document.querySelector("#" + id + " .calendar");
        if (x) {
            var CalendarContainer = document.querySelector("#" + id);
            var c = app.calendar.get(x);
            if (c) {
                var parentpk = CalendarContainer.getAttribute("data-parent-pk");
                var parentpkfield = CalendarContainer.getAttribute("data-foreign-key");
                var dateField = CalendarContainer.getAttribute("data-service-field");
                var service = CalendarContainer.getAttribute("data-service");
                var values = c.value;
                if (values) {
                    app.preloader.show();
                    for (const [key, value] of Object.entries(values)) {
                        var date = new Date(value);
                        if (date) {
                            var timestamp = date.getTime() / 1000;
                            dbExpressData.calendarInsertDateSync(service, timestamp, dateField, parentpkfield, parentpk, 0);
                        }
                    }
                    app.preloader.hide();
                    app.view[0].router.back();

                }

            }
        }
    },
    sendPin: function (sender) {
        var em = document.querySelector("#dbexpress-signin-email");
        var pw = document.querySelector("#dbexpress-signin-password");
        if (((em) && (em.value.length > 0)) && ((pw) && (pw.value.length > 0))) {
            if (kShowPreloader == true) { app.preloader.show(); }
            var options = {};
            options["method"] = "post";
            var formData = new FormData();
            formData.append("email", em.value);
            formData.append("password", pw.value);
            options["body"] = formData;
            var url = thoriumCorePlugin.httpRoot + kApiRoot + "thorium.auth.sendpin.php";
            fetch(url, options)
            .then(
                function (response) {
                    response.text()
                        .then(result => {
                            thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Got Response from url [" + url + "]");
                            var data;
                            try {
                                data = JSON.parse(result);
                            } catch (error) {
                                thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] " + error.message);
                                app.preloader.hide();
                                app.dialog.alert(error.message);
                                return;
                            }
                            if (((data) && (data.code < 0)) || (!data)) {
                                var m = "Unable to send PIN";
                                thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] " + m);
                                app.preloader.hide();
                                app.dialog.alert(m);
                                return;
                            }
                            app.dialog.alert(data.message);
                            app.preloader.hide();
                        })
                        .catch(function (error) {
                            app.preloader.hide();
                            thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] " + error);
                            app.dialog.alert(error.message);
                        });
                })
            .catch(function (error) {
                app.preloader.hide();
                if ((thoriumapi.isLocal() == true)) { //&& (error.message.toLowerCase().indexOf("network connection") != -1)
                    thoriumapi.logEvent(1, "[com.thoriumbuilder.dbexpress] - server busy [" + error.message + "], relaunch service [" + service + "]");
                    dbExpressData.sendPin(sender);
                    return;
                }
                thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] - " + error.message);
                if (app.online == false) {
                    // thoriumapi.showNotification("Internet connection lost");
                } else {
                    app.dialog.alert(error.message);
                }
            });
        }
    },
    getCalendarData: function (calendar, parentpk) {
        var service = calendar.getAttribute('data-service') || null;
        calendar.setAttribute("data-parent-pk", parentpk);
        if (!service) {
            thoriumapi.logEvent(1, "[com.thoriumbuilder.dbexpress] Calendar Service not set");
            return;
        }
        var serviceField = calendar.getAttribute('data-service-field') || null;
        if (!serviceField) {
            thoriumapi.logEvent(1, "[com.thoriumbuilder.dbexpress] Calendar Service Field not set");
            return;
        }
        var behavior = calendar.getAttribute('data-behavior');
        var url = thoriumCorePlugin.httpRoot + kApiRoot + kGetDataApi + "?rnd=" + Math.floor(Date.now() / 1000);
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Get data for Calendar with ID [" + calendar.id + "] from url [" + url + "]");
        var options = {};
        options["method"] = "post";
        var formData = new FormData();
        formData.append("service", service);
        formData.append("token", auth.currentUser.token);
        //Parent Key
        if ((parentpk) && (parentpk.length > 0)) {
            var foreignKey = calendar.getAttribute("data-foreign-key") || null;
            if (foreignKey) {
                formData.append("parentpk", parentpk);
                formData.append("foreignkey", foreignKey);
                thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Adding Filter [Parent key " + foreignKey + "=" + parentpk + "]");
            }
        }
        //User Data
        var usronly = (calendar.getAttribute('data-user-data') == "1") || false;
        if (usronly == true) {
            formData.append("userdata", 1);
            thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Adding Calendar Filter [user data]");
        }

        options["body"] = formData;
        fetch(url, options)
        .then(function (response) {
            response.text()
            .then(result => {
                thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Got Response from url [" + url + "]");
                var data;
                try {
                    data = JSON.parse(result);
                } catch (error) {
                    thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] " + error.message);
                    app.emit('dbExpressDataLoadError', calendar, error.message);
                    app.preloader.hide();
                    app.dialog.alert(error.message);
                    return;
                }
                if (((data) && (data.code < 0)) || (!data)) {
                    var m = "Unable to load repeater with id [" + calendar.id + "], error " + data.code + ": " + data.message;
                    thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] " + m);
                    app.emit('dbExpressDataLoadError', calendar, m);
                    app.preloader.hide();
                    app.dialog.alert(m);
                    return;
                }
                //-- OK we display data
                var i = 0;
                var count = 0;
                var items = data.result;
                if (items) {
                    for (var prop in items) {
                        items[i]["dataindex"] = i;
                        items[i]["service"] = service;
                        var item = items[i];
                        i = i + 1;
                        count = count + 1;
                        var date = new Date(item[serviceField] * 1000);
                        date = new Date(date.toDateString()); //Remove Time
                        if (behavior=="1") {
                            thoriumapi.date.disableDateFromCalendar(calendar.id, date, item); 
                        } else {
                            thoriumapi.date.addEventToCalendar(calendar.id, date, item);
                        }
                    }
                }
                if (!items) { items = {}; }
                app.preloader.hide();
            })
            .catch(function (error) {
                app.preloader.hide();
                thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] " + error);
                app.emit('dbExpressDataLoadError', calendar, error);
                app.dialog.alert(error.message);
            });
        }).catch(function (error) {
            app.preloader.hide();
            if ((thoriumapi.isLocal() == true)) {
                thoriumapi.logEvent(1, "[com.thoriumbuilder.dbexpress] - server busy [" + error.message + "], relaunch service [" + service + "]");
                dbExpressData.getCalendarData(calendar, parentpk);
                return;
            }
            thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] - service [" + service + "] " + error.message);
            app.emit('dbExpressDataLoadError', calendar, error);
            if (app.online == false) {
                // thoriumapi.showNotification("Internet connection lost");
            } else {
                app.dialog.alert(error.message);
            }
        });
    },
    onPageMounted: function (e) {
        //Called when a new page is opened
        var p = e.detail;
        var direction= p.direction;
        var appRoute;
        if (p === page) { elt = app.root; } else { elt = p.$el; appRoute = p.route; }
        if (elt) {
            if (appRoute) {
                //Get Parameters
                var paramRowIndex = appRoute.query.rowindex || null;
                var paramUID = appRoute.query.uid || null;
                if (!paramUID) { paramUID = appRoute.query.id || null; }
                var paramPK = appRoute.query.pk || null;
                var paramService = appRoute.query.service || null;
                //Get references to Current Page
                var currentPage = document.querySelector(".page[data-name='" + p.name + "']");
                var callingPage = document.querySelector('.page-current'); //Page that opened the new page (calling page)
                //Show/Hide UI elements
                dbExpressData.applyUserRolePermissions();              
                if ( (paramRowIndex) && (paramUID) && (paramService) && (paramPK)) {
                    //The new page is just inserted in the DOM at this step, 
                    //Record passed to window 
                    if (currentPage) {
                        currentPage.setAttribute("data-record-uid", paramUID);
                        currentPage.setAttribute("data-record-pk", paramPK);
                        currentPage.setAttribute("data-record-service", paramService);
                    }
                } else { //Page not opened from a Repeater, we get the page current params and add it to the new page
                    paramRowIndex = -1; //No line
                    if  ( (direction=="forward") && (callingPage)) {
                        paramUID = callingPage.getAttribute("data-record-uid") || null;
                        paramPK = callingPage.getAttribute("data-record-pk") || null;
                        paramService = callingPage.getAttribute("data-record-service") || null;
                        if (currentPage) {
                            currentPage.setAttribute("data-record-uid", paramUID);
                            currentPage.setAttribute("data-record-pk", paramPK);
                            currentPage.setAttribute("data-record-service", paramService);
                        }
                    }
                }
                
                //Check if it is necessary to load Record
                var displayer = document.querySelector(".page[data-name='" + p.name + "'] .dbexpress-displayer");
                var form = document.querySelector(".page[data-name='" + p.name + "'] .dbexpress-form[data-from-repeater='true']");

                //Load Record for Displayers and Forms for the new page  (Form update mode)
                if ((form) || (displayer)) {
                    if (form) { form.style.visibility = "hidden"; } //btn.style.display = 'none';
                    if (displayer) {
                        displayer.style.visibility = "hidden";
                        displayer.style.opacity = 0;
                    }
                    //If there is a Form or a Displayer, we load the record 
                    dbExpressData.loadRecord(currentPage, paramService, paramUID);
                    // setTimeout(
                    //     function () {
                    //         dbExpressData.loadRecord(currentPage, paramService, paramUID);
                    //     }
                    //     , 50);

                }
                //Load Foreign Key for Forms (Form Insert Child mode)
                if ((paramService) && (paramPK)) {
                    var formInput = document.querySelector(".page[data-name='" + p.name + "'] .dbexpress-form *[name='" + "fk_" + paramService + "']");
                    if (formInput) {
                        formInput.value = paramPK;
                        formInput.setAttribute("data-parent-pk", paramPK);
                    }
                }

                //Load All Repeaters for the new page
                var repeaters = document.querySelectorAll(".page[data-name='" + p.name + "'] .dbexpress-virtual-list-content");
                for (var i = 0; i < repeaters.length; i++) {
                    var repeater = repeaters[i];
                    repeater.style.visibility = "hidden";
                    repeater.style.opacity = 0;
                    var noloadData = repeater.getAttribute("data-noload") || null;
                    if ((noloadData) && (noloadData == "true")) {
                        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Repeater with ID [" + repeater.id + "] is set to noload-data");
                    } else {
                        dbExpressData.getRepeaterData(repeater, paramPK);
                    }
                }

                //Load All dbExpress Calendars
                var calendars = document.querySelectorAll(".page[data-name='" + p.name + "'] .custom-db-calendar");
                for (var i = 0; i < calendars.length; i++) {
                    var calendar = calendars[i];
                    //var calElt = calendar.querySelector(".calendar");
                    // if ((calElt) && (auth.currentUser.group == 0)) {
                    //     calElt.classList.add("disabled");
                    // } else {
                    //     calElt.classList.remove("disabled");
                    // }
                    dbExpressData.getCalendarData(calendar, paramPK);
                }
                if (typeof openStreetMapPlugin !== 'undefined') {
                    dbExpressMaps.initAllopenStreetMaps(false);
                }
                if (typeof googleMapPlugin !== 'undefined') {
                    dbExpressMaps.initAllGoogleMaps(true);
                }

                //Avatar
                if ((auth.currentUser.photoURL) && (auth.currentUser.photoURL.length > 0)) {
                    var $ta = auth.getDbAssetsUrlRoot() + auth.currentUser.photoURL + "?rnd=" + Math.floor(Date.now() / 1000);
                    $('.dbexpress_avatar').attr('src', $ta);
                }
            }
        }
    },
    setRepeaterDataContent: function (field, items) {
        var displayMode = field.getAttribute("data-displaymode") || 0;
        var templateRef = document.getElementById('template-' + field.id);
        var t;
        t = $('#' + field.id).data("template") || null;
        /*- get template html --*/
        if ((!t) || (t.length == 0)) {
            if ((displayMode < 2)) {
                t = dbExpressData.getRepeaterTemplate(templateRef);
            } else {
                t = templateRef.outerHTML;
            }
            $('#' + field.id).data("template", t);
        }
        var template = t; //Save Original template
        t = t.replaceAll("{data-img-root}", auth.getDbAssetsUrlRoot());
        t = t.replaceAll("{data-asset-root}", auth.getDbAssetsUrlRoot());
        if (displayMode < 2) { /*Virtual List*/
            thoriumCorePlugin.virtuallist.renderVirtualListFromData(field, items, t);
        }
        else { /*Horizontal custom*/
            field.scrollLeft = 0;
            field.innerHTML = template; //Clear existing contents and deassign template
            $('#' + field.id).data("items", items);
            if (items.length > 0) {
                thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Render Repeater for Repeater with ID [" + field.id + "]");
                $("#" + field.id).data('items', items);
                var i = 0;
                //Flatten items
                var itemhtml;
                var root = auth.getDbAssetsUrlRoot();
                thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Get horizontal Repeater Rows for Repeater with ID [" + field.id + "]");
                for (var item in items) { //Loop Results
                    itemhtml = thoriumCorePlugin.virtuallist.renderVlItem(items[item], t, templateRef, 0);
                    //PB Here should add a random to image, but don't know img field
                    // + "?rnd=" +Math.floor(Date.now() / 1000)
                    //     itemhtml = itemhtml.replace("url({data-img-root})", "url(./img/defaultimg.png)");
                    itemhtml = itemhtml.replaceAll("templaterow", "");
                    itemhtml = itemhtml.replaceAll('template-' + field.id, 'template-' + field.id + "-" + i);
                    itemhtml = itemhtml.replaceAll("{data-img-root}", root);
                    itemhtml = itemhtml.replaceAll("{data-asset-root}", root);
                    itemhtml = itemhtml.replace("url(" + root + ")", "url('./img/defaultimg.png')");
                    field.innerHTML = field.innerHTML + itemhtml;
                    i = i + 1;
                }
                field.setAttribute("data-loaded", true);
                field.setAttribute("data-count", items.length);
            }
        }
    },
    getRepeaterTemplate: function (r) {  //IN PROGRESS
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Get Template for Repeater with ID [" + r.id + "]");
        var oh = r.outerHTML;
        r.style.display = null;
        r.removeAttribute("id");
        r.removeAttribute("data-template-height");
        r.classList.remove("templaterow");
        var t = r.outerHTML;
        r.outerHTML = oh;
        return t;
    },
    parseDataRecursive: function (prop, data, item) {
        app.dialog.alert('[com.thoriumbuilder.dbexpress] parseDataRecursive');
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Parsing Data ");
        for (var prop in data) {
            if (typeof data[prop] == "object" && data[prop] !== null) {
                if (Array.isArray(data[prop])) {
                    var p = prop.replaceAll(" ", "_");
                    item[p] = data[prop];
                } else {
                    dbExpressData.parseDataRecursive(prop, data[prop], item);
                }
            } else {
                var p = prop.replaceAll(" ", "_");
                item[p] = data[prop];
            }
        }
        return item;
    },
    fillDisplayerFromMemoryData: function (displayer, data) {
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Set Content for Displayer with ID [" + displayer.id + "]");
        $("#" + displayer.id + " .dbexpress-field").each(function (dbExpressField) {
            //First check if element is within a repeater
            var parentRepeater = dbExpressField.closest('.dbexpress-virtual-list');
            if (!parentRepeater) {
                var tag = dbExpressField.tagName.toLowerCase();
                var c = dbExpressField.className.toLowerCase();
                var fld = dbExpressField.getAttribute("data-dbexpress-field") || null;
                var dataType = dbExpressField.getAttribute("data-dbexpress-type") || null;
                //Cas Particulier Boutons Route
                if (dataType == "route") {
                    var latitude = dbExpressField.getAttribute("data-latitude") || null;
                    var longitude = dbExpressField.getAttribute("data-longitude") || null;
                    var geopointFld = dbExpressField.getAttribute("data-geopoint") || null;
                    var action = dbExpressField.getAttribute("onclick") || null;
                    if ((latitude) && (longitude) && (action)) {
                        var latValue = data[latitude];
                        var lngValue = data[longitude];
                        if ((latValue) && (lngValue)) {
                            action = action.replaceAll("{{" + latitude + "}}", latValue);
                            action = action.replaceAll("{{" + longitude + "}}", lngValue);
                            dbExpressField.setAttribute("onclick", action);
                        } else {
                            dbExpressField.style.display="none";
                            dbExpressField.setAttribute("onclick", "");
                        }
                    } else if ((geopointFld) && (geopointFld.length>0)) {
                        var c=data[geopointFld];
                        if ((c) && (c.length>0)) {
                            const ar = c.replaceAll("{","").replaceAll("}","").split(",");
                            if (ar.length==2) {
                                action = action.replaceAll("{{" + geopointFld+".lat" + "}}", ar[0]);
                                action = action.replaceAll("{{" + geopointFld+".lng" + "}}", ar[1]);
                                dbExpressField.setAttribute("onclick", action);
                            } 
                        } else {
                            dbExpressField.style.display="none";
                        }
                    } else {
                        dbExpressField.style.display="none";
                    }
                    //Cas Particulier Map
                } else if ((dataType) && (dataType == "map")) {
                    var latFld = dbExpressField.getAttribute("data-latitude") || null;
                    var lngFld = dbExpressField.getAttribute("data-longitude") || null;
                    var geopointFld = dbExpressField.getAttribute("data-geopoint") || null;                  
                    if ((!latFld) || (!lngFld)) {
                        if ( (geopointFld) && (geopointFld.length>0) ) {
                            var c=data[geopointFld];
                            if ((c) && (c.length>0)) {
                                const ar = c.replaceAll("{","").replaceAll("}","").split(",");
                                if (ar.length==2) {
                                    dbExpressMaps.initDisplayerMap(dbExpressField, ar[0],  ar[1]);
                                } else {
                                    dbExpressField.style.display = 'none';
                                }
                            } else {
                                dbExpressField.style.display = 'none';
                            }
                        } else {
                            dbExpressField.style.display = 'none';
                        }                       
                    } else {
                        var latValue = data[latFld];
                        var lngValue = data[lngFld];
                        dbExpressMaps.initDisplayerMap(dbExpressField, latValue, lngValue);
                    }
                } else if ((dataType) && (dataType == "rating")) {
                    var stars = '<i class="fa f7-icons s-3">'+kRatingIcon+'</i>';
                    var ratingValue = parseInt(data[fld]) || 0;
                    var rt = '';
                    for (i = 0; i < ratingValue; i++) {
                        rt = rt + stars;
                    }
                    dbExpressField.innerHTML = rt;
                } else if (fld) { //Other Field Types
                    var dataValue = data[fld];
                    //HTML Entities
                    if ((dataValue) && (dataValue.length > 0) && (kDbHTMLRendering == false)) {
                        dataValue = dataValue.replace(/&/g, "&amp;").replace(/>/g, "&gt;").replace(/</g, "&lt;").replace(/"/g, "&quot;");
                    }
                    //Apply Formula
                    var dataDateType = dbExpressField.getAttribute("data-date-type") || null;
                    var fieldFunction = dbExpressField.getAttribute("data-function") || null;
                    var templateHtml = null;
                    if ((dataType) && ((dataType == "datetime") || (dataType == "date") )) {
                        var fd="";
                        var showHours = false;
                        if ((dataType == "datetime")) { showHours = true; }
                        if ( (dataValue) && (dataValue!="0") && (dataValue.toString().length>0) ) {
                            fd = thoriumapi.date.getDateTimeFromTimeStamp(dataValue, dataDateType, "", showHours);
                        } 
                        dbExpressField.innerHTML=fd;
                    } else  if ((dataType) && (dataType == "time") ) {
                        if ( (dataValue) && (dataValue!="0") && (dataValue.toString().length>0) ) {
                            dbExpressField.innerHTML=dataValue;
                        } else {
                            dbExpressField.innerHTML="";
                        }
                    } else if (fieldFunction)  { //Field with Formula
                        templateHtml = thoriumCorePlugin.virtuallist.applyFormulaToField(dbExpressField, dbExpressField.innerHTML, fld, dataValue);
                        dbExpressField.innerHTML = templateHtml;
                    } else if ((dataType) && (dataType == "rating")) {
                        var stars = '<i class="fa f7-icons s-3" >'+kRatingIcon+'</i>';
                        if (!dataValue) { dataValue = 0; }
                        var ratingValue = parseInt(dataValue) || 0;
                        var rt = '';
                        for (i = 0; i < ratingValue; i++) {
                            rt = rt + stars;
                        }
                        dbExpressField.innerHTML = dbExpressField.innerHTML.replaceAll("{{" + fld + "}}", rt);
                    } else if ((dataType) && (dataType == "video")) {
                        var videoElement = dbExpressField.querySelector("video");
                        if (videoElement) {
                            if ((dataValue) && (dataValue.length > 0)) {
                                videoElement.setAttribute("src", auth.getDbAssetsUrlRoot() + dataValue);
                            } else {
                                dbExpressField.style.display = "none";
                                videoElement.setAttribute("src", "");
                            }
                        }
                    } else if ((dataType) && (dataType == "audio")) {
                        var audioElement = dbExpressField.querySelector("audio");
                        if (audioElement) {
                            if ((dataValue) && (dataValue.length > 0)) {
                                audioElement.setAttribute("src", auth.getDbAssetsUrlRoot() + dataValue);
                            } else {
                                dbExpressField.style.display = "none";
                                audioElement.setAttribute("src", "");
                            }
                        }
                    } else if ((dataType) && (dataType == "picture")) {
                        var child = dbExpressField.querySelector('.dbexpress-image-content');
                        if (child) {
                            if (dataValue) {
                                //Classic Responsive Image
                                var root = dbExpressField.getAttribute("data-httproot") || '';
                                if (fld == "createdby_photourl") {
                                    child.style.backgroundImage = "url('" + auth.getDbAssetsUrlRoot() + dataValue + "')";
                                } else if ((dataValue.toLowerCase().indexOf("http://") != -1) || (dataValue.toLowerCase().indexOf("https://") != -1)) {
                                    child.style.backgroundImage = "url('" + dataValue + "')";
                                } else {
                                    if (data.uid) { child.setAttribute("data-img-name", dataValue); }
                                    var newurl = "url(" + auth.getDbAssetsUrlRoot() + dataValue + "?rnd=" + Math.floor(Date.now() / 1000) + ")";
                                    child.style.backgroundImage = newurl;
                                }
                            } else {
                                dbExpressField.style.display = 'none';
                            }
                        } else {
                            //Classic Responsive Image (Mais pas encore implémenté)
                            dbExpressField.setAttribute("src", auth.getDbAssetsUrlRoot() + dataValue);
                        }
                    } else if ((["embed-responsive-item"].indexOf(c) > -1) && (tag != "audio") && (tag != "video")) {
                        var root = dbExpressField.getAttribute("data-httproot") || '';
                        if (dataValue) {
                            dbExpressField.style.backgroundImage = "url('" + root + dataValue + "')";
                        } else {
                            dbExpressField.closest('.embed-responsive').style.display = 'none';
                        }

                    } else {
                        if ((dataValue) || (dataValue == 0)) {
                            var lh = dbExpressField.innerHTML;
                            var dataInner = dbExpressField.getAttribute("data-inner-template") || null;
                            if (!dataInner) {
                                dbExpressField.setAttribute("data-inner-template", lh);
                            } else {
                                lh = dataInner;
                            }
                            if (c.includes("dbexpress-number") == true) {
                                if (parseFloat(dataValue) > 0) {
                                    var dec = parseInt(dbExpressField.getAttribute("data-decimal")) || 0;
                                    var num = parseFloat(dataValue);
                                    dataValue = num.toFixed(dec);
                                }
                            }
                            if ((lh) && (fld)) {
                                dbExpressField.innerHTML = lh.replaceAll("{{" + fld + "}}", dataValue);
                                if (dbExpressField.classList.contains("text-viewer")) {
                                    dbExpressField.style.display = '-webkit-box';
                                } else {
                                    dbExpressField.style.display = 'block';
                                }
                            }
                            if (tag == "a") {
                                lh = dbExpressField.getAttribute("href");
                                if (lh) {
                                    lh = lh.replaceAll("{{" + fld + "}}", dataValue);
                                    dbExpressField.setAttribute("href", lh);
                                }
                            }
                        } else {
                            dbExpressField.style.display = 'none';
                        }
                    }
                    //Check if field is within a datalist displayer
                    var hideLine = false;
                    if ((isNaN(dataValue) == false) && (parseFloat(dataValue) == 0)) {
                        hideLine = true;
                    }
                    if ((!dataValue) || (dataValue.length == 0)) {
                        hideLine = true;
                    }
                    if (dbExpressField.style.display == 'none') {
                        hideLine = true;
                    }
                    if (hideLine == true) {
                        var parentDbDataList = dbExpressField.closest(".dbexpress-datalist");
                        if (parentDbDataList) {
                            var parentLi = dbExpressField.closest("li");
                            if (parentLi) {
                                parentLi.style.display = "none";
                            }
                        }
                    }

                }
            }
        });

        var buttons = document.querySelectorAll("#" + displayer.id + " .dbexpress-action-button");
        for (var i = 0; i < buttons.length; i++) {
            var button = buttons[i];
            var parentRepeater = button.closest('.dbexpress-virtual-list');
            if (!parentRepeater) {
                for (var j = 0; j < button.attributes.length; j++) {
                    var attrib = button.attributes[j];
                    for (const key in data) {
                        attrib.value = attrib.value.replaceAll("{{" + key + "}}", data[key]);
                    }
                    if (attrib.name == "data-service") {
                        var service = displayer.getAttribute("data-service") || null;
                        if (service) { attrib.value = service; }
                    }
                }
            }
        }
        displayer.setAttribute("data-loaded", true);
        app.emit('displayerDataLoaded[#'+displayer.id+']', displayer,data);
        displayer.style.visibility = "visible";
    },
    getDateFromTimeStamp: function (timestamp) {
        var date = new Date(timestamp * 1000);
        if (!(isNaN(date.getTime()))) {
            var intl = new Intl.DateTimeFormat("en-EN", {
                hour12: kHours12,
                hour: "2-digit",
                minute: "2-digit",
                day: "2-digit",
                month: "2-digit",
                year: "numeric",
                literal: "/"
            });
            return intl.format(date).toLowerCase();;
        } else {
            return timestamp;
        }
    },
    collectionrepeaterLineClick: function (e, t) {//OK 11-12-2020
        var rowindex = t.attr('data-index') || null;
        var uid = t.attr('data-uid') || null;
        if (rowindex) {
            thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Detect Click/Touch on Collection item line [" + rowindex + "]");
            var o = t.parents(".dbexpress-virtual-list-content");
            if (o) {
                var items = o.data('items');
                var target = o.attr('data-detail') || null;
                var itemSel = items[rowindex];
                var view = app.view;
                if ((view) && (view.length > 0)) {
                    view[0].router.navigate('/' + target + '/?rowindex=' + rowindex + '&data=' + encodeURIComponent(JSON.stringify(itemSel)) + "&key=" + uid);
                }
            }
        }
    },
    /*-- Virtual list Filter Button Click--*/
    repeaterFilterHandler: function (sender) {
        var targetRepeaterId = sender.getAttribute('data-filter-virtuallist') || null;
        var targetRepeater = document.getElementById(targetRepeaterId);
        if (targetRepeater) {
            thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Set New Filter for Repeater with ID [" + targetRepeaterId + "]");
            targetRepeater.setAttribute("data-loaded", false);
            targetRepeater.setAttribute("data-count", 0);
            //Additional Criterians
            targetRepeater.setAttribute('data-filter-field', sender.getAttribute('data-filter-field') || '');
            targetRepeater.setAttribute('data-filter-operator', sender.getAttribute('data-filter-operator') || '');
            targetRepeater.setAttribute('data-filter-value', sender.getAttribute('data-filter-value') || '');
            targetRepeater.setAttribute('data-filter-field2', sender.getAttribute('data-filter-field2') || '');
            targetRepeater.setAttribute('data-filter-operator2', sender.getAttribute('data-filter-operator2') || '');
            targetRepeater.setAttribute('data-filter-value2', sender.getAttribute('data-filter-value2') || '');
            //User Data
            targetRepeater.setAttribute('data-user-data', sender.getAttribute('data-user-data') || false);
            //sort
            targetRepeater.setAttribute('data-orderby', sender.getAttribute('data-orderby') || '');
            targetRepeater.setAttribute('data-order-sort', sender.getAttribute('data-order-sort') || '');
            //reset extra fk
            targetRepeater.removeAttribute("data-parent-fk");
            targetRepeater.removeAttribute("data-parent-fk-value");
            //Scroll attached VL page-content to top
            var pageContent = targetRepeater.closest(".page-content");
            if (pageContent) {
                pageContent.scrollTop = 0;
                var backdrop = pageContent.querySelector(".searchbar-backdrop");
                if (backdrop) { backdrop.style.opacity = 0; }
            }
            if (kShowPreloader == true) { app.preloader.show(); }

            dbExpressData.getRepeaterData(targetRepeater);
        } else {
            var m = "An error occured, the Repeater is not initialized";
            thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] " + m);
            app.dialog.alert(m);
        }
    },
    repeaterLineButtonClick: function (e, t) { //OK 11-12-2020
        app.dialog.alert('[com.thoriumbuilder.dbexpress] repeaterLineButtonClick');
        var rowindex = t.attr('data-index') || null;
        var uid = t.attr('data-uid') || null;
        var datatarget = t.attr('data-target') || null;
        var transition = t.attr('data-transition') || null;
        if (rowindex) {
            thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Detect Click/Touch on Line [" + rowindex + "]");
            var o = t.parents(".json-virtual-list-content");
            if ((o) && (o.length > 0)) {
                var vl = app.virtualList.get("#" + o[0].id);
                if (vl) {
                    var itemSel = vl.items[rowindex];
                    var view = app.view;
                    var parentVl = t.closest(".virtual-list") || null;
                    var parentID = null;
                    if (parentVl) { parentID = parentVl[0].id; }
                    if ((view) && (view.length > 0)) {
                        app.emit('VirtualListLineClick', t, rowindex, itemSel, datatarget);
                        view[0].router.navigate('/' + datatarget + '/?rowindex=' + rowindex + '&data=' + encodeURIComponent(JSON.stringify(itemSel)) + "&key=" + uid + "&parentvlid=" + parentID, { animate: true, transition: transition, reloadAll: false });
                    }
                }
            }
        }
    },
    //Flatten Form Data to item eg field1.name
    getFlatFormData: function (elt, data, item) {
        app.dialog.alert('[com.thoriumbuilder.dbexpress] getFlatFormData');
        for (var prop in data) {
            if (typeof data[prop] == "object" && data[prop] !== null) {
                if (Array.isArray(data[prop])) {
                    var p = prop.replaceAll(" ", "_");
                    item[p] = data[prop];
                } else {
                    dbExpressData.getFlatFormData(elt + "." + prop, data[prop], item);
                }
            } else {
                var p = prop.replaceAll(" ", "_");
                item[elt + "." + p] = data[prop];
            }
        }
        return item;
    },
    convertFormData: function (fld, key, value, formData) { //OK
        //Generic function: Convert Memory data to Form Data for 
        //a Given Form (used from singleRecordFormInit & fillFormFromData)
        //And fill all inputs with values
        var isShadow = fld.classList.contains("shadow-text");
        if (fld.type == 'checkbox') {
            if ((value === 'true') || (value === true) || (value === '1') || (value === 1) || (value === 'on')) {
                formData[key] = 'on';
                fld.setAttribute("checked", "checked");
            } else {
                formData[key] = 'off';
                fld.removeAttribute("checked");
            }

        } else if ((fld.type == 'select-one') || (fld.tagName.toLowerCase == 'select')) {
            $('#' + fld.id).attr('data-value', value);
            var fp = $('#' + fld.id).closest('.smart-select');
            if ((fp) && (fp.length > 0)) {
                var sm = app.smartSelect.get(fp);
                if (sm) { sm.setValue(value); }
            } else {
                $('#' + fld.id).val(value);
                var n = $('#' + fld.id).html().trim();
                if (n.length == 0) {
                    $('#' + fld.id).html('<option value="' + value + '">' + value + '</option>');
                }
            }
            formData[key] = value;

        } else if (fld.type == 'image') {
            $('#' + fld.id + '-fileinput-filename').css('visibility', 'hidden');
            if ((value) && (value.length > 0)) {
                var $ta = auth.getDbAssetsUrlRoot() + value + "?rnd=" + Math.floor(Date.now() / 1000);
                $('#' + fld.id).data("data-blob", "");
                //$('#' + fld.id + '-fileinput-filename').value(value);
                $('#' + fld.id).css('background-image', 'url(' + $ta + ')');
                $('#' + fld.id + '-fileinput-filename span').text($ta);
                $('#' + fld.id + '-remove').show();
            }

        } else if (fld.type == 'range') {
            formData[key] = value;
            var range = app.range.get(fld.parentElement);
            if (range) {
                range.setValue(value);
            } else {
                range = app.range.create(fld.parentElement);
                range.setValue(value);
            }

        } else if (isShadow == true) {
            formData[key] = value;
            var sid = fld.id.replaceAll("-shadow", '');
            var te = app.textEditor.get('#' + sid);
            if (te) {
                te.setValue(value);
            }
        } else if (fld.type == 'text') {
            fld.value = value;
            var dateType = fld.getAttribute("data-date-type");
            if ((dateType) && ((dateType == "datetime-local") || (dateType == "date"))) {
                //It is a date, we convert the UNIX timestamp to date
                if ((value) && (value != 0)) {
                    fld.setAttribute("data-timestamp", value);
                    try {
                        var showHours=false;
                        if (dateType == "datetime-local") {showHours=true}
                        var niceDate = thoriumapi.date.getDateTimeFromTimeStamp(value, dateType,null,showHours);
                        fld.value = niceDate;
                    } catch(err) {
                        app.dialog.alert(err);
                    }
                } else {
                    fld.value = "";
                }
            }
        } else if (fld.type == 'textarea') {
            fld.value = value;

        } else if (fld.type == 'radio') {
            //Loop on all Radio Items
            var radioParent = fld.closest("ul");
            var radioName = fld.getAttribute("name");
            var radioButtons = radioParent.querySelectorAll("input[name='" + radioName + "']");
            for (var i = 0; i < radioButtons.length; i++) {
                var radioItem = radioButtons[i];
                var radioValue = radioItem.value;
                if (radioValue == value) {
                    radioItem.setAttribute("checked", "checked");
                } else {
                    radioItem.removeAttribute("checked");
                }
            }
        } else if (fld.type == 'file') {
            var lineIdent = fld.getAttribute("data-fileinput-ref");
            var ot = $('#' + lineIdent);
            if ((ot) && (ot.length > 0)) {
                $('#' + lineIdent + '-fileinput-filename').show();
                $('#' + lineIdent + '-fileinput-filename').css('visibility', 'visible');
                $('#' + lineIdent + '-fileinput-filename span').text(value);
                $('#' + lineIdent + '-fileinput-filename i').show();
                $('#' + lineIdent + '-fileinput-link').show();
                $('#' + lineIdent + '-fileinput-link span').html('<a class="external" target="_blank" rel="noopener" data-rel="external" target="_blank" href="' + value + '">See attached file</a>');
                if ((value) && (value.length > 0)) {
                    $('#' + lineIdent + '-remove').show();
                } else {
                    $('#' + lineIdent + '-fileinput-filename').css('visibility', 'hidden');
                }

            }
            formData[key] = value;
        } else {
            formData[key] = value;
            fld.value = value;
        }
        return formData;
    },
    fillFormFromData: function (form, data, parentkey, parentvlid, rowindex) { //OK
        if (!(data)) { return; }
        if (form.getAttribute("data-loaded") === true) { return; };
        var item = {};
        for (var prop in data) {
            if (typeof data[prop] == "object" && data[prop] !== null) {
                item = dbExpressData.getFlatFormData(prop, data[prop], item);
            } else {
                var p = prop.replaceAll(" ", "_");
                item[p] = data[prop];
            }
        }
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Set dbExpress Form Data for form with ID [" + form.id + "]");
        for (let [key, value] of Object.entries(item)) {
            var reservedWords = ["name", "id", "description", "this", "title", "submit"]; //to be completed
            var alias = key; //.toLowerCase();
            if (reservedWords.includes(alias) === true) { alias = alias + "_alias"; } //alias for reserved words
            var fld = form.querySelector('input[name="' + alias + '"]');
            if (!fld) {
                fld = form.querySelector('textarea[name="' + alias + '"]');
            }
            if (!fld) {
                fld = form.querySelector('textarea[name="' + alias + '"]');
            }
            if (fld) {
                dbExpressData.convertFormData(fld, key, value, data);
            }
        }

        //V 3.4.1
        var x = document.querySelectorAll("#" + form.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;
            var fkName = "fk_" + service;
            var fieldValue = item[fkName];
            obj.setAttribute("data-value", fieldValue);
            //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);
                    }
                }
            }
            var mode = 0;
            mode = form.getAttribute("data-api-mode") || '0';
            if ((service) && (mode != 1)) { //Mode Update (Load from Repeater)
                dbExpressData.fillSmartSelect(obj, service);
            }
        }
        //V 3.4.1

        form.setAttribute("data-loaded", true);
        form.setAttribute("data-parent-key", parentkey);
        form.setAttribute("data-virtuallist-id", parentvlid);
        form.setAttribute("data-index", rowindex);
        form.style.visibility = "visible";
        app.emit('formDataLoaded[#'+form.id+']', form,data);
    },
};





$(document).on('click', 'a.dbexpress-search-filter', function (e) {
    e.preventDefault();
    dbExpressData.repeaterFilterHandler(this);
});

$(document).on('click', 'a.btn-json-page', function (e) {
    e.preventDefault();
    dbExpressData.repeaterLineButtonClick(e, $(this));
});


$(document).on('click', '.dbexpress-action-button[data-action="like"]', function (event) {
    event.preventDefault();
    event.stopImmediatePropagation();
});

/* --- E V E N T S  D B E X P R E S S --*/


/*-- Auth Login Events --*/
$(document).on('submit', '.dbexpress-signin-form', function (e) {
    e.preventDefault;
    var f = this;
    if (kShowPreloader == true) { app.preloader.show(); }
    auth.signInFormPost(f);
});

/*-- Auth Reset Password --*/
$(document).on('submit', '.dbexpress-resetpassword-form', function (e) {
    e.preventDefault;
    var f = this;
    if (app.input.validateInputs(f) == false) {
        thoriumapi.logEvent(0, "[com.thoriumbuilder.core] Missing Mandatory fields for Form with id [" + f.id + "]");
        return;
    }
    if (kShowPreloader == true) { app.preloader.show(); }
    auth.resetPasswordSubmitPost(f);
});

/*-- Auth Register Events --*/
$(document).on('submit', '.dbexpress-register-form', function (e) {
    e.preventDefault;
    var pw = $("#dbexpress-register-password").val();
    var pwv = $("#dbexpress-register-password-verification").val();
    if (pw != pwv) {
        app.dialog.alert('Password does not match');
        return;
    }
    if (pw.length < 6) {
        app.dialog.alert('Password too Short (6 characters min)');
        return;
    }
    var toggle = app.toggle.create({
        el: '.dbexpress-register-gdpr'
    });
    if (toggle.checked == false) {
        app.dialog.alert('You must accept the Privacy Policy');
        return;
    }

    if (kShowPreloader == true) { app.preloader.show(); }
    auth.registerFormPost();
});

/* -- User Menu Events --*/
$(document).on('click', '.dbexpress_avatar', function (e) {
    auth.handleAvatarPopup(e, this);
});

$(document).on('click', '.firebase-user-logout', function (e) {
    e.preventDefault;
    app.dialog.confirm('Logout?', function () {
        auth.logout();
    });
});

/*-- Edit Profile Form --*/
$(document).on('click', '.dbexpress-profile-submit', function (e) {
    e.preventDefault;
    if (kShowPreloader == true) { app.preloader.show(); }
    auth.saveProfile();
});

$('select').on('change', function () {
    $(".dbexpress-profile-flag").css("background-image", "url(" + thoriumCorePlugin.httpRoot + kApiRoot + "flags/4x3/" + this.value.toLowerCase() + ".svg)");
});

/* -- Links --*/
//SignIn Link
$(document).on('click', '.signinlink', function (e) {
    e.preventDefault;
    auth.showSigninPopup(e);
});

//Terms of use
$(document).on('click', '.termsofuse', function (e) {
    e.preventDefault;
    auth.showTermsPopup(e);
});

//Privacy Policy
$(document).on('click', '.privacypolicy', function (e) {
    e.preventDefault;
    auth.showPrivacyPopup(e);
});

//Register Link
$(document).on('click', '.registerlink', function (e) {
    e.preventDefault;
    auth.showRegisterPopup(e);
});

//reload Link
$(document).on('click', '.reloadlink', function (e) {
    e.preventDefault;
    location.reload();
});

//Profile link
$(document).on('click', '.profilelink', function (e) {
    e.preventDefault;
    auth.loadProfileScreen();
});

//Reset Password link
$(document).on('click', '.resetpasswwordlink', function (e) {
    e.preventDefault;
    auth.showResetPasswordScreen();
});

/* --- DATA EVENTS --*/
$(document).on("click", ".dbexpress-field-link", function (e) {
    e.preventDefault();
    var textFld = this.querySelector(".dbexpress-field");
    if (textFld) {
        textFld.style.whiteSpace = "break-spaces";
        var t = textFld.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);
        textFld.style.whiteSpace = "normal";
    }
});


$(document).on('submit', '.dbexpress-searchbar', function (e) { //Full text search
    e.preventDefault;
    var inp=e.target.querySelector("input");
    if (inp) {
        dbExpressData.executeRepeaterFullText(inp);
    }
    
});

app.on('searchbarSearch', function (f,s) {
    if ((f.el) && (f.el.getAttribute("data-custom-search") == 'true')) {
        if (s.length==0) {
            dbExpressData.executeRepeaterFullText(f.el.querySelector("input"));
        }
    }
});

$(document).on("click", ".fuzzy-link", function (e) { //Fuzzy search
    e.preventDefault();
    var value = this.innerText;
    var page = this.closest(".page-current");
    if (page) {
        var dbexpresssearchbar = page.querySelector(".dbexpress-searchbar input");
        if (dbexpresssearchbar) {
            dbexpresssearchbar.value = value;
            dbExpressData.executeRepeaterFullText(dbexpresssearchbar);
        }
    }
});


$(document).on('submit', '.form', function (e) {
    dbExpressData.submitForm(e,e.target);
});

$(document).on("click", ".calendar-submit-button", function (e) {
    e.preventDefault();
    var targetId = this.getAttribute('data-calendar') || null;
    if ((targetId) && (targetId.length > 0)) {
        if (auth.currentUser.group == 0) {
            auth.showSigninPopup();
            app.view[0].router.back();
        } else {
            dbExpressData.savecalendar(targetId);
        }
    }
});

$(document).on('click', '.dbwebshare', function (event) { //Click on DbExpress WebShare Button
    event.preventDefault();
    var url = this.getAttribute('data-url') || null;
    var title = this.getAttribute('data-share-text') || null;
    if ((url) && (title) && (navigator.share != undefined)) {
        try {
            navigator.share({ title: title, url: url });
        } catch (err) {
            app.dialog.error("Share failed:", err.message);
        }
    }
});

$(document).on('click', '.dbexpress-action-button[data-action="detail"]', function (event) { //Click on DbExpress Action Button
    event.preventDefault();
    event.stopImmediatePropagation();
    var parentRepeater = this.closest(".dbexpress-virtual-list") || null;
    if (parentRepeater) {
        dbExpressData.onRepeaterLineChange(event, this);
    } else {
        var parentDisplayer = this.closest(".dbexpress-displayer") || null;
        if (parentDisplayer) {
            var uid = this.getAttribute('data-uid') || null;
            var pk = this.getAttribute('data-pk') || null;
            var targetPage = this.getAttribute('data-target') || null;
            var service = this.getAttribute('data-service') || null;
            var transition = this.getAttribute('data-transition') || "f7-push";
            if (targetPage) {
                event.preventDefault();
                var view = app.view;
                if ((view) && (view.length > 0)) {
                    var url='/' + targetPage + "/?uid=" + uid + "&pk=" + pk + "&service=" + service;
                    var props={};
                    props.rowindex=0;
                    props.uid=uid;
                    props.pk=pk;
                    props.service=service;
                    thoriumapi.loadpage(url,props,transition);
                }
            }
        }
    }
});

$(document).on('click', '.dbexpress-action-button[data-action="parentdetail"]', function (event) { //Click on DbExpress Action Button
    event.preventDefault();
    event.stopImmediatePropagation();
    var parentRepeater = this.closest(".dbexpress-virtual-list") || null;
    var parentDisplayer = this.closest(".dbexpress-displayer") || null;
    if ((parentRepeater) || (parentDisplayer)) {
        var fk = this.getAttribute('data-fk') || null;
        if (fk.indexOf("{{") > -1) {
            thoriumapi.logEvent(2, "[com.thoriumbuilder.dbexpress] error,  action button not initialized");
        }
        var targetPage = this.getAttribute('data-target') || null;
        var service = this.getAttribute('data-parent-service') || null;
        var transition = this.getAttribute('data-transition') || "f7-push";
        if ((targetPage) && (service) && (fk)) {
            dbExpressData.getParentRecord(service, fk, targetPage, transition);
        }
    }
});


$(document).on('click', '.dbexpress-action-button[data-action="delete"]', function (event) { //Click on DbExpress Action Button delete
    event.preventDefault();
    event.stopImmediatePropagation();
    dbExpressData.handleDelete(this);
});

$(document).on('click', '.item-dbexpress', function (event) { //Click on Repeater Line
    event.preventDefault();
    dbExpressData.onRepeaterLineChange(event, this);
});

$(document).on('click', '.pinlink', function (event) {
    event.preventDefault();
    dbExpressData.sendPin(this);
});


$(document).on('infinite', function (el) { //OK
    thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] infinite scroll event detected");
    if (dbExpressData.allowInfinite == true) {
        var obl = el.target;
        var vlItems = obl.querySelectorAll('.virtual-list[data-infinite="true"]')
        for (var i = 0; i < vlItems.length; i++) {
            var elt = vlItems[i];
            var fetched = elt.getAttribute("data-fetched") || null;
            if (fetched != "true") {
                var preloader = obl.querySelector(".infinite-scroll-preloader");
                if (preloader) {
                    // preloader.style.display = "block"; 
                    preloader.style.visibility = "visible";
                }
                thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] Loading Additional Data");
                dbExpressData.allowInfinite = false; //Set to loading data
                var parentpk = elt.getAttribute("data-parent-pk") || null;
                var search = elt.getAttribute("data-search") || null;
                setTimeout(function () {
                    dbExpressData.getRepeaterData(elt, parentpk, true, search);
                }, 1000);
            }
        }
    } else {
        thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] infinite scroll handler deactivated");
    }
});

//Global Events
$(document).on('page:mounted', function (e,page) { //OK
    //Page is just inserted in DOM but is not yest visible -- Keep mounted !important
    e.preventDefault;
    thoriumapi.logEvent(0, "[com.thoriumbuilder.dbexpress] page:mounted");
    dbExpressData.onPageMounted(e,page);
})

app.on('onAuthStateChanged', function (user) {
    if (user) {
        dbExpressData.onAuthStateChanged();
        if (user.isAnonymous != true) {
            auth.getCountryList();
        }
    }
});
