/// <summary>
/// DataLab mvc controls and extensions.
/// </summary>

/* Global extensions */
Number.prototype.format = function (n, x) {
    var s = '.';
    var c = ',';

    var re = '\\d(?=(\\d{' + (x || 3) + '})+' + (n > 0 ? '\\D' : '$') + ')',
        num = this.toFixed(Math.max(0, ~~n));

    return (c ? num.replace('.', c) : num).replace(new RegExp(re, 'g'), '$&' + (s || ','));
};

/* View partial postback */
var _partialPostbackCallback = null;
var _partialPostbackErrorCallback = null;
function _partialPostback(form, callback, errorCallback, queryString) {
    try {
        var formUrl = form.action;

        var container = $("#" + form.name + "Container").get(0);
        if (!container) {
            alert("Partial postback container '" + form.name + "Container' not found. Add a partial postback container to the view.");
            return false;
        }

        _showLoader(container);
        _partialPostbackCallback = callback;
        _partialPostbackErrorCallback = errorCallback;

        if (!queryString) {
            queryString = "";
        } else {
            queryString = "&" + queryString;
        }

        var options = {
            success:    _partialPostbackResponse,
            url:        formUrl + "?IsPartialPostback=true" + queryString,
            type:       "post"
        };

        $(form).ajaxSubmit(options);
        return false;
    } catch (ex) {
        _showMessage("ERROR", ex.message);
        return false;
    }
}

function _partialPostbackEl(el, callback, errorCallback, queryString) {
    var form = $(el).closest("form").get(0);
    _partialPostback(form, callback, errorCallback, queryString);
}

function _partialPostbackResponse(responseText, statusText, xhr, $form) {
    _hideAddressBar();
    var form = $form.get(0);
    var container = document.getElementById(form.name + "Container");

    if (!container) {
        _showMessage("ERROR", "Partial postback container '" + form.name + "Container' not found. Add a partial postback container to the view.");
        return false;
    }

    if (responseText.indexOf("EXCEPTION:") > -1) {
        var errorMessage = responseText;
        var exceptionStart = errorMessage.indexOf("EXCEPTION:") + 10;
        var contentStart = errorMessage.indexOf(";");

        if (contentStart > -1) {
            errorMessage = errorMessage.substring(exceptionStart, contentStart);
        } else {
            errorMessage = errorMessage.substring(exceptionStart);
        }

        _hideLoader();
        _showMessage("ERROR", errorMessage);
        if (_partialPostbackErrorCallback != null) {
            _partialPostbackErrorCallback();
            _partialPostbackErrorCallback = null;
        }
        return false;
    }
    
    if (responseText.indexOf("RELOAD:") > -1) {
        window.location.reload();
        return false;
    }

    if (responseText.indexOf("REDIRECT:") > -1) {
        var redirectHref = responseText;
        var redirectStart = redirectHref.indexOf("REDIRECT:") + 9;
        var contentStart = redirectHref.indexOf(";");

        if (contentStart > -1) {
            redirectHref = redirectHref.substring(redirectStart, contentStart);
        } else {
            redirectHref = redirectHref.substring(redirectStart);
        }

        _hideLoader();
        window.location.href = redirectHref;
        return false;
    }

    if (responseText.indexOf("MODELVALIDATIONSTATE:") > -1) {
        var modelValidation = responseText;
        var validationStart = modelValidation.indexOf("MODELVALIDATIONSTATE:") + 21;
        var contentStart = modelValidation.indexOf(";");

        if (contentStart > -1) {
            modelValidation = modelValidation.substring(validationStart, contentStart);
        } else {
            modelValidation = modelValidation.substring(validationStart);
        }

        // DEPREACTED:
        //// Since we're appending the "same" object on each response
        //// we also need to remove it
        //var validationNameStart = modelValidation.indexOf("var ") + 4;
        //var validationNameEnd = modelValidation.indexOf("=", validationNameStart);
        //var validationName = modelValidation.substring(validationNameStart, validationNameEnd);

        //var validationEl = document.getElementById(validationName);
        //if (validationEl) {
        //    validationEl.parentNode.removeChild(validationEl);
        //}

        $(container).append(modelValidation);
        _hideLoader();
        if (_partialPostbackErrorCallback != null) {
            _partialPostbackErrorCallback();
            _partialPostbackErrorCallback = null;
        }
        return false;
    }

    container.innerHTML = "";
    $(container).append(responseText);
    _hideLoader();
    _partialPostbackReady();
    if (_partialPostbackCallback != null) {
        _partialPostbackCallback();
        _partialPostbackCallback = null;
    }
    _partialPostbackErrorCallback = null;
}

/* show dynamic loader */
function _showLoader(targetElement) {
    var loader = document.getElementById("dataLoader");
    if (!loader) {
        loader = document.createElement("div");
        loader.className = "dataLoader";
        loader.id = "dataLoader";
        loader.innerHTML = "Loading...";
        document.body.appendChild(loader);
    }

    if (targetElement) {
        var left = (window.innerWidth / 2) - ($(loader).width() / 2);
        var top = (window.innerHeight / 2) - ($(loader).height() / 2);
        top = top + $(window).scrollTop();
        left = left + $(window).scrollLeft();

        $(loader).css("top", top);
        $(loader).css("left", left);
    }
    loader.style.display = "";
}

function _partialPostbackReady() {
    datalab.dlResizePanelManagerInstance.Update();
    datalab.dlChartManagerInstance.Ready();
    $(window).trigger("partialPostback", [null]);
}

/* hide dynamic loader */
function _hideLoader() {
    var loader = document.getElementById("dataLoader");
    if (loader) {
        loader.style.display = "none";
    }
}

/* dlMessageBox shortcut */
function _showMessage(type, message, callback, callbackArgs) {
    setTimeout(function () {
        datalab.dlMessageBoxInstance.Show(type, null, message, callback, callbackArgs);
    }, datalab.dlMobileInstance.GetKeyboardDelay());
}

/* Misc */
function _hideAddressBar() {
    // DEPRECATED: Creates a fixed scroll offset on top of the window
    //if (document.documentElement.scrollHeight < window.outerHeight / window.devicePixelRatio)
    //    document.documentElement.style.height = (window.outerHeight / window.devicePixelRatio) + 'px';
    setTimeout(window.scrollTo(1, 1), 0);
}

var _disableScrollAdBarId = null
var _disableScrollCount = 0;
function _disableScroll(excludeElement) {
    $(document.body).css("overflow", "hidden");
    $(document).css("overflow", "hidden");
    $(document.documentElement).css("overflow", "hidden");
    $(window).on('resize', _disableScrollResize);

    _disableScrollCount++;
    if (_disableScrollAdBarId) {
        clearInterval(_disableScrollAdBarId);
    }
    _disableScrollAdBarId = setInterval(function () {
        _hideAddressBar();
    }, 50);
}

