
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
// Misc functions
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//


function SetButtonClickEventHandler(element) {

    if (!element)
        return;

    // Does the button already have a click event handler?
    // If so, don't add this one
    var bHasClickHandler = ElementHasClickHandler(element);
    if (bHasClickHandler)
        return;

    //console.log("SetButtonClickEventHandler: " + $(element).attr("id"));

    // Add an event handler
    $(element).on("click", StandardButtonClickEventHandler);
}


function StandardButtonClickEventHandler() {

    var id = $(this).attr("id");
    var data_image = $(this).attr("data-image");

    // Type of content to display
    var data_stcontent = $(this).attr("data-stcontent");
    var data_stfunction = $(this).attr("data-stfunction");

    // Tab title, either from data or the button html text
    var data_title = $(this).attr("data-title");
    if (!data_title)
        data_title = $(this).html();

    // Does this button show ST content?
    if (data_stcontent && data_stcontent.length > 0) {
        ShowStContent(data_stcontent, ""/*ValueString*/, data_title);
        return;
    }

    // Does this button have a function to call?
    if (data_stfunction && data_stfunction.length > 0) {
        ShowContentByMethodName(data_stfunction);
        return;
    }

    // Does this button launch a web page?
    var data_url = $(this).attr("data-url");
    if (data_url && data_url.length > 0) {
        LaunchUrl(data_url);
        return;
    }

    // Close a div?
    var data_closediv = $(this).attr("data-closediv");
    if (data_closediv) {
        var DivToClose = $("#" + data_closediv);
        if (DivToClose && DivToClose.length > 0) {
            DivToClose.css('display', 'none');
            return;
        }
    }

    ToastError("StandardButtonClickEventHandler", "Hmmm, nothing to do for: " + id);

    // No actionable attributes to tell what to do in this function
    // Do nothing
}


function StandardButtonClickEventHandlerForStContent(stcontent, title) {

    ShowStContent(stcontent, ""/*ValueString*/, title);
}



function ElementHasClickHandler(element) {

    // Inline HTML attribute: onclick="..."
    var onClickHandler = $(element).attr("onClick");
    if (onClickHandler != undefined)
        return true;

    // DOM property assignment: element.onclick = function(){...}
    if ($(element)[0].onclick != null)
        return true;

    // Does the button have a "click" event already?
    $.each($._data($(element)[0], "events"), function (i, event) {
        // i is the event type, like "click"
        $.each(event, function (j, h) {
            // h.handler is the function being called
            if (h == "click") {
                return true;
            }
        });
    });

    return false;
}

function LaunchUrl(url) {

    var win = window.open(url, '_blank');
    if (win) {
        //Browser has allowed it to be opened
        win.focus();
    } else {
        //Browser has blocked it
        ToastError("Popup", "Please allow popups for this website");
    }
}



// Clear all the values used by Ajax
function ClearSelectedObjectHiddenValues() {

    $("#HiddenSelectedDataTypeId").val("");
    $("#HiddenSelectedTableId").val("");
    $("#HiddenSelectedRecordId").val("");
}




/**
 * Set up a callback that is triggered when an element becomes visible
 * Lazy loading technique to call only when the element is actually viewed
 * @param {any} element
 * @param {any} callback
 */
function onVisible(element, callback) {

    if (!element) {
        //console.log("Hmmm, onVisible as no element: " + callback);
        if (callback)
            callback();
        return;
    }

    new IntersectionObserver((entries, observer) => {
        entries.forEach(entry => {
            if (entry.intersectionRatio > 0) {
                callback(element);
                observer.disconnect();
            }
        });
    }).observe(element);
}







function ToastMessage(title, msg) {

    if (msg) {
        msg = msg.replace(new RegExp('\r\n', 'g'), '\n');
        msg = msg.replace(new RegExp('\r', 'g'), '\n');
        msg = msg.replace(new RegExp('\n', 'g'), '<br />');
    }

    LoadToasterDisplayValuesFromCookies();

    // Toaster messages turned off?
    if (!bToasterDisplayIsChecked)
        return;

    var nTimeoutMs = nToasterDisplaySeconds * 1000;
    $.toaster({ settings: { timeout: nTimeoutMs } });
    $.toaster(msg, title, 'success');
    $.toaster.reset();
}