function _disableScrollResize(event) {
    _hideAddressBar();
}

function _enableScroll() {
    _disableScrollCount--;

    if (_disableScrollCount > 0) {
        return;
    }

    $(document.body).css("overflow", "");
    $(document).css("overflow", "");
    $(document.documentElement).css("overflow", "");
    $(window).off('resize', _disableScrollResize);
    if (_disableScrollAdBarId) {
        clearInterval(_disableScrollAdBarId);
    }
}

function _hideKeyboard() {
    if (document.activeElement) {
        document.activeElement.blur();
    }
}

/* Tablet debug */
function _debug(message) {
    var dbgDiv = document.getElementById("dlDebug");
    if (!dbgDiv) {
        dbgDiv = document.createElement("DIV");
        dbgDiv.id = "dlDebug";
        dbgDiv.style.position = "absolute";
        dbgDiv.style.top = "0px";
        dbgDiv.style.left = "0px";
        dbgDiv.style.width = "100px";
        dbgDiv.style.height = "400px";
        dbgDiv.style.zIndex = 10000;
        dbgDiv.style.backgroundColor = "#000";
        dbgDiv.style.color = "#FFF";
        document.body.appendChild(dbgDiv);
    }

    var lines = 0;
    if (dbgDiv.hasAttribute("lines")) {
        lines = parseInt(dbgDiv.getAttribute("lines"));
    }

    if (lines > 20) {
        dbgDiv.innerHTML = "";
        lines = 0;
    }
    dbgDiv.innerHTML += message + "<br>";
    dbgDiv.setAttribute("lines", ++lines);
}