function ToastError(title, msg) {

    if (msg && !Array.isArray(msg)) {
        msg = msg.replace(new RegExp('\r\n', 'g'), '\n');
        msg = msg.replace(new RegExp('\r', 'g'), '\n');
        msg = msg.replace(new RegExp('\n', 'g'), '<br />');
    }

    // Always show errors, cannot turn these off
    // Toaster messages turned off?
    //if (!bToasterDisplayIsChecked)
    //    return;

    var nTimeoutMs = nToasterDisplaySeconds * 1000;
    $.toaster({ settings: { timeout: nTimeoutMs } });
    $.toaster(msg, title, 'danger');
    $.toaster.reset();
}

function ToastErrorWithTimeout(title, msg, timeoutms) {

    if (msg && !Array.isArray(msg)) {
        msg = msg.replace(new RegExp('\r\n', 'g'), '\n');
        msg = msg.replace(new RegExp('\r', 'g'), '\n');
        msg = msg.replace(new RegExp('\n', 'g'), '<br />');
    }

    $.toaster({ settings: { timeout: timeoutms } });
    $.toaster(msg, title, 'danger');
    $.toaster.reset();
}

/**
 * Remove all the toast from the toaster
 */
function ToastClear() {
    $.toaster({ });
    $.toaster.empty();
}



//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
// Set virtual directory for Ajax calls
// Examples:
// https://sthosted66.com/bigapp
// https://sthosted66.com/anotherapp

var pathname = window.location.pathname;
var VirtualDirectory;
if (pathname == "/")
    VirtualDirectory = "";
else if (pathname.indexOf("localhost") >= 0 && pathname.indexOf(":") >= 0) {
    VirtualDirectory = "";
}
else {
    if ((pathname.lastIndexOf('/')) === pathname.length + 1) {
        VirtualDirectory = pathname.substring(pathname.indexOf('/'), pathname.lastIndexOf('/'));
    } else {
        VirtualDirectory = pathname;
    }
}




//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//

// Get the current mouse position from any function
var currentMousePos = { x: -1, y: -1 };
$(document).mousemove(function (event) {
    currentMousePos.x = event.pageX;
    currentMousePos.y = event.pageY;
});
$(document).mousedown(function (event) {
    currentMousePos.x = event.pageX;
    currentMousePos.y = event.pageY;
});


//$(document).ready(function () {

// Detect when Back button pressed
// This doesn't work well on Android phone
    //if (window.history && window.history.pushState) {
    //    //window.history.pushState('forward', null, './#forward');
    //    window.history.pushState('', null, './');
    //    $(window).on('popstate', function () {

    //        // Back or Forward button pressed (cannot tell which in this version of jQuery)
    //        OnBackContent();
    //        ToastMessage("Back to previous content");
    //    });
    //}

//});



//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//


function base64ToByteArray(base64String) {
    try {
        var sliceSize = 1024;
        var byteCharacters = atob(base64String);
        var bytesLength = byteCharacters.length;
        var slicesCount = Math.ceil(bytesLength / sliceSize);
        var byteArrays = new Array(slicesCount);

        for (var sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) {
            var begin = sliceIndex * sliceSize;
            var end = Math.min(begin + sliceSize, bytesLength);

            var bytes = new Array(end - begin);
            for (var offset = begin, i = 0; offset < end; ++i, ++offset) {
                bytes[i] = byteCharacters[offset].charCodeAt(0);
            }
            byteArrays[sliceIndex] = new Uint8Array(bytes);
        }
        return byteArrays;
    } catch (e) {
        console.log("Couldn't convert to byte array: " + e);
        return undefined;
    }
}


//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//


/**
 * DOM Element: Ensure visible
 * @param {any} target
 */
function ScrollIntoViewIfNeeded(target) {

    // DOM element
    // ensurevisible
    if (!target)
        return;

    var BoundingClientRect = target.getBoundingClientRect();
    var NextElement = FindNextVisibleElement(target, 100/*DistanceInPixels*/);

    if (target.getBoundingClientRect().bottom > window.innerHeight) {
        if (NextElement)
            NextElement.scrollIntoView(false);
        else
            target.scrollIntoView(false);
    }

    if (target.getBoundingClientRect().top < 0) {
        target.scrollIntoView();
    }

    // Scroll horizontally
    target.scrollIntoView({ block: "end", inline: "end" });
}


/**
 * Ugly hack to find an element farther down the page
 * Used to scroll into view, so that the real element is plainly visible with a little margin below it
 * @param {any} element
 * @param {any} DistanceInPixels
 */
function FindNextVisibleElement(element, DistanceInPixels) {

    try {
        var BoundingClientRect = element.getBoundingClientRect();
        var NextElement = element.nextSibling;
        while (NextElement && NextElement.nodeType != 1)
            if (NextElement.nextSibling) NextElement = NextElement.nextSibling; else NextElement = NextElement.parentElement;
        if (!NextElement)
            return;

        for (let i = 0; i < 100; i++) {

            // Is the next element down far enough?
            var NextBoundingClientRect = NextElement.getBoundingClientRect();
            if (NextBoundingClientRect.top - BoundingClientRect.top > DistanceInPixels)
                return NextElement;

            if (NextElement.nextSibling) NextElement = NextElement.nextSibling; else NextElement = NextElement.parentElement;
            while (NextElement && NextElement.nodeType != 1)
                if (NextElement.nextSibling) NextElement = NextElement.nextSibling; else NextElement = NextElement.parentElement;
            if (!NextElement)
                return;
        }
    }
    catch (err) {
    }
}


///**
// * Get the text that is displayed for this element
// * Dig into the children if this is a div
// * @param {any} ElementObject
// * @returns
// */
//function GetElementText(ElementObject) {

//    if (!ElementObject)
//        return "";
//    if (ElementObject.length == 0)
//        return "";

//    var elementType = ElementObject.prop('nodeName');

//    // Unknown
//    return ElementObject.text();
//}


//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//


/**
 * Open or close the dropdown div
 * @param {any} DropdownDivId
 */
function ToggleDropdownVisible(DropdownDivId) {

    var x = document.getElementById(DropdownDivId);
    if (!x)
        return;

    if (x.style.display != 'none') {
        x.style.display = 'none';
    } else {
        x.style.display = 'block';
        SetDropDownIsVisible(DropdownDivId);
        RepositionDropdownUnderInitiator(DropdownDivId);
        ScrollIntoViewIfNeeded(x);
    }
}

function CloseDropdown(DropdownDivId) {

    var x = document.getElementById(DropdownDivId);
    if (x) {
        x.style.display = 'none';

        // Remove the "click outside dropdown" listener function
        // This function recieves clicks anywhere in the window except this dropdown div
        document.removeEventListener("click", OnClickOutsideDropdown);
    }
}

function ClosePopup(PopupDivId, bRemoveFromBody) {

    var x = document.getElementById(PopupDivId);
    if (x) {
        x.style.display = 'none';

        // Remove the "click outside popup" listener function
        // This function recieves clicks anywhere in the window except this dropdown div
        document.removeEventListener("click", OnClickOutsidePopupDiv);

        // Remove from body
        if (bRemoveFromBody) {
            $('#' + PopupDivId).remove();
        }
    }
}


function CloseLastDropdown() {

    // Is another dropdown open now?
    var CurrentDropdownDivId = $('input[id="HiddenDropdownId"]').val();
    if (CurrentDropdownDivId)
        CloseDropdown(CurrentDropdownDivId)

    // Clear the dropdown Id
    $('input[id="HiddenDropdownId"]').val("");
}

function CloseLastPopup() {

    // Is another popup open now?
    var CurrentPopupDivId = $('input[id="HiddenPopupDivId"]').val();
    var bRemoveFromBody = $('input[id="HiddenPopupRemoveFromBody"]').val();

    if (CurrentPopupDivId)
        ClosePopup(CurrentPopupDivId, bRemoveFromBody)

    // Clear the popup Id
    $('input[id="HiddenPopupDivId"]').val("");
    $('input[id="HiddenPopupRemoveFromBody"]').val("");
}


/**
 * Keep track of the most recently opened dropdown so it can be closed automatically
 * @param {any} DropdownDivId
 */