/* DataLab namespace */
datalab = (function () {

    var dlMobile = (function () {

        // static
        var _MobileBrowsers = (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent));
        var _addressBarHeight = 50;
        var _keyboardDelay = 300;
        var _resizeDelay = 100;

        // .ctor
        var dlMobile = function () {
        };

        // methods
        dlMobile.prototype = {
            IsMobile: function () {
                return _MobileBrowsers;
            },

            GetAddressBarHeight: function () {
                return _addressBarHeight;
            },

            GetKeyboardDelay: function () {
                return _keyboardDelay;
            },

            GetResizeDelay: function () {
                return _resizeDelay;
            }
        };

        return dlMobile;

    })();

    var dlMobileInstance = new dlMobile();

    /* Message box */
    var dlMessageBox = (function () {

        // static
        var maxWidth = 500;
        var maxHeight = 400;
        var overlay = null;
        var control = null;
        var content = null;
        var header = null;
        var buttonOk = null;
        var buttonNo = null;
        var buttonYes = null;

        // .ctor
        var dlMessageBox = function () {

        };

        // methods
        dlMessageBox.prototype = {
            Ready: function() {
                overlay = document.createElement("div");
                overlay.className = "MessageBoxOverlay";
                overlay.style.display = "none";

                control = document.createElement("div");
                control.className = "MessageBox";
                overlay.appendChild(control);

                header = document.createElement("h2");
                header.className = "MessageBoxHeader";
                control.appendChild(header);

                content = document.createElement("textarea");
                content.className = "MessageBoxMessage";
                control.appendChild(content);

                buttonOk = document.createElement("input");
                buttonOk.className = "MessageBoxOk";
                buttonOk.type = "button";
                buttonOk.value = Loc.Trans.OK;
                control.appendChild(buttonOk);

                buttonNo = document.createElement("input");
                buttonNo.className = "MessageBoxNo";
                buttonNo.type = "button";
                buttonNo.value = Loc.Trans.No;
                control.appendChild(buttonNo);

                buttonYes = document.createElement("input");
                buttonYes.className = "MessageBoxYes";
                buttonYes.type = "button";
                buttonYes.value = Loc.Trans.Yes;
                control.appendChild(buttonYes);

                document.body.appendChild(overlay);
            },

            Show: function (type, title, message, callback, callbackArgs) {
                header.style.display = "";
                control.className = "MessageBox";

                if (type == "ERROR") {
                    header.innerHTML = Loc.Trans.Error;
                    control.className = "MessageBox MessageBoxError";
                }
                else if (type == "INFO") {
                    header.innerHTML = Loc.Trans.Info;
                    control.className = "MessageBox MessageBoxInfo";
                }
                else if (type == "CONFIRM") {
                    header.innerHTML = Loc.Trans.Confirm;
                    control.className = "MessageBox MessageBoxConfirm";
                }
                else {
                    header.style.display = "none";
                }

                if (title) {
                    header.innerHTML = title;
                }

                $(buttonOk).on("click", { messageBox: this, action: "OK", cb: callback, cba: callbackArgs }, function (e) {
                    e.data.messageBox.Close(e.data.action, e.data.cb, e.data.cba);
                });
                $(buttonYes).on("click", { messageBox: this, action: "Yes", cb: callback, cba: callbackArgs }, function (e) {
                    e.data.messageBox.Close(e.data.action, e.data.cb, e.data.cba);
                });
                $(buttonNo).on("click", { messageBox: this, action: "No", cb: callback, cba: callbackArgs }, function (e) {
                    e.data.messageBox.Close(e.data.action, e.data.cb, e.data.cba);
                });

                content.value = message;
                overlay.style.display = "";
                this.Update();
                return;
            },

            Close: function (action, callback, callbackArgs) {
                $(buttonOk).off("click");
                $(buttonYes).off("click");
                $(buttonNo).off("click");
                overlay.style.display = "none";

                if (callback) {
                    callback(action, callbackArgs);
                }
            },

            Update: function () {
                var width = parseInt($(control).width());
                var height = parseInt($(control).height());
                var top = Math.round((window.innerHeight - height) / 2 + (height / 2));
                var left = Math.round((window.innerWidth - width) / 2 + (width / 2));

                $(control).css("top", top + "px");
                $(control).css("left", left + "px");
            }
        };

        return dlMessageBox;

    })();
    
    var dlMessageBoxInstance = new dlMessageBox();

    /* ContentMenu class */
    var dlContentMenu = (function () {

        // static
        var _minWidth = 1200;
        var _contentMenu = null;
        var _contentMenuItems = null;

        // .ctor
        var dlContentMenu = function () {

        };

        // methods
        dlContentMenu.prototype = {
            Ready: function () {
                _contentMenu = $("#MenuLeft");
                _contentMenuItems = $("#ContentMenu").find("span");
                this.Update();
            },

            Update: function () {
                if (window.innerWidth < _minWidth) {
                    _contentMenu.css("width", "90px");
                    $(_contentMenuItems).each(function (index) {
                        $(this).css("display", "none");
                    });
                } else {
                    _contentMenu.css("width", "");
                    $(_contentMenuItems).each(function (index) {
                        $(this).css("display", "");
                    });
                }
            }
        };

        return dlContentMenu;

    })();

    var dlContentMenuInstance = new dlContentMenu();

    /* ChartManager class */
    var dlChartManager = (function () {

        // static
        var _charts = new Array();

        // .ctor
        var dlChartManager = function () {
        };

        // methods
        dlChartManager.prototype = {
            Ready: function () {
                for (var i = 0; i < _charts.length; i++) {
                    _charts[i].Create();
                }
            },

            Add: function (chart) {
                for (var i = 0; i < _charts.length; i++) {
                    if (_charts[i].name == chart.name) {
                        if (_charts[i].chart) {
                            _charts[i].chart.clear();
                            _charts[i].chart.destroy();
                        }
                        _charts[i] = chart;
                        return;
                    }
                }
                _charts.push(chart);
            },

            _isInResizePanel: function(name) {
                for (var i = 0; i < _charts.length; i++) {
                    if (_charts[i].name == name) {
                        return _charts[i]._isInResizePanel();
                    }
                }
            },

            Resize: function () {
                for (var i = 0; i < _charts.length; i++) {
                    _charts[i].Resize();
                }
            },

            Clear: function () {
                _charts = new Array();
            }
        };

        return dlChartManager;

    })();

    var dlChartManagerInstance = new dlChartManager();

    /* ChartHelper class */
    var dlChart = (function () {

        // static
        var chartOptions = {
            animation: true,
            animationSteps: 30,
            animationEasing: 'easeOutQuart',
            showScale: true,
            scaleOverride: false,
            scaleSteps: null,
            scaleStepWidth: null,
            scaleStartValue: null,
            scaleLineColor: 'rgba(0,0,0,.1)',
            scaleLineWidth: 1,
            scaleShowLabels: true,
            scaleLabel: '<%=value%>',
            scaleIntegersOnly: true,
            scaleBeginAtZero: true,
            scaleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
            scaleFontSize: 12,
            scaleFontStyle: 'normal',
            scaleFontColor: '#666',
            scaleGridLineColor: 'rgba(0,0,0,.1)',
            responsive: false,
            maintainAspectRatio: false,
            showTooltips: true,
            customTooltips: false,
            tooltipEvents: ['mousemove', 'touchstart', 'touchmove'],
            tooltipFillColor: 'rgba(0,0,0,0.8)',
            tooltipFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif'",
            tooltipFontSize: 14,
            tooltipFontStyle: 'normal',
            tooltipFontColor: '#fff',
            tooltipTitleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
            tooltipTitleFontSize: 14,
            tooltipTitleFontStyle: 'bold',
            tooltipTitleFontColor: '#fff',
            tooltipYPadding: 6,
            tooltipXPadding: 6,
            tooltipCaretSize: 8,
            tooltipCornerRadius: 0,
            tooltipXOffset: 10,
            tooltipTemplate: '<%if (label){%><%=label%>: <%}%><%=value.format(2)%>',
            multiTooltipTemplate: '<%= value %>',
            onAnimationProgress: function(){},
            onAnimationComplete: function(){},
            segmentShowStroke : true,
            segmentStrokeColor : '#fff',
            segmentStrokeWidth : 2,
            percentageInnerCutout : 0,
            animateRotate : true,
            animateScale : false,
            legendTemplate : '',
            bezierCurve : false,
            bezierCurveTension : 0.4,
            pointDot : false,
            pointDotRadius : 3,
            pointDotStrokeWidth : 0,
            pointHitDetectionRadius : 3,
            barShowStroke: false,
            scaleLabelXEveryNth: 1,
            scaleShortValueType: 2
        };

        // .ctor
        var dlChart = function (name, type, data, clientData, options, clientClick) {
            this.name = name;
            this.type = type;
            this.context;
            this.chart;
            this.container;
            this.canvas;
            this.legend;
            this.clientData = clientData;
            this.data = data;
            this.selItem = null;
            this.options = options;
            this.clientClick = clientClick;
            this.isInResizePanel = false;
            this.isInit = true;
            datalab.dlChartManagerInstance.Add(this);
        };

        // methods
        dlChart.prototype = {
            GetId: function () {
                return this.name;
            },

            _isInResizePanel: function() {
                var canvas = $("#chartContainer" + this.name).get(0);
                var parent = canvas;
                var maxRecursion = 10;

                while (parent.parentNode) {
                    if ($(parent).attr("resizepanel") == "true") {
                        return true;
                        break;
                    }
                    if (maxRecursion == 0) {
                        break;
                    }
                    parent = parent.parentNode;
                    maxRecursion--;
                }

                return false;
            },

            GetIdFromClick: function (event) {
                if (this.type == "Bar") activeBars = this.chart.getBarsAtEvent(event);
                if (this.type == "Pie") activeBars = this.chart.getSegmentsAtEvent(event);
                if (this.type == "Line") activeBars = this.chart.getPointsAtEvent(event);
                if (!activeBars || activeBars.length == 0) return;
                var value = activeBars[0].label + activeBars[0].value;

                for (var key in this.clientData) {
                    if (value == key)
                        return this.clientData[key];
                };

                return null;
            },

            Create: function () {
                if (!$("#chart" + this.name).length)
                    return;

                // Wait for resize panels to finish resizing
                setTimeout(this.Create_Internal, 200, this);
            },
            
            Create_Internal: function (chart) {
                var cOptions = jQuery.extend(true, {}, chartOptions);
                if (chart.options != "") {
                    eval(chart.options);
                }

                chart.context = $("#chart" + chart.name).get(0).getContext("2d");
                chart.container = $("#chartContainer" + chart.name);
                chart.canvas = $("#chart" + chart.name);
                chart.legend = $("#chartLegend" + chart.name);
                chart.isInResizePanel = chart._isInResizePanel();

                var cWidth = $(chart.container).width();
                var cHeight = $(chart.container).height();
                $(chart.canvas).get(0).width = cWidth;
                $(chart.canvas).get(0).height = cHeight;

                if (chart.type == "Bar") {
                    chart.chart = new Chart(chart.context).Bar(chart.data, cOptions);
                }
                if (chart.type == "Line") {
                    chart.chart = new Chart(chart.context).Line(chart.data, cOptions);
                }
                if (chart.type == "Pie") {
                    chart.chart = new Chart(chart.context).Pie(chart.data, cOptions);
                }

                if (chart.clientClick != "") {
                    $(chart.canvas).click({ chart: chart }, function (event) {
                        var v = event.data.chart.GetIdFromClick(event);
                        if (v != null && (v == event.data.chart.selItem || !datalab.dlMobileInstance.IsMobile())) {
                            eval(event.data.chart.clientClick + "('" + v + "')");
                        }
                        event.data.chart.selItem = v;
                    });
                }

                if (chart.isInResizePanel) {
                    $(window).on("resizePanelComplete", { chart: chart }, function (event) {
                        event.data.chart.Resize();
                    });
                } else {
                    $(window).on("resizeComplete", { chart: chart }, function (event) {
                        event.data.chart.Resize();
                    });
                }
            },

            Resize: function () {
                if (this.isInit) {
                    this.isInit = false;
                    return;
                }

                // Wait for resize panels to finish resizing
                setTimeout(function (chart, canvas, container) {
                    try {
                        chart.chart.stop();
                        chart.chart.clear();
                        chart.chart.resize(chart.chart.render, true, $(container).width(), $(container).height());
                    } catch(ex) { ; }

                }, 200, this, this.canvas, this.container);
            }
        };

        return dlChart;

    })();

    /* ResizePanelManagerHelper class */
    var dlResizePanelManager = (function () {

        // static
        var _resizePanels = new Array();
        var _updateCount = 0;

        // .ctor
        var dlResizePanelManager = function () {
        };

        // methods
        dlResizePanelManager.prototype = {
            Add: function (p) {
                for (var i = 0; i < _resizePanels.length; i++) {
                    if (_resizePanels[i].GetId() == p.GetId()) {
                        _resizePanels[i] = p;
                        _updateCount++;

                        if (_updateCount == _resizePanels.length) {
                            _updateCount = 0;
                        }
                        return;
                    }
                }
                _resizePanels.push(p);
            },

            Update: function (forceUpdate, internalCall) {
                for (var i = 0; i < _resizePanels.length; i++) {
                    _resizePanels[i].Update(forceUpdate);
                }

                // A double pass fixed some display rotation issues
                if (!internalCall) {
                    datalab.dlResizePanelManagerInstance.Update(true, true);
                }

                _resizePanelComplete();
            },

            UpdateFromParent: function (parentElement, forceUpdate, internalCall) {
                for (var i = 0; i < _resizePanels.length; i++) {
                    var p = _resizePanels[i];
                    var pid = p.GetId();
                    var pel = document.getElementById(pid);
                    if (!pel) return;
                    var parent = pel.parentNode;

                    while (parent) {
                        if (parent == parentElement) {
                            p.Update(forceUpdate);
                            break;
                        }
                        parent = parent.parentNode;
                    }
                }

                _resizePanelComplete();
            }
        };

        return dlResizePanelManager;

    })();

    var dlResizePanelManagerInstance = new dlResizePanelManager();

    /* ResizePanelHelper class */
    var dlResizePanel = (function () {

        // .ctor
        var dlResizePanel = function (settings) {
            this._settings = settings;
            this._settings.rowIndex = -1;
            this._settings.rowWidth = 0;
            this._settings.rowPercent = 0;
            this._isResized = false;
            this._settings.row = null;
            this._rows = new Array();
            dlResizePanelManagerInstance.Add(this);
        };

        // methods
        dlResizePanel.prototype = {
            GetId: function() {
                return this._settings.id;
            },

            Update: function (forceUpdate) {
                if (this._lastWindowWidth == window.innerWidth && !forceUpdate) {
                    return;
                }

                // We need to show the element before we can get the container width
                // and then hide it again, we need to toggle the parent invisible container
                var isVisible = $(this._settings.containerEl).is(":visible");
                var hiddenParent = null;
                if (!isVisible) {
                    hiddenParent = $("#" + this._settings.id).parents(":hidden")
                    var parentCount = $(hiddenParent).length;
                    hiddenParent = $(hiddenParent)[parentCount - 1];
                    $(hiddenParent).css({ "visibility": "hidden", "display": "block" });
                }

                this._lastWindowWidth = window.innerWidth;
                this._settings.containerEl = $("#" + this._settings.id);
                this.containerWidth = $(this._settings.containerEl).width();

                // Reset parents visibility, we only need to get the actual container height
                if (!isVisible && hiddenParent != null) {
                    $(hiddenParent).css({ "visibility": "", "display": "none" });
                }
                
                if (this._isResized) {
                    for (var j = 0; j < this._settings.panels.length; j++) {
                        var panel = this._settings.panels[j];

                        if (panel.oWidth) {
                            panel.width = panel.oWidth;
                            panel.rowIndex = null;
                            var panelEl = $("#" + panel.id);

                            $("#" + panel.id).css("width", panel.width + "%");
                            $(this._settings.containerEl).append(panelEl);
                        }
                    }

                    for (var j = 0; j < this._rows.length; j++) {
                        if (this._rows[j].parentNode) {
                            this._rows[j].parentNode.removeChild(this._rows[j]);
                        }
                    }
                }

                this._settings.rowIndex = -1;
                this._settings.rowWidth = 0;
                this._settings.rowPercent = 0;
                this._isResized = false;
                this._settings.row = null;
                this._rows = new Array();

                for (var j = 0; j < this._settings.panels.length; j++) {
                    var panel = this._settings.panels[j];
                    var panelEl = $("#" + panel.id);
                    panel.calcWidth = panel.minWidth;

                    if (this._settings.rowWidth + panel.calcWidth > this.containerWidth) {
                        this._isResized = true;
                        this.UpdateChild(this._settings.rowIndex, this._settings.rowPercent, j);
                        this._createRow();
                    }

                    panel.rowIndex = this._settings.rowIndex;
                    panel.rowPercent = this._settings.rowPercent;
                    this._settings.rowWidth += panel.calcWidth;
                    this._settings.rowPercent += panel.width;

                    if (this._settings.row != null) {
                        $(this._settings.row).append(panelEl);
                    }
                }

                if (this._isResized) {
                    this.UpdateChild(this._settings.rowIndex, this._settings.rowPercent);
                }
            },

            _createRow: function () {
                this._settings.rowIndex++;
                this._settings.rowWidth = 0;
                this._settings.rowPercent = 0;
                this._settings.row = document.createElement("div");
                this._settings.row.style.width = "100%";
                this._settings.row.id = "resizePanelRow" + this._settings.rowIndex;
                this._settings.row.className = this._settings.cssClass;
                $(this._settings.containerEl).append(this._settings.row);
                this._rows.push(this._settings.row);
            },

            UpdateChild: function (rowIndex, rowPercent, currIndex) {
                var recalcPanels = new Array();

                for (var i = 0; i < this._settings.panels.length; i++) {
                    var p = this._settings.panels[i];
                    if ((p.rowIndex == rowIndex)) {
                        recalcPanels.push(p);
                    }
                }

                var totalWidth = 0;
                for (var i = 0; i < recalcPanels.length; i++) {
                    var p = recalcPanels[i];

                    if (!p.oWidth) p.oWidth = p.width;
                    p.width = Math.round(p.width * (100 / rowPercent));
                    totalWidth += p.width;

                    if (i == recalcPanels.length - 1) {
                        if (totalWidth != 100) {
                            p.width += 100 - totalWidth;
                        }
                    }

                    if (p.width == 0) {
                        p.width = 100;
                    }

                    $("#" + p.id).css("width", p.width + "%");
                }
            }
        };

        return dlResizePanel;

    })();

    // Edit form
    var dlEditForm = (function () {

        var _callbackForm = null;

        // .ctor
        var dlEditForm = function (settings) {
            this.settings = settings;
            this.id = settings.id;
            this.el = $("#" + this.id);
            this.elContainer = $(this.el).children()[0];
            this.elHeader = $(this.elContainer).children()[0];
            this.elContent = $(this.elContainer).children()[1];
            this.elFooter = $(this.elContainer).children()[2];
            this.elContentInner = $(this.elContent).children()[0];
            this.elClose = $("#" + this.id + "Close");
            this.elEditAction = $("#" + this.id + "EditAction");
            this.elForm = null;
            this.formItems = new Array();
            this.currentFormItemIndex = 0;
            this.currentFormItem = null;
            this.pageOffsetTop = 0;
            this.contentHeight = null;
            this.outerHeight = null;
            this.scrollTooltip = null;
            this.Ready();

            if (!this.settings.displayInline) {
                this.SetPosition();
            }
        };

        // methods
        dlEditForm.prototype = {
            GetCallbackForm: function() {
                return _callbackForm;
            },

            SetCallbackForm: function (form) {
                _callbackForm = form;
            },

            Ready: function () {
                _hideAddressBar();
                this._FindForm();

                if (datalab.dlMobileInstance.IsMobile()) {
                    this._fixDropDownMobile();
                }

                if (this.settings.parent != "") {
                    if (this.elForm) {
                        var parent = $("#" + this.settings.parent);
                        $(parent).after(this.elForm);
                    } else {
                        var parent = $("#" + this.settings.parent);
                        $(parent).after(this.el);
                    }
                }

                if (!this.settings.disableScreen) {
                    var parent = $(this.el).get(0).parentNode;
                    parent.appendChild($(this.elContainer).get(0));
                    parent.removeChild($(this.el).get(0));
                    this.pageOffsetTop = $(this.elContainer).position().top;
                } else {
                    _disableScroll($(this.elContent).get(0));
                    datalab.dlEditGridManagerInstance.DisableAutoSize();
                }
                
                if (!this.settings.displayInline) {
                    $(window).on("resizeComplete", { form: this }, function (event) {
                        event.data.form.SetPosition(true);
                        // DEPRECATED: we're waiting on resize complete event
                        // Add keyboard show / hide delay
                        //setTimeout(function (form) {
                        //    form.SetPosition(true);
                        //}, datalab.dlMobileInstance.GetKeyboardDelay(), event.data.form);
                    });
                }

                // DEPRECATED: Not working very well when nesting resize panels, plan for future releases
                // Check if form contains resize panels, then we wait for all resize panels to resize
                // before we position and recalculate window height
                /*var resizePanels = $(this.elContainer).find("[resizepanel]");
                if (resizePanels.length > 0) {
                    $(window).on("resizePanelComplete", { form: this }, function (event) {
                        // Add keyboard show / hide delay
                        setTimeout(function (form) {
                            form.SetPosition(true);
                        }, datalab.dlMobileInstance.GetKeyboardDelay(), event.data.form);
                    });
                }*/

                $(this.elClose).on("click", { panel: this }, function (e) {
                    e.data.panel.Close();
                });

                this.currentFormItemIndex = 0;
                this.currentFormItem = null;
                this._FindFormInputs();

                // Use the "GO" button to change focus (TAB) to next input control on mobile devices
                if (this.elForm) {
                    $(this.elForm).keydown({ form: this }, function (event) {
                        if (event.which != 13) {
                            return;
                        }
                        event.data.form._FocusNextFormItem();
                        return false;
                    });

                    $(this.elForm).submit(function (event) {
                        event.preventDefault();
                        return false;
                    });
                }
            },

            _FindForm: function() {
                var parent = $(this.elContainer).get(0);
                while (parent != null) {
                    if (parent.tagName.toLowerCase() == "form") {
                        this.elForm = parent;
                        return;
                    }
                    parent = parent.parentNode;
                }
            },

            _FindFormInputs: function () {
                if (!this.elForm) {
                    return;
                }

                var _self = this;
                $(this.elForm).find("[tabindex]").each(function (index, formItem) {
                    if ($(formItem).attr("tabindex") != "-1") {
                        _self.formItems.push(formItem);
                    }
                });

                // Sort inputs by tab index
                _self.formItems.sort(function compare(a, b) {
                    var taba = parseInt($(a).attr("tabindex"));
                    var tabb = parseInt($(b).attr("tabindex"));

                    if (taba < tabb) return -1;
                    if (taba > tabb) return 1;
                    return 0;
                });
            },

            _FocusNextFormItem: function () {
                // Get currently focused input element
                this.currentFormItem == null;
                if (document.activeElement) {
                    for (var i = 0; i < this.formItems.length; i++) {
                        if (document.activeElement == $(this.formItems[i]).get(0)) {
                            this.currentFormItemIndex = i;
                            this.currentFormItem = this.formItems[i];
                        }
                    }
                }

                // Set focus to default input
                if (this.currentFormItem == null) {
                    if (this.formItems.length > 0) {
                        this.currentFormItemIndex = 0;
                        this.currentFormItem = this.formItems[0];
                    }
                }

                // Get next input element 
                else {
                    this.currentFormItemIndex++;
                    if (this.currentFormItemIndex > this.formItems.length - 1) {
                        if (!this.settings.autoSubmit) {
                            var result = _showMessage("CONFIRM", Loc.Trans.ConfirmSaveChangesMessage, this._MessgeBoxSubmitCallback, this);
                        } else {
                            this._MessgeBoxSubmitCallback("Yes", this);
                        }
                        return;
                    }
                    this.currentFormItem = this.formItems[this.currentFormItemIndex];
                }

                if (this.currentFormItem) {
                    $(this.currentFormItem).focus();
                    this._ScrollIntoView();
                }
            },

            _MessgeBoxSubmitCallback: function (action, form) {
                if (action == "Yes") {
                    form.Save();
                }
            },

            SetPosition: function (resize) {
                var containerMargin = parseInt($(this.elContainer).css("marginTop")) * 2;
                var containerPadding = parseInt($(this.elContainer).css("paddingTop")) * 2;
                var containerBorderRad = parseInt($(this.elContainer).css("border-radius")) * 2;
                var contentPadding = parseInt($(this.elContent).css("paddingTop")) * 2;
                var contentBorder = parseInt($(this.elContent).css("border-width")) * 2;
                var innerHeight = parseInt($(this.elContentInner).outerHeight());
                var headerHeight = parseInt($(this.elHeader).outerHeight());
                var footerHeight = parseInt($(this.elFooter).outerHeight());
                var verticalScroll = 0;

                // Firefox doesn't support border radius anymore, so we'll ignore those few pixels
                if (!containerBorderRad) {
                    containerBorderRad = 0;
                }
                if (!contentBorder) {
                    contentBorder = 0;
                }

                var windowHeight = window.innerHeight;
                var outerHeight = 0;
                var contentHeight = 0;
                var top = 0;
                var left = 0;

                // This prevents out-resize when we have tabs inside the edit form
                if (this.contentHeight != null) {
                    contentHeight = this.contentHeight;
                    outerHeight = this.outerHeight;
                } else {
                    outerHeight = headerHeight + innerHeight + footerHeight + containerPadding + contentPadding + contentBorder + containerBorderRad;
                    contentHeight = innerHeight + containerBorderRad;
                }

                var totalHeight = outerHeight + containerMargin;
                var minWidth = (window.innerWidth - containerMargin - containerPadding);
                var width = window.innerWidth - containerMargin;

                if (width > this.settings.maxWidth) {
                    width = this.settings.maxWidth;
                }

                if (minWidth < width) {
                    width = window.innerWidth - containerMargin - containerPadding;
                } else {
                    left = (window.innerWidth - width) / 2 - (containerMargin / 2 + containerPadding / 2);
                }

                if (contentHeight < this.settings.minHeight) {
                    var heightOffset = this.settings.minHeight - contentHeight;
                    contentHeight = this.settings.minHeight;
                    outerHeight += heightOffset;
                }

                if (this.contentHeight == null) {
                    this.contentHeight = contentHeight;
                    this.outerHeight = outerHeight;
                }

                if (windowHeight < totalHeight) {
                    contentHeight -= totalHeight - windowHeight;
                    verticalScroll = totalHeight - windowHeight;
                } else {
                    top = (windowHeight - outerHeight - containerMargin) / 2;
                }

                top -= this.pageOffsetTop;

                if (width < this.settings.minWidth) {
                    width = this.settings.minWidth;
                }

                $(this.el).height(windowHeight);
                if (this.settings.animate) {
                    $(this.elContainer).animate({ top: top, left: left, width: width }, 200, "swing");
                    $(this.elContent).animate({ height: contentHeight }, 200, "swing", (function (resize, form) {
                        return function () {
                            if (resize) {
                                datalab.dlResizePanelManagerInstance.UpdateFromParent($(form.elContainer).get(0), true);
                                form._ScrollIntoView();
                            }

                            if (verticalScroll > 20 && this.scrollTooltip == null && datalab.dlMobileInstance.IsMobile()) {
                                var dlTip = new dlTooltip();
                                dlTip.Show(this.elContent, "", { x: "center", y: "middle" }, "TooltipScroll", 1500, this.elContainer);
                                this.scrollTooltip = true;
                            }
                        }
                    })(resize, this));
                } else {
                    $(this.elContainer).css({ top: top, left: left, width: width });
                    $(this.elContent).height(contentHeight);

                    if (resize) {
                        datalab.dlResizePanelManagerInstance.UpdateFromParent($(this.elContainer).get(0), true);
                        this._ScrollIntoView();
                    }

                    if (verticalScroll > 20 && this.scrollTooltip == null && datalab.dlMobileInstance.IsMobile()) {
                        var dlTip = new dlTooltip();
                        dlTip.Show(this.elContent, "", { x: "center", y: "middle" }, "TooltipScroll", 1500, this.elContainer);
                        this.scrollTooltip = true;
                    }
                }

                // We need to scroll back to top when orientation changes
                if (datalab.dlMobileInstance.IsMobile()) {
                    $(document).scrollTop(0);
                }
            },

            // Scroll focused form element into view port, this is usefull when the soft keyboard shows or hides
            _ScrollIntoView: function (element) {
                if (!element) {
                    element = document.activeElement;
                }

                if (element) {
                    var scrollTo = element;
                    var scrollOffset = $(scrollTo).offset().top - $(this.elContent).offset().top + $(this.elContent).scrollTop();
                    scrollOffset -= 5;
                    $(this.elContent).animate({ scrollTop: scrollOffset }, 200);
                }
            },

            Close: function () {
                _enableScroll();
                var el = $(this.el).get(0);

                if (el && el.parentNode) {
                    el.parentNode.removeChild(el);
                }

                if (this.elForm) {
                    $(this.elForm).unbind("keydown");
                }

                datalab.dlEditGridManagerInstance.EnableAutoSize();
            },

            Cancel: function () {
                this.Close();
                return false;
            },

            Save: function () {
                _enableScroll();
                if (this.elForm == null) {
                    return true;
                }

                var baseAction = baseAction = this.elForm.action;
                if (this.settings.saveController != "" && this.settings.saveAction != "") {
                    this.elForm.action = "/" + this.settings.saveController + "/" + this.settings.saveAction;
                }

                $(this.elEditAction).val("1");
                if ($(this.elForm).attr("partialpostback") == "true") {
                    this.SetCallbackForm(this);
                    if (this.settings.clientCallback) {
                        var f = this;
                        _partialPostback(this.elForm, this.settings.clientCallback, function () { f._OnServerError(f) });
                    } else {
                        _partialPostback(this.elForm, null, this._OnServerError);
                    }
                } else {
                    this.elForm.submit();
                    if (this.settings.clientCallback) {
                        this.settings.clientCallback();
                    }
                }

                this.elForm.action = baseAction;
                if (this.elForm) {
                    $(this.elForm).unbind("keydown");
                }

                datalab.dlEditGridManagerInstance.EnableAutoSize();
                return false;
            },

            Delete: function () {
                _enableScroll();
                if (this.elForm == null) {
                    return true;
                }

                var baseAction = baseAction = this.elForm.action;
                if (this.settings.deleteController != "" && this.settings.deleteAction != "") {
                    this.elForm.action = "/" + this.settings.deleteController + "/" + this.settings.deleteAction;
                }
                this.formBaseAction = baseAction;

                if (this.settings.deleteMessageShow) {
                    var message = this.settings.deleteMessage;
                    if (!message || message == "") {
                        message = Loc.Trans.ConfirmDeleteItemMessage;
                    }
                    _showMessage("CONFIRM", message, this._DeleteCallback, this)
                } else {
                    this._DeleteCallback("Yes", this);
                }

                datalab.dlEditGridManagerInstance.EnableAutoSize();
                return false;
            },

            _DeleteCallback: function (action, form) {
                if (action != "Yes") {
                    return;
                }

                $(form.elEditAction).val("2");
                if ($(form.elForm).attr("partialpostback") == "true") {
                    form.SetCallbackForm(form);
                    if (form.settings.clientCallback) {
                        _partialPostback(form.elForm, form.settings.clientCallback, function () { form._OnServerError(form) });
                    } else {
                        _partialPostback(form.elForm, null, form._OnServerError);
                    }
                } else {
                    form.elForm.submit();
                    if (form.settings.clientCallback) {
                        form.settings.clientCallback();
                    }
                }

                form.elForm.action = form.formBaseAction;
                if (form.elForm) {
                    $(form.elForm).unbind("keydown");
                }
            },

            _OnServerError: function (form) {
                if (!form) return;

                var callbackForm = form.GetCallbackForm();
                if (callbackForm) {
                    callbackForm.Ready();
                }
            },
            
            // On android browsers css positon: fixed does not work
            // so on drop down we move the dropdown in the parent container to overcome overflow: hidden
            // of the scroll container
            _fixDropDownMobile: function () {
                var dropDownElements = $(this.elContent).find("div .dxpcDropDown_Metropolis");
                $(dropDownElements).each(function (key, value) {
                    var id = $(value).attr("id");
                    var i = id.indexOf("_DDD");
                    var scriptName = id.substring(0, i);

                    setTimeout(function (scriptName) {
                        var script = window[scriptName];
                        script.DropDown.AddHandler(datalab.dlEditForm.prototype._fixDropDownMobileDropDown);
                        script.CloseUp.AddHandler(datalab.dlEditForm.prototype._fixDropDownMobileCloseUp);

                        // Restore position on orientation change
                        $(window).on("resizeComplete", { s: script, scriptName: scriptName, id: id }, function (event) {
                            setTimeout(function (s, scriptName, el) {
                                var el = $("#" + id);
                                var input = $("#" + scriptName);
                                var inputPos = $(input).offset();
                                var inputWidth = $(input).width();
                                var inputHeight = $(input).outerHeight();

                                if (!el || !inputPos) return;
                                var childEl = $(el).find("#" + scriptName + "_DDD_L_D");

                                $(childEl).css({ width: inputWidth });
                                $(el).css({ left: inputPos.left, top: inputPos.top + inputHeight, width: inputWidth });
                            }, datalab.dlMobileInstance.GetKeyboardDelay(), event.data.s, event.data.scriptName, event.data.el)
                        });
                    }, 0, scriptName, id);
                });
            },

            _fixDropDownMobileDropDown: function (s, e) {
                var elContainer = document.getElementById('efTaxRateContainer');
                var el = document.getElementById(s.name + '_DDD_PW-1');
                if (!el) return;
                s.originalParent = el.parentNode;
                $(el).insertBefore($(elContainer));
            },

            _fixDropDownMobileCloseUp: function (s, e) {
                var el = document.getElementById(s.name + '_DDD_PW-1');
                if (!el) return;
                $(s.originalParent).append($(el));
            }
        };

        return dlEditForm;

    })();

    // Edit grid manager
    var dlEditGridManager = (function () {

        var _autoSizeEnabled = true;

        // .ctor
        var dlEditGridManager = function () {
            this._grids = new Array();
        };

        dlEditGridManager.prototype = {
            Add: function (grid) {
                this._grids.push(grid);
            },

            Ready: function () {
                for (var i = 0; i < this._grids.length; i++) {
                    this._grids[i].Ready();
                }
            },

            Get: function (id) {
                for (var i = 0; i < this._grids.length; i++) {
                    if (this._grids[i].settings.id == id)
                        return this._grids[i];
                }
                return null;
            },

            DisableAutoSize: function () {
                _autoSizeEnabled = false;
            },

            EnableAutoSize: function () {
                _autoSizeEnabled = true;
            },

            CanAutoSize: function () {
                return _autoSizeEnabled;
            }
        };
        
        return dlEditGridManager;

    })();

    var dlEditGridManagerInstance = new dlEditGridManager();

    // Edit grid
    var dlEditGrid = (function () {

        // .ctor
        var dlEditGrid = function (settings) {
            this.settings = settings;
            datalab.dlEditGridManagerInstance.Add(this);
        };

        // methods
        dlEditGrid.prototype = {
            Ready: function () {
                if (this.settings.autoSize) {
                    this.AutoSize();
                    $(window).on('resize', { grid: this }, function (event) {
                        if (!datalab.dlEditGridManagerInstance.CanAutoSize()) {
                            return;
                        }

                        var grid = event.data.grid;
                        setTimeout(function () {
                            grid.AutoSize();
                        }, datalab.dlMobileInstance.GetResizeDelay());
                    });
                }

                // Adjust date filter display based on user's screen size
                if (this.settings.dateFilter) {
                    $(window).on("resizeComplete", { grid: this }, function (event) {
                        event.data.grid._AdjustDisplay();
                    });
                    $(window).on("partialPostback", { grid: this }, function (event) {
                        event.data.grid._AdjustDisplay();
                    });
                    this._AdjustDisplay();
                }
            },

            _AdjustDisplay: function () {
                var _minWidth = 800;
                if (window.innerWidth < _minWidth) {
                    if (this.settings.dateFilter.enableBack && this.settings.dateFilter.enableDateFrom) {
                        $("#" + this.settings.id + "DateBack").css("display", "none");
                    }
                    if (this.settings.dateFilter.enableNext && this.settings.dateFilter.enableDateTo) {
                        $("#" + this.settings.id + "DateNext").css("display", "none");
                    }
                } else {
                    if (this.settings.dateFilter.enableBack && this.settings.dateFilter.enableDateFrom) {
                        $("#" + this.settings.id + "DateBack").css("display", "");
                    }
                    if (this.settings.dateFilter.enableNext && this.settings.dateFilter.enableDateTo) {
                        $("#" + this.settings.id + "DateNext").css("display", "");
                    }
                }
            },

            AutoSize: function() {
                // Calculate aproximetly how much space is available to display data rows
                this.el = $("#" + this.settings.id);
                this.availableSpace = window.innerHeight - $(this.el).offset().top;

                var topPager = $(this.el).find(".dxgvPagerTopPanel_Metropolis");
                var botPager = $(this.el).find(".dxgvPagerBottomPanel_Metropolis");
                var gridHeader = $("#" + this.settings.id + "_DXHeadersRow0");

                var innerHeightOffset = 0;
                if ($(topPager).length) {
                    innerHeightOffset += $(topPager).outerHeight(true);
                }
                // DEPRECATED: It's enougn if only top pager is visible
                /*if ($(botPager).length) {
                    innerHeightOffset += $(botPager).height();
                }*/
                if ($(gridHeader).length) {
                    innerHeightOffset += $(gridHeader).outerHeight(true);
                }

                this.availableSpace -= innerHeightOffset;

                // Try to get the height of a single row, or fallback to aproximate default
                var rowHeight = 45;
                var firstRow = $("#" + this.settings.id + "_DXDataRow0");
                if ($(firstRow).length) {
                    rowHeight = firstRow.height();
                }

                // Calculate how many rows we can display without scrolling the client window
                var pageSize = Math.floor(this.availableSpace / rowHeight);
                if (this.settings.pageSize != pageSize) {
                    this.settings.pageSize = pageSize;
                    this.ChangePageSize();
                }
            },

            _OnBeginCallback: function (sender, e) {
                if (this._ChangePageSize) {
                    $("#" + "eg" + this.settings.id + "PageSize").val(this.settings.pageSize);
                    e.customArgs["eg" + this.settings.id + "PageSize"] = this.settings.pageSize;
                }
                this._ChangePageSize = false;
            },

            _OnSort: function (sender, e) {
                var sortIndex = e.column.index;
                var sortDir = 0;

                if ($("#" + "eg" + this.settings.id + "SortDir").val() == "0") {
                    sortDir = 1;
                }
                if ($("#" + "eg" + this.settings.id + "SortIndex").val() != "" + sortIndex) {
                    sortDir = 0;
                }

                $("#" + "eg" + this.settings.id + "SortIndex").val(sortIndex);
                $("#" + "eg" + this.settings.id + "SortDir").val(sortDir);
            },

            ChangePageSize: function() {
                this._ChangePageSize = true;
                window[this.settings.id].PerformCallback();
            }
        };

        return dlEditGrid;

    })();

    // Model validation
    var dlValidation = (function () {

        // .ctor
        var dlValidation = function (settings) {
            this.settings = settings;
            this.ShowErrors();
        };

        // methods
        dlValidation.prototype = {
            ShowErrors: function () {
                for (var i = 0; i < this.settings.objects.length; i++) {
                    var obj = this.settings.objects[i];

                    var clientId = obj.property;
                    if (obj.clientId != "") {
                        clientId = obj.clientId;
                    }

                    var el = this._FindElement(clientId);
                    if (el) {
                        if (obj.isError) {
                            $(el).addClass("EditFormValidationError");
                        } else {
                            $(el).removeClass("EditFormValidationError");
                        }
                    }
                }
            },

            _FindElement: function (clientId) {
                var el = document.getElementById(clientId + "_I");

                if (!el) {
                    el = document.getElementById(clientId);
                }

                return el;
            }
        };

        return dlValidation;

    })();

    // Model validation
    var dlTooltip = (function () {

        // .ctor
        var dlTooltip = function () {
            this.el = null;
        };

        // methods
        dlTooltip.prototype = {
            Show: function (target, content, position, className, hideDelay, appendTo) {
                if (!this.el) {
                    this.el = document.createElement("div");
                    if (!appendTo) {
                        document.body.appendChild(this.el);
                    } else {
                        $(appendTo).append(this.el);
                    }
                }
                if (!className) {
                    className = "Tooltip";
                }

                this.el.className = className;
                this.el.innerHTML = content;

                var pos = $(target).position();
                var tipwidth = $(this.el).width();
                var tipheight = $(this.el).height();
                var width = $(target).width();
                var height = $(target).height();
                var x = pos.left;
                var y = pos.top;

                if (!position) {
                    position = { y: "top", x: "right" };
                }

                if (position.y == "top") {
                    // Already top
                }
                if (position.y == "middle") {
                    y += (height / 2) - (tipheight / 2);
                }
                if (position.y == "bottom") {
                    y += height - tipheight;
                }

                if (position.x == "left") {
                    // Already left
                }
                if (position.x == "center") {
                    x += (width / 2) - (tipwidth / 2);
                }
                if (position.x == "right") {
                    x += width - tipwidth;
                }

                $(this.el).css({ left: x, top: y, display: "block" });

                if (hideDelay) {
                    setTimeout(this.Hide, hideDelay, this.el);
                }
            },

            Hide: function (el) {
                if (this.el) el = this.el;
                if (!el) return;
                $(el).fadeOut(500);
            }
        };

        return dlTooltip;

    })();

    // public namespace members
    return {
        dlMessageBoxInstance: dlMessageBoxInstance,
        dlMobileInstance: dlMobileInstance,
        dlChartManagerInstance: dlChartManagerInstance,
        dlChart: dlChart,
        dlResizePanelManagerInstance: dlResizePanelManagerInstance,
        dlResizePanel: dlResizePanel,
        dlContentMenuInstance: dlContentMenuInstance,
        dlEditForm: dlEditForm,
        dlEditGrid: dlEditGrid,
        dlEditGridManager: dlEditGridManager,
        dlEditGridManagerInstance: dlEditGridManagerInstance,
        dlValidation: dlValidation,
        dlTooltip: dlTooltip
    };
})();

/* Global hooks */
var _resizeCompleteId = null;
$(document).ready(function () {
    _hideAddressBar();
    datalab.dlMessageBoxInstance.Ready();
    datalab.dlContentMenuInstance.Ready();
    datalab.dlResizePanelManagerInstance.Update();
    datalab.dlChartManagerInstance.Ready();
    datalab.dlEditGridManagerInstance.Ready();

    $(window).on("resizeComplete", function (event) {
        _hideAddressBar();
        datalab.dlContentMenuInstance.Update();
        datalab.dlResizePanelManagerInstance.Update();
        datalab.dlMessageBoxInstance.Update();
    });

    $(window).on("resize", function () {
        clearTimeout(_resizeCompleteId);
        _resizeCompleteId = setTimeout(_resizeComplete, 200);
    });
});

// DEBUG
//window.onerror = function (errorMsg, url, lineNumber) {
//    _debug("error: " + errorMsg + ", " + lineNumber);
//}

function _resizeComplete(e) {
    $(window).trigger("resizeComplete", [ e ]);
}

function _resizePanelComplete(e) {
    $(window).trigger("resizePanelComplete", [ e ]);
}