function SetDropDownIsVisible(DropdownDivId) {

    // Is another dropdown open now?
    var CurrentDropdownDivId = $('input[id="HiddenDropdownId"]').val();
    if (CurrentDropdownDivId) {
        if (CurrentDropdownDivId != DropdownDivId) {
            var CurrentDropdownDiv = $("#" + CurrentDropdownDivId);
            if (CurrentDropdownDiv && CurrentDropdownDiv.length > 0) {
                // Close the current one
                CurrentDropdownDiv[0].style.display = 'none';
            }
        }
    }

    // Set the new Id
    $('input[id="HiddenDropdownId"]').val(DropdownDivId);

    // Add the "click outside dropdown" listener function
    // This function recieves clicks anywhere in the window except this dropdown div
    DropDownOpenTime = new Date($.now());
    document.addEventListener("click", OnClickOutsideDropdown);
}

function RepositionDropdownUnderInitiator(DropdownDivId) {

    if (!DropdownDivId)
        return;
    var DropdownDiv = $("#" + DropdownDivId);
    if (!DropdownDiv)
        return;
    if (DropdownDiv.length == 0)
        return;

    // Get window size
    var body = $(window);
    var bw = body.width();
    var bh = body.height();

    // Find "initiator" so dropdown can be placed below it
    // This is the icon users click to open dropdown
    var data_initiator = DropdownDiv.attr("data-initiator");
    if (!data_initiator)
        return;
    var DataInitiatorDiv = $("#" + data_initiator);
    if (!DataInitiatorDiv)
        return;
    if (DataInitiatorDiv.length == 0)
        return;

    var InitiatorPostion = DataInitiatorDiv.position();
    var InitiatorOuterHeight = DataInitiatorDiv.outerHeight(true);

    // Get dropdown size
    // Do not allow dropdown to be larger than screen
    var w = DropdownDiv.width();
    var h = DropdownDiv.height();
    var bot = DataInitiatorDiv.position().top + DataInitiatorDiv.outerHeight(true);
    if (w > (bw-50))
        DropdownDiv.css({ "width": (bw - 50) + "px", });
    if (h > (bh - bot - 100))
        DropdownDiv.css({ "height": (bh - bot - 100) + "px", });

    // Align dropdown to the right edge of the initiator (icon user clicked)
    DropdownDiv.position({
        of: DataInitiatorDiv,
        my: 'right-10 top+20',
        at: 'right top'
    });
}

/**
 * Find a parent that is the dropdown panel div
 * Close this div when selecting an item
 * @param {any} ElementObject
 */
function FindDropdownPanelParent(ElementObject) {

    if (!ElementObject)
        return null;

    var DropdownParent = ElementObject.parents('.custom-dropdown-panel');
    return DropdownParent;
}


function FindDropdownInitiator(ElementObject) {

    if (!ElementObject)
        return null;

    // First, find the dropdown parent div that this element is inside
    // Then find the "initiator" of this dropdown. That is the property container
    var DropdownParent = FindDropdownPanelParent(ElementObject)
    if (!DropdownParent)
        return null;
    if (DropdownParent.length == 0)
        return null;

    // Get the id of the "initiator" dov
    var DropdownInitiatorId = DropdownParent.attr('data-initiator');
    if (!DropdownInitiatorId)
        return null;

    // This div "initiates" the dropdown when clicked
    var DropdownInitiator = $("#" + DropdownInitiatorId);
    return DropdownInitiator;
}


/**
 * Find a parent with class=propertycontainer
 * This div will have the following "data" attributes
 * 1. data-propertytype (the System.Type of the property value)
 * 2. data-propertyid (the DbColumn for the recordset)
 * 3. data-propertyname (the name of the propery being set)
 * 4. data-objecttype (the System.Type of the recordset object)
 * 5. data-objecttable (the database table for the recordset)
 * 6. data-objectid (the GUID of the recordset)
 * @param {any} ElementObject
 */
function FindPropertyContainer(ElementObject) {

    if (!ElementObject)
        return null;

    // Is the property container our parent?
    var PropertyContainer = ElementObject.parents('.propertycontainer');
    if (PropertyContainer && PropertyContainer.length > 0)
        return PropertyContainer;

    // Is this a dropdown div?
    // If so, it is not a child of the property container, instead it is outside
    // Find the div that "initiates" the dropdown this element is inside
    // This is likely the property container with all the "data" attributes about the property
    var DropdownInitiator = FindDropdownInitiator(ElementObject);
    if (!DropdownInitiator)
        return null;
    if (DropdownInitiator.length == 0)
        return null;

    // Is this the property container (probably)
    if (DropdownInitiator.hasClass("propertycontainer"))
        return DropdownInitiator;

    PropertyContainer = ElementObject.parents('.propertycontainer');
    return PropertyContainer;
}


/**
 * Get a single "TAG" value from this string
 * Example: "TAG-FUNCTION:OnDeleteRecordsetAttachment(\"" + sDbTable + "\", \"" + sRecordId + "\", \"" + sAttachment + "\")" + "~" + "TAG-IMAGE:" + Images.DELETE;
 * @param {any} ValueString
 * @param {any} TagName
 */
function ExtractTagValue(ValueString, TagName) {

    if (!ValueString)
        return "";

    // All tags are split by tilde
    var ValueArray = ValueString.split('~');
    if (!ValueArray)
        return "";
    if (ValueArray.length == 0)
        return "";

    // Find the tag in the array
    for (const sTagPlusValue of ValueArray) {
        if (sTagPlusValue.indexOf(TagName) >= 0) {
            var sValue = sTagPlusValue.replace(TagName, "");
            return sValue;
        }
    }

    // No value
    return "";
}

function ExtractParameterValueByName(params, paramname) {

    if (!params)
        return null;
    if (!Array.isArray(params))
        return null;
    if (params.length == 0)
        return null;

    for (let i = 0; i < params.length; ++i) {
        // The first parameter is the name
        // The next parameter is the actual value
        var param = params[i];

        // Array inside this array?
        if (Array.isArray(param)) {
            var sReturn = ExtractParameterValueByName(param, paramname);
            if (typeof sReturn !== 'undefined')
                return sReturn;
        }

        var paramvalue = param[paramname];
        if (typeof paramvalue !== 'undefined')
            return paramvalue;
    }

    // Not found in array
    return null;
}

var DropDownOpenTime = new Date();

function OnClickOutsideDropdown() {

    // Strangely, click comes in immediate after display
    // Ignore immediate clicks
    var clicktime = new Date($.now());
    var timeDiff = clicktime - DropDownOpenTime;
    if (timeDiff < 200)
        return;

    // Get div element to check if click is outside it
    var CurrentDropdownDivId = $('input[id="HiddenDropdownId"]').val();
    if (!CurrentDropdownDivId || CurrentDropdownDivId == null)
        return;
    var DropdownDivObject = $("#" + CurrentDropdownDivId);
    if (!DropdownDivObject || DropdownDivObject.length == 0)
        return;
    var DropdownDivElement = DropdownDivObject[0];
    if (!DropdownDivElement)
        return;

    // Click outside div?
    if (DropdownDivElement !== event.target && !DropdownDivElement.contains(event.target)) {
        CloseLastDropdown();
        //ToastError("OnClickOutsideDropdown", "Got it!");
    }
}



/**
 * Disable access to the entire page
 * User can reopen it again, but at least they can't continue this session
 * */
function ShutdownApp(DisplayMessage) {

    // Them them why the app is shut down
    ToastErrorWithTimeout("", DisplayMessage, 60000);

    // Cover the entire page
    $("body").append('<div id="overlay" style="background-color: rgba(100, 100, 128, 0.8);position:absolute;top:0;left:0;height:100%;width:100%;z-index:999"></div>');

}



/**
 * Increase or decrease the font size for an element
 * @param {any} Element
 * @param {any} bUp
 */
function ElementFontSizeUp(Element, bUp) {

    if (!Element)
        return;

    var style = window.getComputedStyle(Element, null).getPropertyValue('font-size');
    var fontSize = parseFloat(style);
    fontSize = (fontSize + (bUp ? 1 : -1));
    Element.style.fontSize = fontSize + 'px';
}

/**
 * Increase or decrease the font size for all children in an element
 * @param {any} Parent
 * @param {any} bUp
 */
function ChildrenFontSizeUp(Parent, bUp) {

    if (!Parent)
        return;
    var Children = Parent.find("*");
    if (!Children)
        return;

    // Loop over all children
    for (i = 0; i < Children.length; i++) {
        var Child = Children[i];
        ElementFontSizeUp(Child, bUp);
    }
}



//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
// Open the Please Wait dialog
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
var bPleaseWaitDialogIsOpen = false;
function OpenPleaseWaitDialog(TitleString) {

    var HeaderText = TitleString;
    if (!HeaderText)
        HeaderText = "Please Wait";

    // Open the dialog
    // igniteui.com/help/igdialog-property-reference
    $("#igDialogPleaseWait").igDialog({ modal: false, headerText: HeaderText, height: 150, width: 250, resizable: false, showCloseButton: false, closeOnEscape: true, openAnimation: "", closeAnimation: "fade"/*, openAnimation: "fade"*/ });
    $("#igDialogPleaseWait").igDialog("open");

    CenterDialog("igDialogPleaseWait");

    // Set up controls
    bPleaseWaitDialogIsOpen = true;
}


//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
// Close the dialog
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
function ClosePleaseWaitDialog() {
    $("#igDialogPleaseWait").igDialog("close");
    bPleaseWaitDialogIsOpen = false;
}

function IsPleaseWaitDialogOpen() {
    return bPleaseWaitDialogIsOpen;
}





//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//


/**
 * Convert Base64 to binary
 * @param {any} Base64String
 */
function Base64StringToBlob(Base64String) {

    if (!Base64String)
        return new Blob();

    try {
        // Strip image tag from base64 data
        Base64String = Base64String.replace(/data:image\/png;base64,/, '');

        var binary = atob(Base64String)
        var array = new Uint8Array(binary.length)
        for (var i = 0; i < binary.length; i++) { array[i] = binary.charCodeAt(i) }
        var blob = new Blob([array]);
        return blob;
    }
    catch (e) {
        return new Blob();
    }
}


function Base64StringToCharString(Base64String) {

    if (!Base64String)
        return "";

    try {
        // Strip image tag from base64 data
        Base64String = Base64String.replace(/data:image\/png;base64,/, '');
        var binary = atob(Base64String)
        return binary;
    }
    catch (e) {
        return "";
    }
}



//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//


function Encrypt146(str) {
    if (!str) str = "";
    str = (str == "undefined" || str == "null") ? "" : str;
    try {
        var key = 146;
        var pos = 0;
        ostr = '';
        while (pos < str.length) {
            ostr = ostr + String.fromCharCode(str.charCodeAt(pos) ^ key);
            pos += 1;
        }

        return ostr;
    } catch (ex) {
        return '';
    }
}

function Decrypt146(str) {
    if (!str) str = "";
    str = (str == "undefined" || str == "null") ? "" : str;
    try {
        var key = 146;
        var pos = 0;
        ostr = '';
        while (pos < str.length) {
            ostr = ostr + String.fromCharCode(key ^ str.charCodeAt(pos));
            pos += 1;
        }

        return ostr;
    } catch (ex) {
        return '';
    }
}



//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
// OnIdle
// Send emails and perform period updates


var bOnIdleStarted = false;

/**
 * Send emails and perform period updates
 * */
function StartOnIdle() {
    if (bOnIdleStarted)
        return;
    bOnIdleStarted = true;


// Hold off for now....
// This probably needs a better solution, such as a separate EXE process to send email notifications and update database records
//    // Single kickoff, just after login
//    setTimeout(function () { AjaxOnIdle(); }, 10000);

//    // Repeat every x minutes
//    setInterval(function () { AjaxOnIdle(); }, (30/*mins*/ * 60/*secs*/ * 1000/*ms*/));
}



function AjaxOnIdle() {

    var params = {
    };

    $.ajax({
        url: VirtualDirectory + "/Home/AjaxOnIdle",
        type: "POST",
        data: JSON.stringify(params),
        contentType: "application/json",
        success: function (response) {
            if (response.Success) {
                }
        },
        error: function (xhr, status, error) {
            var errormsg = "Error " + xhr.status + ", " + xhr.statusText;
            ToastError("AjaxOnIdle", errormsg);
            console.log('AJAX: ' + errormsg);
        }
    });
}



//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//


function FocusAndSelectAllOnTimeout(elementid) {

    try {
        if (!elementid)
            return;
        var ElementObject = $("#" + elementid);
        if (!ElementObject)
            return;
        if (ElementObject.length == 0)
            return;

        setTimeout(function () {
            ElementObject.show();
            ElementObject[0].focus();
            ElementObject.get(0).focus();
            ElementObject.focus();
            ElementObject.select();
        }, 500);
    } catch (e) {
    }
}


function PlayBase64Sound(Base64SoundString) {

    if (!Base64SoundString)
        return;
    if (Base64SoundString.length == 0)
        return;

    try {
        var snd = new Audio(Base64SoundString);
        snd.play();
    } catch (e) {
    }

    //ToastError("Play sound", "Uhhh, not just yet!");
}

/**
 * Override the default favicon with a new icon from the database
 * See "Varinfo" database record, for resellers and product overloads
 * Use the 32x32 logo that is loaded into a Varinfo.ImageObject32 record
 * Varinfo.ImagePath32 can be used to load a file from disk and place the data into Varinfo.ImageObject32
 * @param {any} sDataUri
 * @returns
 */
function SetFaviconFromDataUri(sDataUri) {
    if (!sDataUri) return;
    var link = document.querySelector("link[rel='icon'], link[rel='shortcut icon']");
    if (link) { link.href = sDataUri; link.type = "image/png"; }
}




//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//
//\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\////\\//



function GetMainAppCssLinksAsString() {
    var sCssLinks = "";

    sCssLinks += "<link href=\"~/Content/standardtime-main.css?random-characters-@DateTime.Now\" rel=\"stylesheet\" />\r\n";
    sCssLinks += "<link href=\"~/Content/standardtime-ribbon.css?random-characters-@DateTime.Now\" rel=\"stylesheet\" />\r\n";
    sCssLinks += "<link href=\"~/Content/standardtime-search.css?random-characters-@DateTime.Now\" rel=\"stylesheet\" />\r\n";
    sCssLinks += "<link href=\"~/Content/standardtime-toolbar.css?random-characters-@DateTime.Now\" rel=\"stylesheet\" />\r\n";
    sCssLinks += "<link href=\"~/Content/standardtime-homeicons.css?random-characters-@DateTime.Now\" rel=\"stylesheet\" />\r\n";
    sCssLinks += "<link href=\"~/Content/standardtime-dockmanager.css?random-characters-@DateTime.Now\" rel=\"stylesheet\" />\r\n";
    sCssLinks += "<link href=\"~/Content/standardtime-gantt.css?random-characters-@DateTime.Now\" rel=\"stylesheet\" />\r\n";

    return sCssLinks;
}

function AddMainAppCssToDocument(DocumentHead) {

    AddMainAppCssFileToDocument(DocumentHead, "/Content/standardtime-main.css");
    AddMainAppCssFileToDocument(DocumentHead, "/Content/standardtime-homeicons.css");
    AddMainAppCssFileToDocument(DocumentHead, "/Content/standardtime-dockmanager.css");
    AddMainAppCssFileToDocument(DocumentHead, "/Content/standardtime-gantt.css");

    // Works... but...
    // Something really bad happens to javascript function because of this
    // Requires window.location.reload() afterward
    //var MainAppHeadElements = document.getElementsByTagName('head')[0].innerHTML;
    //var NewHeadElements = $(MainAppHeadElements).clone();
    //$(DocumentHead).append(NewHeadElements);
}

function AddMainAppCssFileToDocument(DocumentHead, cssurl) {

    $.ajax({
        url: cssurl,
        success: function (data) {
            $(DocumentHead).append("<style>" + data + "</style>");
        }
    })
}




function treatAsUTC(date) {
    var result = new Date(date);
    result.setMinutes(result.getMinutes() - result.getTimezoneOffset());
    return result;
}

function daysBetween(startDate, endDate) {
    var millisecondsPerDay = 24 * 60 * 60 * 1000;
    return (treatAsUTC(endDate) - treatAsUTC(startDate)) / millisecondsPerDay;
}

function isValidDate(d) {
    return d instanceof Date && !isNaN(d);
}

function addDays(date, number) {
    const newDate = new Date(date);
    return new Date(newDate.setDate(newDate.getDate() + number));
}


function FindLeftPositionWithinParent(FindObject, ParentObject) {

    var nLeftPostion = 0;

    while (!FindObject.is(ParentObject)) {

        var nLeft = FindObject.position().left;
        nLeftPostion += nLeft;

        // Next
        FindObject = FindObject.parent();
    }

    return nLeftPostion;
}

function ReplaceAllColumnNameTags(sDbColumn, bWithTagName) {

    if (bWithTagName) {
        sDbColumn = sDbColumn.replaceAll("[", "TAG-LEFTBRACKET");
        sDbColumn = sDbColumn.replaceAll("]", "TAG-RIGHTBRACKET");
        sDbColumn = sDbColumn.replaceAll("/", "TAG-SLASH");
        sDbColumn = sDbColumn.replaceAll(" ", "TAG-SPACE");
        sDbColumn = sDbColumn.replaceAll("%", "TAG-PERC");
        sDbColumn = sDbColumn.replaceAll("~", "TAG-TILDE");
        sDbColumn = sDbColumn.replaceAll(".", "TAG-DOT");
    }
    else {
        sDbColumn = sDbColumn.replaceAll("TAG-LEFTBRACKET", "[");
        sDbColumn = sDbColumn.replaceAll("TAG-RIGHTBRACKET", "]");
        sDbColumn = sDbColumn.replaceAll("TAG-SLASH", "/");
        sDbColumn = sDbColumn.replaceAll("TAG-SPACE", " ");
        sDbColumn = sDbColumn.replaceAll("TAG-PERC", "%");
        sDbColumn = sDbColumn.replaceAll("TAG-TILDE", "~");
        sDbColumn = sDbColumn.replaceAll("TAG-DOT", ".");
    }
    return sDbColumn;
}


//function AbsoluteCoordinates($element) {
//    var sTop = $(window).scrollTop();
//    var sLeft = $(window).scrollLeft();
//    var w = $element.width();
//    var h = $element.height();
//    var offset = $element.offset();
//    var $p = $element;
//    var elementtype = $p.prop('nodeName');

//    while ($p && elementtype != 'body' && elementtype != 'BODY') {
//        var pOffset = $p.parent().offset();
//        if (typeof pOffset == 'undefined') break;
//        offset.left = offset.left + (pOffset.left);
//        offset.top = offset.top + (pOffset.top);
//        $p = $p.parent();
//        elementtype = $p.prop('nodeName');
//    }

//    var pos = {
//        left: offset.left + sLeft,
//        right: offset.left + w + sLeft,
//        top: offset.top + sTop,
//        bottom: offset.top + h + sTop,
//    }
//    pos.tl = { x: pos.left, y: pos.top };
//    pos.tr = { x: pos.right, y: pos.top };
//    pos.bl = { x: pos.left, y: pos.bottom };
//    pos.br = { x: pos.right, y: pos.bottom };
//    //console.log( 'left: ' + pos.left + ' - right: ' + pos.right +' - top: ' + pos.top +' - bottom: ' + pos.bottom  );
//    return pos;
//}


(function ($) {
    $.fn.getStyleObject = function () {
        var dom = this.get(0);
        var style;
        var returns = {};
        if (window.getComputedStyle) {
            var camelize = function (a, b) {
                return b.toUpperCase();
            };
            style = window.getComputedStyle(dom, null);
            for (var i = 0, l = style.length; i < l; i++) {
                var prop = style[i];
                var camel = prop.replace(/\-([a-z])/g, camelize);
                var val = style.getPropertyValue(prop);
                returns[camel] = val;
            };
            return returns;
        };
        if (style = dom.currentStyle) {
            for (var prop in style) {
                returns[prop] = style[prop];
            };
            return returns;
        };
        return this.css();
    }
})(jQuery);





function AjaxQueryStartsWith(DbTableName, sFindPartialName, successcallback) {

    var params = {
        "DbTableName": DbTableName,
        "FindPartialName": sFindPartialName
    };

    $.ajax({
        url: VirtualDirectory + "/Home/AjaxQueryStartsWith",
        type: "POST",
        data: JSON.stringify(params),
        contentType: "application/json",
        success: function (response) {
            if (response.Success) {
                //ToastMessage("AjaxQueryStartsWith", response.CsvNames);
                if (successcallback)
                    successcallback(DbTableName, sFindPartialName, response.CsvNames);
            }
            else {
                ToastError("AjaxQueryStartsWith", response.ErrorResults);
            }
        },
        error: function (xhr, status, error) {
            var errormsg = "Error " + xhr.status + ", " + xhr.statusText;
            ToastError("AjaxQueryStartsWith", errormsg);
            console.log('AJAX: ' + errormsg);
        }
    });
}
