Current File : //proc/thread-self/root/usr/share/phpmyadmin/js/src/makegrid.js
/* global firstDayOfCalendar */ // templates/javascript/variables.twig

/**
 * Create advanced table (resize, reorder, and show/hide columns; and also grid editing).
 * This function is designed mainly for table DOM generated from browsing a table in the database.
 * For using this function in other table DOM, you may need to:
 * - add "draggable" class in the table header <th>, in order to make it resizable, sortable or hidable
 * - have at least one non-"draggable" header in the table DOM for placing column visibility drop-down arrow
 * - pass the value "false" for the parameter "enableGridEdit"
 * - adjust other parameter value, to select which features that will be enabled
 *
 * @param t the table DOM element
 * @param enableResize Optional, if false, column resizing feature will be disabled
 * @param enableReorder Optional, if false, column reordering feature will be disabled
 * @param enableVisib Optional, if false, show/hide column feature will be disabled
 * @param enableGridEdit Optional, if false, grid editing feature will be disabled
 */
// eslint-disable-next-line no-unused-vars
var makeGrid = function (t, enableResize, enableReorder, enableVisib, enableGridEdit) {
    var isResizeEnabled = enableResize === undefined ? true : enableResize;
    var isReorderEnabled = enableReorder === undefined ? true : enableReorder;
    var isVisibEnabled = enableVisib === undefined ? true : enableVisib;
    var isGridEditEnabled = enableGridEdit === undefined ? true : enableGridEdit;

    var g = {
        /** *********
         * Constant
         ***********/
        minColWidth: 15,


        /** *********
         * Variables, assigned with default value, changed later
         ***********/
        actionSpan: 5,              // number of colspan in Actions header in a table
        tableCreateTime: null,      // table creation time, used for saving column order and visibility to server, only available in "Browse tab"

        // Column reordering variables
        colOrder: [],      // array of column order

        // Column visibility variables
        colVisib: [],      // array of column visibility
        showAllColText: '',         // string, text for "show all" button under column visibility list
        visibleHeadersCount: 0,     // number of visible data headers

        // Table hint variables
        reorderHint: '',            // string, hint for column reordering
        sortHint: '',               // string, hint for column sorting
        markHint: '',               // string, hint for column marking
        copyHint: '',               // string, hint for copy column name
        showReorderHint: false,
        showSortHint: false,
        showMarkHint: false,

        // Grid editing
        isCellEditActive: false,    // true if current focus is in edit cell
        isEditCellTextEditable: false,  // true if current edit cell is editable in the text input box (not textarea)
        currentEditCell: null,      // reference to <td> that currently being edited
        cellEditHint: '',           // hint shown when doing grid edit
        gotoLinkText: '',           // "Go to link" text
        wasEditedCellNull: false,   // true if last value of the edited cell was NULL
        maxTruncatedLen: 0,         // number of characters that can be displayed in a cell
        saveCellsAtOnce: false,     // $cfg[saveCellsAtOnce]
        isCellEdited: false,        // true if at least one cell has been edited
        saveCellWarning: '',        // string, warning text when user want to leave a page with unsaved edited data
        lastXHR : null,             // last XHR object used in AJAX request
        isSaving: false,            // true when currently saving edited data, used to handle double posting caused by pressing ENTER in grid edit text box in Chrome browser
        alertNonUnique: '',         // string, alert shown when saving edited nonunique table

        // Common hidden inputs
        token: null,
        server: null,
        db: null,
        table: null,


        /** **********
         * Functions
         ************/

        /**
         * Start to resize column. Called when clicking on column separator.
         *
         * @param e event
         * @param obj dragged div object
         */
        dragStartRsz: function (e, obj) {
            var n = $(g.cRsz).find('div').index(obj);    // get the index of separator (i.e., column index)
            $(obj).addClass('colborder_active');
            g.colRsz = {
                x0: e.pageX,
                n: n,
                obj: obj,
                objLeft: $(obj).position().left,
                objWidth: $(g.t).find('th.draggable:visible').eq(n).find('span').outerWidth()
            };
            // eslint-disable-next-line compat/compat
            $(document.body).css('cursor', 'col-resize').noSelect();
            if (g.isCellEditActive) {
                g.hideEditCell();
            }
        },

        /**
         * Start to reorder column. Called when clicking on table header.
         *
         * @param e event
         * @param obj table header object
         */
        dragStartReorder: function (e, obj) {
            // prepare the cCpy (column copy) and cPointer (column pointer) from the dragged column
            $(g.cCpy).text($(obj).text());
            var objPos = $(obj).position();
            $(g.cCpy).css({
                top: objPos.top + 20,
                left: objPos.left,
                height: $(obj).height(),
                width: $(obj).width()
            });
            $(g.cPointer).css({
                top: objPos.top
            });

            // get the column index, zero-based
            var n = g.getHeaderIdx(obj);

            g.colReorder = {
                x0: e.pageX,
                y0: e.pageY,
                n: n,
                newn: n,
                obj: obj,
                objTop: objPos.top,
                objLeft: objPos.left
            };

            // eslint-disable-next-line compat/compat
            $(document.body).css('cursor', 'move').noSelect();
            if (g.isCellEditActive) {
                g.hideEditCell();
            }
        },

        /**
         * Handle mousemove event when dragging.
         *
         * @param e event
         */
        dragMove: function (e) {
            var dx;
            if (g.colRsz) {
                dx = e.pageX - g.colRsz.x0;
                if (g.colRsz.objWidth + dx > g.minColWidth) {
                    $(g.colRsz.obj).css('left', g.colRsz.objLeft + dx + 'px');
                }
            } else if (g.colReorder) {
                // dragged column animation
                dx = e.pageX - g.colReorder.x0;
                $(g.cCpy)
                    .css('left', g.colReorder.objLeft + dx)
                    .show();

                // pointer animation
                var hoveredCol = g.getHoveredCol(e);
                if (hoveredCol) {
                    var newn = g.getHeaderIdx(hoveredCol);
                    g.colReorder.newn = newn;
                    if (newn !== g.colReorder.n) {
                        // show the column pointer in the right place
                        var colPos = $(hoveredCol).position();
                        var newleft = newn < g.colReorder.n ?
                            colPos.left :
                            colPos.left + $(hoveredCol).outerWidth();
                        $(g.cPointer)
                            .css({
                                left: newleft,
                                visibility: 'visible'
                            });
                    } else {
                        // no movement to other column, hide the column pointer
                        $(g.cPointer).css('visibility', 'hidden');
                    }
                }
            }
        },

        /**
         * Stop the dragging action.
         *
         * @param e event
         */
        dragEnd: function (e) {
            if (g.colRsz) {
                var dx = e.pageX - g.colRsz.x0;
                var nw = g.colRsz.objWidth + dx;
                if (nw < g.minColWidth) {
                    nw = g.minColWidth;
                }
                var n = g.colRsz.n;
                // do the resizing
                g.resize(n, nw);

                g.reposRsz();
                g.reposDrop();
                g.colRsz = false;
                $(g.cRsz).find('div').removeClass('colborder_active');
            } else if (g.colReorder) {
                // shift columns
                if (g.colReorder.newn !== g.colReorder.n) {
                    g.shiftCol(g.colReorder.n, g.colReorder.newn);
                    // assign new position
                    var objPos = $(g.colReorder.obj).position();
                    g.colReorder.objTop = objPos.top;
                    g.colReorder.objLeft = objPos.left;
                    g.colReorder.n = g.colReorder.newn;
                    // send request to server to remember the column order
                    if (g.tableCreateTime) {
                        g.sendColPrefs();
                    }
                    g.refreshRestoreButton();
                }

                // animate new column position
                $(g.cCpy).stop(true, true)
                    .animate({
                        top: g.colReorder.objTop,
                        left: g.colReorder.objLeft
                    }, 'fast')
                    .fadeOut();
                $(g.cPointer).css('visibility', 'hidden');

                g.colReorder = false;
            }
            // eslint-disable-next-line compat/compat
            $(document.body).css('cursor', 'inherit').noSelect(false);
        },

        /**
         * Resize column n to new width "nw"
         *
         * @param n zero-based column index
         * @param nw new width of the column in pixel
         */
        resize: function (n, nw) {
            $(g.t).find('tr').each(function () {
                $(this).find('th.draggable:visible').eq(n).find('span')
                    .add($(this).find('td:visible').eq(g.actionSpan + n).find('span'))
                    .css('width', nw);
            });
        },

        /**
         * Reposition column resize bars.
         */
        reposRsz: function () {
            $(g.cRsz).find('div').hide();
            var $firstRowCols = $(g.t).find('tr').first().find('th.draggable:visible');
            var $resizeHandles = $(g.cRsz).find('div').removeClass('condition');
            $(g.t).find('table.pma_table').find('thead th').first().removeClass('before-condition');
            for (var n = 0, l = $firstRowCols.length; n < l; n++) {
                var $col = $($firstRowCols[n]);
                var colWidth;
                if (navigator.userAgent.toLowerCase().indexOf('safari') !== -1) {
                    colWidth = $col.outerWidth();
                } else {
                    colWidth = $col.outerWidth(true);
                }
                $($resizeHandles[n]).css('left', $col.position().left + colWidth)
                    .show();
                if ($col.hasClass('condition')) {
                    $($resizeHandles[n]).addClass('condition');
                    if (n > 0) {
                        $($resizeHandles[n - 1]).addClass('condition');
                    }
                }
            }
            if ($($resizeHandles[0]).hasClass('condition')) {
                $(g.t).find('thead th').first().addClass('before-condition');
            }
            $(g.cRsz).css('height', $(g.t).height());
        },

        /**
         * Shift column from index oldn to newn.
         *
         * @param oldn old zero-based column index
         * @param newn new zero-based column index
         */
        shiftCol: function (oldn, newn) {
            $(g.t).find('tr').each(function () {
                if (newn < oldn) {
                    $(this).find('th.draggable').eq(newn)
                        .add($(this).find('td').eq(g.actionSpan + newn))
                        .before($(this).find('th.draggable').eq(oldn)
                            .add($(this).find('td').eq(g.actionSpan + oldn)));
                } else {
                    $(this).find('th.draggable').eq(newn)
                        .add($(this).find('td').eq(g.actionSpan + newn))
                        .after($(this).find('th.draggable').eq(oldn)
                            .add($(this).find('td').eq(g.actionSpan + oldn)));
                }
            });
            // reposition the column resize bars
            g.reposRsz();

            // adjust the column visibility list
            if (newn < oldn) {
                $(g.cList).find('.lDiv div').eq(newn)
                    .before($(g.cList).find('.lDiv div').eq(oldn));
            } else {
                $(g.cList).find('.lDiv div').eq(newn)
                    .after($(g.cList).find('.lDiv div').eq(oldn));
            }
            // adjust the colOrder
            var tmp = g.colOrder[oldn];
            g.colOrder.splice(oldn, 1);
            g.colOrder.splice(newn, 0, tmp);
            // adjust the colVisib
            if (g.colVisib.length > 0) {
                tmp = g.colVisib[oldn];
                g.colVisib.splice(oldn, 1);
                g.colVisib.splice(newn, 0, tmp);
            }
        },

        /**
         * Find currently hovered table column's header (excluding actions column).
         *
         * @param e event
         * @return {object|undefined} the hovered column's th object or undefined if no hovered column found.
         */
        getHoveredCol: function (e) {
            var hoveredCol;
            var $headers = $(g.t).find('th.draggable:visible');
            $headers.each(function () {
                var left = $(this).offset().left;
                var right = left + $(this).outerWidth();
                if (left <= e.pageX && e.pageX <= right) {
                    hoveredCol = this;
                }
            });
            return hoveredCol;
        },

        /**
         * Get a zero-based index from a <th class="draggable"> tag in a table.
         *
         * @param obj table header <th> object
         * @return {number} zero-based index of the specified table header in the set of table headers (visible or not)
         */
        getHeaderIdx: function (obj) {
            return $(obj).parents('tr').find('th.draggable').index(obj);
        },

        /**
         * Reposition the columns back to normal order.
         */
        restoreColOrder: function () {
            // use insertion sort, since we already have shiftCol function
            for (var i = 1; i < g.colOrder.length; i++) {
                var x = g.colOrder[i];
                var j = i - 1;
                while (j >= 0 && x < g.colOrder[j]) {
                    j--;
                }
                if (j !== i - 1) {
                    g.shiftCol(i, j + 1);
                }
            }
            if (g.tableCreateTime) {
                // send request to server to remember the column order
                g.sendColPrefs();
            }
            g.refreshRestoreButton();
        },

        /**
         * Send column preferences (column order and visibility) to the server.
         */
        sendColPrefs: function () {
            if ($(g.t).is('.ajax')) {   // only send preferences if ajax class
                if (typeof g.db !== 'string' && typeof g.table !== 'string') {
                    // The server has nothing to do with it
                    // Issue: https://github.com/phpmyadmin/phpmyadmin/issues/15658
                    return;
                }
                var postParams = {
                    'ajax_request': true,
                    'db': g.db,
                    'table': g.table,
                    'token': g.token,
                    'server': g.server,
                    'table_create_time': g.tableCreateTime
                };
                if (g.colOrder.length > 0) {
                    $.extend(postParams, { 'col_order': g.colOrder.toString() });
                }
                if (g.colVisib.length > 0) {
                    $.extend(postParams, { 'col_visib': g.colVisib.toString() });
                }
                $.post('index.php?route=/sql/set-column-preferences', postParams, function (data) {
                    if (data.success !== true) {
                        var $tempDiv = $(document.createElement('div'));
                        $tempDiv.html(data.error);
                        $tempDiv.addClass('alert alert-danger');
                        Functions.ajaxShowMessage($tempDiv, false);
                    }
                });
            }
        },

        /**
         * Refresh restore button state.
         * Make restore button disabled if the table is similar with initial state.
         */
        refreshRestoreButton: function () {
            // check if table state is as initial state
            var isInitial = true;
            for (var i = 0; i < g.colOrder.length; i++) {
                if (g.colOrder[i] !== i) {
                    isInitial = false;
                    break;
                }
            }
            // check if only one visible column left
            var isOneColumn = g.visibleHeadersCount === 1;
            // enable or disable restore button
            if (isInitial || isOneColumn) {
                $(g.o).find('div.restore_column').hide();
            } else {
                $(g.o).find('div.restore_column').show();
            }
        },

        /**
         * Update current hint using the boolean values (showReorderHint, showSortHint, etc.).
         *
         * @return {string}
         *
         */
        updateHint: function () {
            var text = '';
            if (!g.colRsz && !g.colReorder) {     // if not resizing or dragging
                if (g.visibleHeadersCount > 1) {
                    g.showReorderHint = true;
                }
                if ($(t).find('th.marker').length > 0) {
                    g.showMarkHint = true;
                }
                if (g.showSortHint && g.sortHint) {
                    text += text.length > 0 ? '<br>' : '';
                    text += '- ' + g.sortHint;
                }
                if (g.showMultiSortHint && g.strMultiSortHint) {
                    text += text.length > 0 ? '<br>' : '';
                    text += '- ' + g.strMultiSortHint;
                }
                if (g.showMarkHint &&
                    g.markHint &&
                    ! g.showSortHint && // we do not show mark hint, when sort hint is shown
                    g.showReorderHint &&
                    g.reorderHint
                ) {
                    text += text.length > 0 ? '<br>' : '';
                    text += '- ' + g.reorderHint;
                    text += text.length > 0 ? '<br>' : '';
                    text += '- ' + g.markHint;
                    text += text.length > 0 ? '<br>' : '';
                    text += '- ' + g.copyHint;
                }
            }
            return text;
        },

        /**
         * Toggle column's visibility.
         * After calling this function and it returns true, afterToggleCol() must be called.
         *
         * @param {number} n
         *
         * @return {boolean} True if the column is toggled successfully.
         */
        toggleCol: function (n) {
            if (g.colVisib[n]) {
                // can hide if more than one column is visible
                if (g.visibleHeadersCount > 1) {
                    $(g.t).find('tr').each(function () {
                        $(this).find('th.draggable').eq(n)
                            .add($(this).find('td').eq(g.actionSpan + n))
                            .hide();
                    });
                    g.colVisib[n] = 0;
                    $(g.cList).find('.lDiv div').eq(n).find('input').prop('checked', false);
                } else {
                    // cannot hide, force the checkbox to stay checked
                    $(g.cList).find('.lDiv div').eq(n).find('input').prop('checked', true);
                    return false;
                }
            } else {    // column n is not visible
                $(g.t).find('tr').each(function () {
                    $(this).find('th.draggable').eq(n)
                        .add($(this).find('td').eq(g.actionSpan + n))
                        .show();
                });
                g.colVisib[n] = 1;
                $(g.cList).find('.lDiv div').eq(n).find('input').prop('checked', true);
            }
            return true;
        },

        /**
         * This must be called if toggleCol() returns is true.
         *
         * This function is separated from toggleCol because, sometimes, we want to toggle
         * some columns together at one time and do just one adjustment after it, e.g. in showAllColumns().
         */
        afterToggleCol: function () {
            // some adjustments after hiding column
            g.reposRsz();
            g.reposDrop();
            g.sendColPrefs();

            // check visible first row headers count
            g.visibleHeadersCount = $(g.t).find('tr').first().find('th.draggable:visible').length;
            g.refreshRestoreButton();

            // Display minimum of one column - disable checkbox for hiding last column
            if (g.visibleHeadersCount <= 1) {
                $(g.cList).find('.lDiv div').each(function () {
                    $(this).find('input:checkbox:checked').prop('disabled', true);
                });
            } else {
                // Remove disabled property if showing more than one column
                $(g.cList).find('.lDiv div').each(function () {
                    $(this).find('input:checkbox:disabled').prop('disabled', false);
                });
            }
        },

        /**
         * Show columns' visibility list.
         *
         * @param obj The drop down arrow of column visibility list
         */
        showColList: function (obj) {
            // only show when not resizing or reordering
            if (!g.colRsz && !g.colReorder) {
                var pos = $(obj).position();
                $(g.cList).css({
                    top: pos.top + $(obj).outerHeight(true)
                })
                    .show();
                $(obj).addClass('coldrop-hover');
            }
        },

        /**
         * Hide columns' visibility list.
         */
        hideColList: function () {
            $(g.cList).hide();
            $(g.cDrop).find('.coldrop-hover').removeClass('coldrop-hover');
        },

        /**
         * Reposition the column visibility drop-down arrow.
         */
        reposDrop: function () {
            var $th = $(t).find('th:not(.draggable)');
            for (var i = 0; i < $th.length; i++) {
                var $cd = $(g.cDrop).find('div').eq(i);   // column drop-down arrow
                var pos = $($th[i]).position();
                $cd.css({
                    left: pos.left + $($th[i]).width() - $cd.width(),
                    top: pos.top
                });
            }
        },

        /**
         * Show all hidden columns.
         */
        showAllColumns: function () {
            for (var i = 0; i < g.colVisib.length; i++) {
                if (!g.colVisib[i]) {
                    g.toggleCol(i);
                }
            }
            g.afterToggleCol();
        },

        /**
         * Show edit cell, if it can be shown
         *
         * @param cell <td> element to be edited
         */
        showEditCell: function (cell) {
            // destroy the date picker instance left if any, see: #17703
            var $datePickerInstance = $(g.cEdit).find('.hasDatepicker');
            if ($datePickerInstance.length > 0) {
                $datePickerInstance.datepicker('destroy');
            }

            if ($(cell).is('.grid_edit') &&
                !g.colRsz && !g.colReorder) {
                if (!g.isCellEditActive) {
                    var $cell = $(cell);

                    if ('string' === $cell.attr('data-type') ||
                        'blob' === $cell.attr('data-type') ||
                        'json' === $cell.attr('data-type')
                    ) {
                        g.cEdit = g.cEditTextarea;
                    } else {
                        g.cEdit = g.cEditStd;
                    }

                    // remove all edit area and hide it
                    $(g.cEdit).find('.edit_area').empty().hide();
                    // reposition the cEdit element
                    $(g.cEdit).css({
                        top: $cell.position().top,
                        left: $cell.position().left
                    })
                        .show()
                        .find('.edit_box')
                        .css({
                            width: $cell.outerWidth(),
                            height: $cell.outerHeight()
                        });
                    // fill the cell edit with text from <td>
                    var value = Functions.getCellValue(cell);
                    if ($cell.attr('data-type') === 'json' && $cell.is('.truncated') === false) {
                        value = Functions.stringifyJSON(value, null, 4);
                    }
                    $(g.cEdit).find('.edit_box').val(value);

                    g.currentEditCell = cell;
                    $(g.cEdit).find('.edit_box').trigger('focus');
                    moveCursorToEnd($(g.cEdit).find('.edit_box'));
                    $(g.cEdit).find('*').prop('disabled', false);
                }
            }

            function moveCursorToEnd (input) {
                var originalValue = input.val();
                var originallength = originalValue.length;
                input.val('');
                input.trigger('blur').trigger('focus').val(originalValue);
                input[0].setSelectionRange(originallength, originallength);
            }
        },

        /**
         * Remove edit cell and the edit area, if it is shown.
         *
         * @param force Optional, force to hide edit cell without saving edited field.
         * @param data  Optional, data from the POST AJAX request to save the edited field
         *              or just specify "true", if we want to replace the edited field with the new value.
         * @param field Optional, the edited <td>. If not specified, the function will
         *              use currently edited <td> from g.currentEditCell.
         * @param options Optional, this object contains a boolean named move (true, if called from move* functions)
         *                and a <td> to which the grid_edit should move
         */
        hideEditCell: function (force, data, field, options) {
            if (g.isCellEditActive && !force) {
                // cell is being edited, save or post the edited data
                if (options !== undefined) {
                    g.saveOrPostEditedCell(options);
                } else {
                    g.saveOrPostEditedCell();
                }
                return;
            }

            // cancel any previous request
            if (g.lastXHR !== null) {
                g.lastXHR.abort();
                g.lastXHR = null;
            }

            if (data) {
                if (g.currentEditCell) {    // save value of currently edited cell
                    // replace current edited field with the new value
                    var $thisField = $(g.currentEditCell);
                    var isNull = $thisField.data('value') === null;
                    if (isNull) {
                        $thisField.find('span').html('NULL');
                        $thisField.addClass('null');
                    } else {
                        $thisField.removeClass('null');
                        var value = data.isNeedToRecheck
                            ? data.truncatableFieldValue
                            : $thisField.data('value');

                        // Truncates the text.
                        $thisField.removeClass('truncated');
                        if (CommonParams.get('pftext') === 'P' && value.length > g.maxTruncatedLen) {
                            $thisField.addClass('truncated');
                            value = value.substring(0, g.maxTruncatedLen) + '...';
                        }

                        // Add <br> before carriage return.
                        var newHtml = Functions.escapeHtml(value);
                        newHtml = newHtml.replace(/\n/g, '<br>\n');

                        var decimals = parseInt($thisField.attr('data-decimals'));

                        // remove decimal places if column type not supported
                        if ((decimals === 0) && ($thisField.attr('data-type').indexOf('time') !== -1)) {
                            newHtml = newHtml.substring(0, newHtml.indexOf('.'));
                        }

                        // remove additional decimal places
                        if ((decimals > 0) && ($thisField.attr('data-type').indexOf('time') !== -1)) {
                            newHtml = newHtml.substring(0, newHtml.length - (6 - decimals));
                        }

                        var selector = 'span';
                        if ($thisField.hasClass('hex') && $thisField.find('a').length) {
                            selector = 'a';
                        }

                        // Updates the code keeping highlighting (if any).
                        var $target = $thisField.find(selector);
                        if (!Functions.updateCode($target, newHtml, value)) {
                            $target.html(newHtml);
                        }
                    }
                    if ($thisField.is('.bit')) {
                        $thisField.find('span').text($thisField.data('value'));
                    }
                }
                if (data.transformations !== undefined) {
                    $.each(data.transformations, function (cellIndex, value) {
                        var $thisField = $(g.t).find('.to_be_saved').eq(cellIndex);
                        $thisField.find('span').html(value);
                    });
                }
                if (data.relations !== undefined) {
                    $.each(data.relations, function (cellIndex, value) {
                        var $thisField = $(g.t).find('.to_be_saved').eq(cellIndex);
                        $thisField.find('span').html(value);
                    });
                }

                // refresh the grid
                g.reposRsz();
                g.reposDrop();
            }

            // hide the cell editing area
            $(g.cEdit).hide();
            $(g.cEdit).find('.edit_box').trigger('blur');
            g.isCellEditActive = false;
            g.currentEditCell = null;
            // destroy datepicker in edit area, if exist
            var $dp = $(g.cEdit).find('.hasDatepicker');
            if ($dp.length > 0) {
                // eslint-disable-next-line no-underscore-dangle
                $(document).on('mousedown', $.datepicker._checkExternalClick);
                $dp.datepicker('refresh');

                // change the cursor in edit box back to normal
                // (the cursor become a hand pointer when we add datepicker)
                $(g.cEdit).find('.edit_box').css('cursor', 'inherit');
            }
        },

        /**
         * Show drop-down edit area when edit cell is focused.
         */
        showEditArea: function () {
            if (!g.isCellEditActive) {   // make sure the edit area has not been shown
                g.isCellEditActive = true;
                g.isEditCellTextEditable = false;
                /**
                 * @var $td current edited cell
                 */
                var $td = $(g.currentEditCell);
                /**
                 * @var $editArea the editing area
                 */
                var $editArea = $(g.cEdit).find('.edit_area');
                /**
                 * @var whereClause WHERE clause for the edited cell
                 */
                var whereClause = $td.parent('tr').find('.where_clause').val();
                /**
                 * @var fieldName  String containing the name of this field.
                 * @see Sql.getFieldName()
                 */
                var fieldName = Sql.getFieldName($(t), $td);
                /**
                 * @var relationCurrValue String current value of the field (for fields that are foreign keyed).
                 */
                var relationCurrValue = $td.text();
                /**
                 * @var relationKeyOrDisplayColumn String relational key if in 'Relational display column' mode,
                 * relational display column if in 'Relational key' mode (for fields that are foreign keyed).
                 */
                var relationKeyOrDisplayColumn = $td.find('a').attr('title');
                /**
                 * @var currValue String current value of the field (for fields that are of type enum or set).
                 */
                var currValue = $td.find('span').text();

                // empty all edit area, then rebuild it based on $td classes
                $editArea.empty();

                // remember this instead of testing more than once
                var isNull = $td.is('.null');

                // add goto link, if this cell contains a link
                if ($td.find('a').length > 0) {
                    var gotoLink = document.createElement('div');
                    gotoLink.className = 'goto_link';
                    $(gotoLink).append(g.gotoLinkText + ' ').append($td.find('a').clone());
                    $editArea.append(gotoLink);
                }

                g.wasEditedCellNull = false;
                if ($td.is(':not(.not_null)')) {
                    // append a null checkbox
                    $editArea.append('<div class="null_div"><label>NULL:<input type="checkbox"></label></div>');

                    var $checkbox = $editArea.find('.null_div input');
                    // check if current <td> is NULL
                    if (isNull) {
                        $checkbox.prop('checked', true);
                        g.wasEditedCellNull = true;
                    }

                    // if the select/editor is changed un-check the 'checkbox_null_<field_name>_<row_index>'.
                    if ($td.is('.enum, .set')) {
                        $editArea.on('change', 'select', function () {
                            $checkbox.prop('checked', false);
                        });
                    } else if ($td.is('.relation')) {
                        $editArea.on('change', 'select', function () {
                            $checkbox.prop('checked', false);
                        });
                        $editArea.on('click', '.browse_foreign', function () {
                            $checkbox.prop('checked', false);
                        });
                    } else {
                        $(g.cEdit).on('keypress change paste', '.edit_box', function () {
                            $checkbox.prop('checked', false);
                        });
                        // Capture ctrl+v (on IE and Chrome)
                        $(g.cEdit).on('keydown', '.edit_box', function (e) {
                            if (e.ctrlKey && e.which === 86) {
                                $checkbox.prop('checked', false);
                            }
                        });
                        $editArea.on('keydown', 'textarea', function () {
                            $checkbox.prop('checked', false);
                        });
                    }
                    // if some text is written in textbox automatically unmark the null checkbox and if it is emptied again mark the checkbox.
                    $(g.cEdit).find('.edit_box').on('input', function () {
                        if ($(g.cEdit).find('.edit_box').val() !== '') {
                            $checkbox.prop('checked', false);
                        } else {
                            $checkbox.prop('checked', true);
                        }
                    });
                    // if null checkbox is clicked empty the corresponding select/editor.
                    $checkbox.on('click', function () {
                        if ($td.is('.enum')) {
                            $editArea.find('select').val('');
                        } else if ($td.is('.set')) {
                            $editArea.find('select').find('option').each(function () {
                                var $option = $(this);
                                $option.prop('selected', false);
                            });
                        } else if ($td.is('.relation')) {
                            // if the dropdown is there to select the foreign value
                            if ($editArea.find('select').length > 0) {
                                $editArea.find('select').val('');
                            }
                        } else {
                            $editArea.find('textarea').val('');
                        }
                        $(g.cEdit).find('.edit_box').val('');
                    });
                }

                // reset the position of the edit_area div after closing datetime picker
                $(g.cEdit).find('.edit_area').css({ 'top' :'0','position':'' });

                var postParams;
                if ($td.is('.relation')) {
                    // handle relations
                    $editArea.addClass('edit_area_loading');

                    // initialize the original data
                    $td.data('original_data', null);

                    /**
                     * @var postParams Object containing parameters for the POST request
                     */
                    postParams = {
                        'ajax_request' : true,
                        'server' : g.server,
                        'db' : g.db,
                        'table' : g.table,
                        'column' : fieldName,
                        'curr_value' : relationCurrValue,
                        'relation_key_or_display_column' : relationKeyOrDisplayColumn
                    };

                    g.lastXHR = $.post('index.php?route=/sql/get-relational-values', postParams, function (data) {
                        g.lastXHR = null;
                        $editArea.removeClass('edit_area_loading');
                        if ($(data.dropdown).is('select')) {
                            // save original_data
                            var value = $(data.dropdown).val();
                            $td.data('original_data', value);
                            // update the text input field, in case where the "Relational display column" is checked
                            $(g.cEdit).find('.edit_box').val(value);
                        }

                        $editArea.append(data.dropdown);
                        $editArea.append('<div class="cell_edit_hint">' + g.cellEditHint + '</div>');

                        // for 'Browse foreign values' options,
                        // hide the value next to 'Browse foreign values' link
                        $editArea.find('span.curr_value').hide();
                        // handle update for new values selected from new window
                        $editArea.find('span.curr_value').on('change', function () {
                            $(g.cEdit).find('.edit_box').val($(this).text());
                        });
                    }); // end $.post()

                    $editArea.show();
                    $editArea.on('change', 'select', function () {
                        $(g.cEdit).find('.edit_box').val($(this).val());
                    });
                    g.isEditCellTextEditable = true;
                } else if ($td.is('.enum')) {
                    // handle enum fields
                    $editArea.addClass('edit_area_loading');

                    /**
                     * @var postParams Object containing parameters for the POST request
                     */
                    postParams = {
                        'ajax_request' : true,
                        'server' : g.server,
                        'db' : g.db,
                        'table' : g.table,
                        'column' : fieldName,
                        'curr_value' : currValue
                    };
                    g.lastXHR = $.post('index.php?route=/sql/get-enum-values', postParams, function (data) {
                        g.lastXHR = null;
                        if (typeof data === 'object' && data.success === false) {
                            Functions.ajaxShowMessage(data.error, undefined, 'error');
                            return;
                        }
                        $editArea.removeClass('edit_area_loading');
                        $editArea.append(data.dropdown);
                        $editArea.append('<div class="cell_edit_hint">' + g.cellEditHint + '</div>');
                    }); // end $.post()

                    $editArea.show();
                    $editArea.on('change', 'select', function () {
                        $(g.cEdit).find('.edit_box').val($(this).val());
                    });
                } else if ($td.is('.set')) {
                    // handle set fields
                    $editArea.addClass('edit_area_loading');

                    // if the data is truncated, get the full data
                    if ($td.is('.truncated')) {
                        postParams = {
                            'ajax_request': true,
                            'server': g.server,
                            'db': g.db,
                            'table': g.table,
                            'column': fieldName,
                            'curr_value': currValue,
                            'get_full_values': true,
                            'where_clause': whereClause
                        };
                    } else {
                        postParams = {
                            'ajax_request': true,
                            'server': g.server,
                            'db': g.db,
                            'table': g.table,
                            'column': fieldName,
                            'curr_value': currValue
                        };
                    }

                    g.lastXHR = $.post('index.php?route=/sql/get-set-values', postParams, function (data) {
                        g.lastXHR = null;
                        if (typeof data === 'object' && data.success === false) {
                            Functions.ajaxShowMessage(data.error, undefined, 'error');
                            return;
                        }
                        $editArea.removeClass('edit_area_loading');
                        $editArea.append(data.select);
                        $td.data('original_data', $(data.select).val().join());
                        $editArea.append('<div class="cell_edit_hint">' + g.cellEditHint + '</div>');
                    }); // end $.post()

                    $editArea.show();
                    $editArea.on('change', 'select', function () {
                        $(g.cEdit).find('.edit_box').val($(this).val());
                    });
                } else if ($td.is('.truncated, .transformed')) {
                    if ($td.is('.to_be_saved')) {   // cell has been edited
                        var value = $td.data('value');
                        $(g.cEdit).find('.edit_box').val(value);
                        $editArea.append('<textarea></textarea>');
                        $editArea.find('textarea').val(value);
                        $editArea
                            .on('keyup', 'textarea', function () {
                                $(g.cEdit).find('.edit_box').val($(this).val());
                            });
                        $(g.cEdit).on('keyup', '.edit_box', function () {
                            $editArea.find('textarea').val($(this).val());
                        });
                        $editArea.append('<div class="cell_edit_hint">' + g.cellEditHint + '</div>');
                    } else {
                        // handle truncated/transformed values values
                        $editArea.addClass('edit_area_loading');

                        // initialize the original data
                        $td.data('original_data', null);

                        /**
                         * @var sqlQuery   String containing the SQL query used to retrieve value of truncated/transformed data
                         */
                        var sqlQuery = 'SELECT `' + fieldName + '` FROM `' + g.table + '` WHERE ' + whereClause;

                        // Make the Ajax call and get the data, wrap it and insert it
                        g.lastXHR = $.post('index.php?route=/sql', {
                            'server' : g.server,
                            'db' : g.db,
                            'ajax_request' : true,
                            'sql_query' : sqlQuery,
                            'grid_edit' : true
                        }, function (data) {
                            g.lastXHR = null;
                            $editArea.removeClass('edit_area_loading');
                            if (typeof data !== 'undefined' && data.success === true) {
                                if ($td.attr('data-type') === 'json') {
                                    data.value = Functions.stringifyJSON(data.value, null, 4);
                                }
                                $td.data('original_data', data.value);
                                $(g.cEdit).find('.edit_box').val(data.value);
                            } else {
                                Functions.ajaxShowMessage(data.error, false);
                            }
                        }); // end $.post()
                    }
                    g.isEditCellTextEditable = true;
                } else if ($td.is('.timefield, .datefield, .datetimefield, .timestampfield')) {
                    var $inputField = $(g.cEdit).find('.edit_box');

                    // remember current datetime value in $input_field, if it is not null
                    var datetimeValue = !isNull ? $inputField.val() : '';

                    var showMillisec = false;
                    var showMicrosec = false;
                    var timeFormat = 'HH:mm:ss';
                    // check for decimal places of seconds
                    if (($td.attr('data-decimals') > 0) && ($td.attr('data-type').indexOf('time') !== -1)) {
                        if (datetimeValue && datetimeValue.indexOf('.') === false) {
                            datetimeValue += '.';
                        }
                        if ($td.attr('data-decimals') > 3) {
                            showMillisec = true;
                            showMicrosec = true;
                            timeFormat = 'HH:mm:ss.lc';

                            if (datetimeValue) {
                                datetimeValue += '000000';
                                datetimeValue = datetimeValue.substring(0, datetimeValue.indexOf('.') + 7);
                                $inputField.val(datetimeValue);
                            }
                        } else {
                            showMillisec = true;
                            timeFormat = 'HH:mm:ss.l';

                            if (datetimeValue) {
                                datetimeValue += '000';
                                datetimeValue = datetimeValue.substring(0, datetimeValue.indexOf('.') + 4);
                                $inputField.val(datetimeValue);
                            }
                        }
                    }

                    // add datetime picker
                    Functions.addDatepicker($inputField, $td.attr('data-type'), {
                        showMillisec: showMillisec,
                        showMicrosec: showMicrosec,
                        timeFormat: timeFormat,
                        firstDay: firstDayOfCalendar
                    });

                    $inputField.on('keyup', function (e) {
                        if (e.which === 13) {
                            // post on pressing "Enter"
                            e.preventDefault();
                            e.stopPropagation();
                            g.saveOrPostEditedCell();
                        } else if (e.which !== 27) {
                            Functions.toggleDatepickerIfInvalid($td, $inputField);
                        }
                    });

                    $inputField.datepicker('show');
                    Functions.toggleDatepickerIfInvalid($td, $inputField);

                    // unbind the mousedown event to prevent the problem of
                    // datepicker getting closed, needs to be checked for any
                    // change in names when updating
                    // eslint-disable-next-line no-underscore-dangle
                    $(document).off('mousedown', $.datepicker._checkExternalClick);

                    // move ui-datepicker-div inside cEdit div
                    var datepickerDiv = $('#ui-datepicker-div');
                    datepickerDiv.css({ 'top': 0, 'left': 0, 'position': 'relative' });
                    $(g.cEdit).append(datepickerDiv);

                    // cancel any click on the datepicker element
                    $editArea.find('> *').on('click', function (e) {
                        e.stopPropagation();
                    });

                    g.isEditCellTextEditable = true;
                } else {
                    g.isEditCellTextEditable = true;
                    // only append edit area hint if there is a null checkbox
                    if ($editArea.children().length > 0) {
                        $editArea.append('<div class="cell_edit_hint">' + g.cellEditHint + '</div>');
                    }
                }
                if ($editArea.children().length > 0) {
                    $editArea.show();
                }
            }
        },

        /**
         * Post the content of edited cell.
         *
         * @param options Optional, this object contains a boolean named move (true, if called from move* functions)
         *                and a <td> to which the grid_edit should move
         */
        postEditedCell: function (options) {
            if (g.isSaving) {
                return;
            }
            g.isSaving = true;
            /**
             * @var relationFields Array containing the name/value pairs of relational fields
             */
            var relationFields = {};
            /**
             * @var relationalDisplay string 'K' if relational key, 'D' if relational display column
             */
            var relationalDisplay = $(g.o).find('input[name=relational_display]:checked').val();
            /**
             * @var transformFields    Array containing the name/value pairs for transformed fields
             */
            var transformFields = {};
            /**
             * @var transformationFields   Boolean, if there are any transformed fields in the edited cells
             */
            var transformationFields = false;
            /**
             * @var fullSqlQuery String containing the complete SQL query to update this table
             */
            var fullSqlQuery = '';
            /**
             * @var relFieldsList  String, url encoded representation of {@link relations_fields}
             */
            var relFieldsList = '';
            /**
             * @var transformFieldsList  String, url encoded representation of {@link transformFields}
             */
            var transformFieldsList = '';
            /**
             * @var fullWhereClause Array containing where clause for updated fields
             */
            var fullWhereClause = [];
            /**
             * @var isUnique   Boolean, whether the rows in this table is unique or not
             */
            var isUnique = $(g.t).find('td.edit_row_anchor').is('.nonunique') ? 0 : 1;
            /**
             * multi edit variables
             */
            var multiEditFieldsName = [];
            var multiEditFieldsType = [];
            var multiEditFields = [];
            var multiEditFieldsNull = [];

            // alert user if edited table is not unique
            if (!isUnique) {
                alert(g.alertNonUnique);
            }

            // loop each edited row
            $(g.t).find('td.to_be_saved').parents('tr').each(function () {
                var $tr = $(this);
                var whereClause = $tr.find('.where_clause').val();
                if (typeof whereClause === 'undefined') {
                    whereClause = '';
                }
                fullWhereClause.push(whereClause);
                var conditionArray = JSON.parse($tr.find('.condition_array').val());

                /**
                 * multi edit variables, for current row
                 * @TODO array indices are still not correct, they should be md5 of field's name
                 */
                var fieldsName = [];
                var fieldsType = [];
                var fields = [];
                var fieldsNull = [];

                // loop each edited cell in a row
                $tr.find('.to_be_saved').each(function () {
                    /**
                     * @var $thisField    Object referring to the td that is being edited
                     */
                    var $thisField = $(this);

                    /**
                     * @var fieldName  String containing the name of this field.
                     * @see Sql.getFieldName()
                     */
                    var fieldName = Sql.getFieldName($(g.t), $thisField);

                    /**
                     * @var thisFieldParams   Array temporary storage for the name/value of current field
                     */
                    var thisFieldParams = {};

                    if ($thisField.is('.transformed')) {
                        transformationFields =  true;
                    }
                    thisFieldParams[fieldName] = $thisField.data('value');

                    /**
                     * @var isNull String capturing whether 'checkbox_null_<field_name>_<row_index>' is checked.
                     */
                    var isNull = thisFieldParams[fieldName] === null;

                    fieldsName.push(fieldName);

                    if (isNull) {
                        fieldsNull.push('on');
                        fields.push('');
                    } else {
                        if ($thisField.is('.bit')) {
                            fieldsType.push('bit');
                        } else if ($thisField.hasClass('hex')) {
                            fieldsType.push('hex');
                        }
                        fieldsNull.push('');

                        if ($thisField.attr('data-type') !== 'json') {
                            fields.push($thisField.data('value'));
                        } else {
                            const JSONString = Functions.stringifyJSON($thisField.data('value'));
                            fields.push(JSONString);
                        }

                        var cellIndex = $thisField.index('.to_be_saved');
                        if ($thisField.is(':not(.relation, .enum, .set, .bit)')) {
                            if ($thisField.is('.transformed')) {
                                transformFields[cellIndex] = {};
                                $.extend(transformFields[cellIndex], thisFieldParams);
                            }
                        } else if ($thisField.is('.relation')) {
                            relationFields[cellIndex] = {};
                            $.extend(relationFields[cellIndex], thisFieldParams);
                        }
                    }
                    // check if edited field appears in WHERE clause
                    if (whereClause.indexOf(Sql.urlEncode(fieldName)) > -1) {
                        var fieldStr = '`' + g.table + '`.' + '`' + fieldName + '`';
                        for (var field in conditionArray) {
                            if (field.indexOf(fieldStr) > -1) {
                                conditionArray[field] = isNull ? 'IS NULL' : '= \'' + thisFieldParams[fieldName].replace(/'/g, '\'\'') + '\'';
                                break;
                            }
                        }
                    }
                }); // end of loop for every edited cells in a row

                // save new_clause
                var newClause = '';
                for (var field in conditionArray) {
                    newClause += field + ' ' + conditionArray[field] + ' AND ';
                }
                newClause = newClause.substring(0, newClause.length - 5); // remove the last AND
                $tr.data('new_clause', newClause);
                // save condition_array
                $tr.find('.condition_array').val(JSON.stringify(conditionArray));

                multiEditFieldsName.push(fieldsName);
                multiEditFieldsType.push(fieldsType);
                multiEditFields.push(fields);
                multiEditFieldsNull.push(fieldsNull);
            }); // end of loop for every edited rows

            relFieldsList = $.param(relationFields);
            transformFieldsList = $.param(transformFields);

            // Make the Ajax post after setting all parameters
            /**
             * @var postParams Object containing parameters for the POST request
             */
            var postParams = { 'ajax_request' : true,
                'sql_query' : fullSqlQuery,
                'server' : g.server,
                'db' : g.db,
                'table' : g.table,
                'clause_is_unique' : isUnique,
                'where_clause' : fullWhereClause,
                'fields[multi_edit]' : multiEditFields,
                'fields_name[multi_edit]' : multiEditFieldsName,
                'fields_type[multi_edit]' : multiEditFieldsType,
                'fields_null[multi_edit]' : multiEditFieldsNull,
                'rel_fields_list' : relFieldsList,
                'do_transformations' : transformationFields,
                'transform_fields_list' : transformFieldsList,
                'relational_display' : relationalDisplay,
                'goto' : encodeURIComponent('index.php?route=/sql'),
                'submit_type' : 'save'
            };

            if (!g.saveCellsAtOnce) {
                $(g.cEdit).find('*').prop('disabled', true);
                $(g.cEdit).find('.edit_box').addClass('edit_box_posting');
            } else {
                $(g.o).find('div.save_edited').addClass('saving_edited_data')
                    .find('input').prop('disabled', true);    // disable the save button
            }

            $.ajax({
                type: 'POST',
                url: 'index.php?route=/table/replace',
                data: postParams,
                success:
                    function (data) {
                        g.isSaving = false;
                        if (!g.saveCellsAtOnce) {
                            $(g.cEdit).find('*').prop('disabled', false);
                            $(g.cEdit).find('.edit_box').removeClass('edit_box_posting');
                        } else {
                            $(g.o).find('div.save_edited').removeClass('saving_edited_data')
                                .find('input').prop('disabled', false);  // enable the save button back
                        }
                        if (typeof data !== 'undefined' && data.success === true) {
                            if (typeof options === 'undefined' || ! options.move) {
                                Functions.ajaxShowMessage(data.message);
                            }

                            // update where_clause related data in each edited row
                            $(g.t).find('td.to_be_saved').parents('tr').each(function () {
                                var newClause = $(this).data('new_clause');
                                var $whereClause = $(this).find('.where_clause');
                                var oldClause = $whereClause.val();
                                var decodedOldClause = oldClause;
                                var decodedNewClause = newClause;

                                $whereClause.val(newClause);
                                // update Edit, Copy, and Delete links also
                                $(this).find('a').each(function () {
                                    $(this).attr('href', $(this).attr('href').replace(oldClause, newClause));
                                    // update delete confirmation in Delete link
                                    if ($(this).attr('href').indexOf('DELETE') > -1) {
                                        $(this).removeAttr('onclick')
                                            .off('click')
                                            .on('click', function () {
                                                return Functions.confirmLink(this, 'DELETE FROM `' + g.db + '`.`' + g.table + '` WHERE ' +
                                                       decodedNewClause + (isUnique ? '' : ' LIMIT 1'));
                                            });
                                    }
                                });
                                // update the multi edit checkboxes
                                $(this).find('input[type=checkbox]').each(function () {
                                    var $checkbox = $(this);
                                    var checkboxName = $checkbox.attr('name');
                                    var checkboxValue = $checkbox.val();

                                    $checkbox.attr('name', checkboxName.replace(oldClause, newClause));
                                    $checkbox.val(checkboxValue.replace(decodedOldClause, decodedNewClause));
                                });
                            });
                            // update the display of executed SQL query command
                            if (typeof data.sql_query !== 'undefined') {
                                // extract query box
                                var $resultQuery = $($.parseHTML(data.sql_query));
                                var sqlOuter = $resultQuery.find('.sqlOuter').wrap('<p>').parent().html();
                                var tools = $resultQuery.find('.tools').wrap('<p>').parent().html();
                                // sqlOuter and tools will not be present if 'Show SQL queries' configuration is off
                                if (typeof sqlOuter !== 'undefined' && typeof tools !== 'undefined') {
                                    $(g.o).find('.result_query').not($(g.o).find('.result_query').last()).remove();
                                    var $existingQuery = $(g.o).find('.result_query');
                                    // If two query box exists update query in second else add a second box
                                    if ($existingQuery.find('div.sqlOuter').length > 1) {
                                        $existingQuery.children().eq(3).remove();
                                        $existingQuery.children().eq(3).remove();
                                        $existingQuery.append(sqlOuter + tools);
                                    } else {
                                        $existingQuery.append(sqlOuter + tools);
                                    }
                                    Functions.highlightSql($existingQuery);
                                }
                            }
                            // hide and/or update the successfully saved cells
                            g.hideEditCell(true, data);

                            // remove the "Save edited cells" button
                            $(g.o).find('div.save_edited').hide();
                            // update saved fields
                            $(g.t).find('.to_be_saved')
                                .removeClass('to_be_saved')
                                .data('value', null)
                                .data('original_data', null);

                            g.isCellEdited = false;
                        } else {
                            Functions.ajaxShowMessage(data.error, false);
                            if (!g.saveCellsAtOnce) {
                                $(g.t).find('.to_be_saved')
                                    .removeClass('to_be_saved');
                            }
                        }
                    }
            }).done(function () {
                if (options !== undefined && options.move) {
                    g.showEditCell(options.cell);
                }
            }); // end $.ajax()
        },

        /**
         * Save edited cell, so it can be posted later.
         *
         * @return {bool}
         */
        saveEditedCell: function () {
            /**
             * @var $thisField    Object referring to the td that is being edited
             */
            var $thisField = $(g.currentEditCell);
            var $testElement = ''; // to test the presence of a element

            var needToPost = false;

            /**
             * @var fieldName  String containing the name of this field.
             * @see Sql.getFieldName()
             */
            var fieldName = Sql.getFieldName($(g.t), $thisField);

            /**
             * @var thisFieldParams   Array temporary storage for the name/value of current field
             */
            var thisFieldParams = {};

            /**
             * @var isNull String capturing whether 'checkbox_null_<field_name>_<row_index>' is checked.
             */
            var isNull = $(g.cEdit).find('input:checkbox').is(':checked');

            if ($(g.cEdit).find('.edit_area').is('.edit_area_loading')) {
                // the edit area is still loading (retrieving cell data), no need to post
                needToPost = false;
            } else if (isNull) {
                if (!g.wasEditedCellNull) {
                    thisFieldParams[fieldName] = null;
                    needToPost = true;
                }
            } else {
                if ($thisField.is('.bit')) {
                    thisFieldParams[fieldName] = $(g.cEdit).find('.edit_box').val();
                } else if ($thisField.is('.set')) {
                    $testElement = $(g.cEdit).find('select');
                    thisFieldParams[fieldName] = $testElement.map(function () {
                        return $(this).val();
                    }).get().join(',');
                } else if ($thisField.is('.relation, .enum')) {
                    // for relation and enumeration, take the results from edit box value,
                    // because selected value from drop-down, new window or multiple
                    // selection list will always be updated to the edit box
                    thisFieldParams[fieldName] = $(g.cEdit).find('.edit_box').val();
                } else if ($thisField.hasClass('hex')) {
                    if ($(g.cEdit).find('.edit_box').val().match(/^(0x)?[a-f0-9]*$/i) !== null) {
                        thisFieldParams[fieldName] = $(g.cEdit).find('.edit_box').val();
                    } else {
                        var hexError = '<div class="alert alert-danger" role="alert">' + Messages.strEnterValidHex + '</div>';
                        Functions.ajaxShowMessage(hexError, false);
                        thisFieldParams[fieldName] = Functions.getCellValue(g.currentEditCell);
                    }
                } else {
                    thisFieldParams[fieldName] = $(g.cEdit).find('.edit_box').val();
                }

                let isValueUpdated;
                if ($thisField.attr('data-type') !== 'json') {
                    isValueUpdated = thisFieldParams[fieldName] !== Functions.getCellValue(g.currentEditCell);
                } else {
                    const JSONString = Functions.stringifyJSON(thisFieldParams[fieldName]);
                    isValueUpdated = JSONString !== JSON.stringify(JSON.parse(Functions.getCellValue(g.currentEditCell)));
                }

                if (g.wasEditedCellNull || isValueUpdated) {
                    needToPost = true;
                }
            }

            if (needToPost) {
                $(g.currentEditCell).addClass('to_be_saved')
                    .data('value', thisFieldParams[fieldName]);
                if (g.saveCellsAtOnce) {
                    $(g.o).find('div.save_edited').show();
                }
                g.isCellEdited = true;
            }

            return needToPost;
        },

        /**
         * Save or post currently edited cell, depending on the "saveCellsAtOnce" configuration.
         *
         * @param options Optional, this object contains a boolean named move (true, if called from move* functions)
         *                and a <td> to which the grid_edit should move
         */
        saveOrPostEditedCell: function (options) {
            var saved = g.saveEditedCell();
            // Check if $cfg['SaveCellsAtOnce'] is false
            if (!g.saveCellsAtOnce) {
                // Check if need_to_post is true
                if (saved) {
                    // Check if this function called from 'move' functions
                    if (options !== undefined && options.move) {
                        g.postEditedCell(options);
                    } else {
                        g.postEditedCell();
                    }
                // need_to_post is false
                } else {
                    // Check if this function called from 'move' functions
                    if (options !== undefined && options.move) {
                        g.hideEditCell(true);
                        g.showEditCell(options.cell);
                    // NOT called from 'move' functions
                    } else {
                        g.hideEditCell(true);
                    }
                }
            // $cfg['SaveCellsAtOnce'] is true
            } else {
                // If need_to_post
                if (saved) {
                    // If this function called from 'move' functions
                    if (options !== undefined && options.move) {
                        g.hideEditCell(true, true, false, options);
                        g.showEditCell(options.cell);
                    // NOT called from 'move' functions
                    } else {
                        g.hideEditCell(true, true);
                    }
                } else {
                    // If this function called from 'move' functions
                    if (options !== undefined && options.move) {
                        g.hideEditCell(true, false, false, options);
                        g.showEditCell(options.cell);
                    // NOT called from 'move' functions
                    } else {
                        g.hideEditCell(true);
                    }
                }
            }
        },

        /**
         * Initialize column resize feature.
         */
        initColResize: function () {
            // create column resizer div
            g.cRsz = document.createElement('div');
            g.cRsz.className = 'cRsz';

            // get data columns in the first row of the table
            var $firstRowCols = $(g.t).find('tr').first().find('th.draggable');

            // create column borders
            $firstRowCols.each(function () {
                var cb = document.createElement('div'); // column border
                $(cb).addClass('colborder')
                    .on('mousedown', function (e) {
                        g.dragStartRsz(e, this);
                    });
                $(g.cRsz).append(cb);
            });
            g.reposRsz();

            // attach to global div
            $(g.gDiv).prepend(g.cRsz);
        },

        /**
         * Initialize column reordering feature.
         */
        initColReorder: function () {
            g.cCpy = document.createElement('div');     // column copy, to store copy of dragged column header
            g.cPointer = document.createElement('div'); // column pointer, used when reordering column

            // adjust g.cCpy
            g.cCpy.className = 'cCpy';
            $(g.cCpy).hide();

            // adjust g.cPointer
            g.cPointer.className = 'cPointer';
            $(g.cPointer).css('visibility', 'hidden');  // set visibility to hidden instead of calling hide() to force browsers to cache the image in cPointer class

            // assign column reordering hint
            g.reorderHint = Messages.strColOrderHint;

            // get data columns in the first row of the table
            var $firstRowCols = $(g.t).find('tr').first().find('th.draggable');

            // initialize column order
            var $colOrder = $(g.o).find('.col_order');   // check if column order is passed from PHP
            var i;
            if ($colOrder.length > 0) {
                g.colOrder = $colOrder.val().split(',');
                for (i = 0; i < g.colOrder.length; i++) {
                    g.colOrder[i] = parseInt(g.colOrder[i], 10);
                }
            } else {
                g.colOrder = [];
                for (i = 0; i < $firstRowCols.length; i++) {
                    g.colOrder.push(i);
                }
            }

            // register events
            $(g.t).find('th.draggable')
                .on('mousedown', function (e) {
                    $(g.o).addClass('turnOffSelect');
                    if (g.visibleHeadersCount > 1) {
                        g.dragStartReorder(e, this);
                    }
                })
                .on('mouseenter', function () {
                    if (g.visibleHeadersCount > 1) {
                        $(this).css('cursor', 'move');
                    } else {
                        $(this).css('cursor', 'inherit');
                    }
                })
                .on('mouseleave', function () {
                    g.showReorderHint = false;
                    $(this).uiTooltip('option', {
                        content: g.updateHint()
                    });
                })
                .on('dblclick', function (e) {
                    e.preventDefault();
                    var res = Functions.copyToClipboard($(this).data('column'));
                    if (res) {
                        Functions.ajaxShowMessage(Messages.strCopyColumnSuccess, false, 'success');
                    } else {
                        Functions.ajaxShowMessage(Messages.strCopyColumnFailure, false, 'error');
                    }
                });
            $(g.t).find('th.draggable a')
                .on('dblclick', function (e) {
                    e.stopPropagation();
                });
            // restore column order when the restore button is clicked
            $(g.o).find('div.restore_column').on('click', function () {
                g.restoreColOrder();
            });

            // attach to global div
            $(g.gDiv).append(g.cPointer);
            $(g.gDiv).append(g.cCpy);

            // prevent default "dragstart" event when dragging a link
            $(g.t).find('th a').on('dragstart', function () {
                return false;
            });

            // refresh the restore column button state
            g.refreshRestoreButton();
        },

        /**
         * Initialize column visibility feature.
         */
        initColVisib: function () {
            g.cDrop = document.createElement('div');    // column drop-down arrows
            g.cList = document.createElement('div');    // column visibility list

            // adjust g.cDrop
            g.cDrop.className = 'cDrop';

            // adjust g.cList
            g.cList.className = 'cList';
            $(g.cList).hide();

            // assign column visibility related hints
            g.showAllColText = Messages.strShowAllCol;

            // get data columns in the first row of the table
            var $firstRowCols = $(g.t).find('tr').first().find('th.draggable');

            var i;
            // initialize column visibility
            var $colVisib = $(g.o).find('.col_visib');   // check if column visibility is passed from PHP
            if ($colVisib.length > 0) {
                g.colVisib = $colVisib.val().split(',');
                for (i = 0; i < g.colVisib.length; i++) {
                    g.colVisib[i] = parseInt(g.colVisib[i], 10);
                }
            } else {
                g.colVisib = [];
                for (i = 0; i < $firstRowCols.length; i++) {
                    g.colVisib.push(1);
                }
            }

            // make sure we have more than one column
            if ($firstRowCols.length > 1) {
                var $colVisibTh = $(g.t).find('th:not(.draggable)').slice(0, 1);
                Functions.tooltip(
                    $colVisibTh,
                    'th',
                    Messages.strColVisibHint
                );

                // create column visibility drop-down arrow(s)
                $colVisibTh.each(function () {
                    var cd = document.createElement('div'); // column drop-down arrow
                    $(cd).addClass('coldrop')
                        .on('click', function () {
                            if (g.cList.style.display === 'none') {
                                g.showColList(this);
                            } else {
                                g.hideColList();
                            }
                        });
                    $(g.cDrop).append(cd);
                });

                // add column visibility control
                g.cList.innerHTML = '<div class="lDiv"></div>';
                var $listDiv = $(g.cList).find('div');

                var tempClick = function () {
                    if (g.toggleCol($(this).index())) {
                        g.afterToggleCol();
                    }
                };

                for (i = 0; i < $firstRowCols.length; i++) {
                    var currHeader = $firstRowCols[i];
                    var listElmt = document.createElement('div');
                    $(listElmt).text($(currHeader).text())
                        .prepend('<input type="checkbox" ' + (g.colVisib[i] ? 'checked="checked" ' : '') + '>');
                    $listDiv.append(listElmt);
                    // add event on click
                    $(listElmt).on('click', tempClick);
                }
                // add "show all column" button
                var showAll = document.createElement('div');
                $(showAll).addClass('showAllColBtn')
                    .text(g.showAllColText);
                $(g.cList).append(showAll);
                $(showAll).on('click', function () {
                    g.showAllColumns();
                });
                // prepend "show all column" button at top if the list is too long
                if ($firstRowCols.length > 10) {
                    var clone = showAll.cloneNode(true);
                    $(g.cList).prepend(clone);
                    $(clone).on('click', function () {
                        g.showAllColumns();
                    });
                }
            }

            // hide column visibility list if we move outside the list
            $(g.t).find('td, th.draggable').on('mouseenter', function () {
                g.hideColList();
            });

            // attach to first row first col of the grid
            var thFirst = $(g.t).find('th.d-print-none');
            $(thFirst).append(g.cDrop);
            $(thFirst).append(g.cList);

            // some adjustment
            g.reposDrop();
        },

        /**
         * Move currently Editing Cell to Up
         *
         * @param e
         *
         */
        moveUp: function (e) {
            e.preventDefault();
            var $thisField = $(g.currentEditCell);
            var fieldName = Sql.getFieldName($(g.t), $thisField);

            var whereClause = $thisField.parents('tr').first().find('.where_clause').val();
            if (typeof whereClause === 'undefined') {
                whereClause = '';
            }
            var found = false;
            var $prevRow;

            $thisField.parents('tr').first().parents('tbody').children().each(function () {
                if ($(this).find('.where_clause').val() === whereClause) {
                    found = true;
                }
                if (!found) {
                    $prevRow = $(this);
                }
            });

            var newCell;

            if (found && $prevRow) {
                $prevRow.children('td').each(function () {
                    if (Sql.getFieldName($(g.t), $(this)) === fieldName) {
                        newCell = this;
                    }
                });
            }

            if (newCell) {
                g.hideEditCell(false, false, false, { move : true, cell : newCell });
            }
        },

        /**
         * Move currently Editing Cell to Down
         *
         * @param e
         *
         */
        moveDown: function (e) {
            e.preventDefault();

            var $thisField = $(g.currentEditCell);
            var fieldName = Sql.getFieldName($(g.t), $thisField);

            var whereClause = $thisField.parents('tr').first().find('.where_clause').val();
            if (typeof whereClause === 'undefined') {
                whereClause = '';
            }
            var found = false;
            var $nextRow;
            var j = 0;
            var nextRowFound = false;
            $thisField.parents('tr').first().parents('tbody').children().each(function () {
                if ($(this).find('.where_clause').val() === whereClause) {
                    found = true;
                }
                if (found) {
                    if (j >= 1 && ! nextRowFound) {
                        $nextRow = $(this);
                        nextRowFound = true;
                    } else {
                        j++;
                    }
                }
            });

            var newCell;
            if (found && $nextRow) {
                $nextRow.children('td').each(function () {
                    if (Sql.getFieldName($(g.t), $(this)) === fieldName) {
                        newCell = this;
                    }
                });
            }

            if (newCell) {
                g.hideEditCell(false, false, false, { move : true, cell : newCell });
            }
        },

        /**
         * Move currently Editing Cell to Left
         *
         * @param e
         *
         */
        moveLeft: function (e) {
            e.preventDefault();

            var $thisField = $(g.currentEditCell);
            var fieldName = Sql.getFieldName($(g.t), $thisField);

            var whereClause = $thisField.parents('tr').first().find('.where_clause').val();
            if (typeof whereClause === 'undefined') {
                whereClause = '';
            }
            var found = false;
            var $foundRow;
            $thisField.parents('tr').first().parents('tbody').children().each(function () {
                if ($(this).find('.where_clause').val() === whereClause) {
                    found = true;
                    $foundRow = $(this);
                }
            });

            var leftCell;
            var cellFound = false;
            if (found) {
                $foundRow.children('td.grid_edit').each(function () {
                    if (Sql.getFieldName($(g.t), $(this)) === fieldName) {
                        cellFound = true;
                    }
                    if (!cellFound) {
                        leftCell = this;
                    }
                });
            }

            if (leftCell) {
                g.hideEditCell(false, false, false, { move : true, cell : leftCell });
            }
        },

        /**
         * Move currently Editing Cell to Right
         *
         * @param e
         *
         */
        moveRight: function (e) {
            e.preventDefault();

            var $thisField = $(g.currentEditCell);
            var fieldName = Sql.getFieldName($(g.t), $thisField);

            var whereClause = $thisField.parents('tr').first().find('.where_clause').val();
            if (typeof whereClause === 'undefined') {
                whereClause = '';
            }
            var found = false;
            var $foundRow;
            var j = 0;
            $thisField.parents('tr').first().parents('tbody').children().each(function () {
                if ($(this).find('.where_clause').val() === whereClause) {
                    found = true;
                    $foundRow = $(this);
                }
            });

            var rightCell;
            var cellFound = false;
            var nextCellFound = false;
            if (found) {
                $foundRow.children('td.grid_edit').each(function () {
                    if (Sql.getFieldName($(g.t), $(this)) === fieldName) {
                        cellFound = true;
                    }
                    if (cellFound) {
                        if (j >= 1 && ! nextCellFound) {
                            rightCell = this;
                            nextCellFound = true;
                        } else {
                            j++;
                        }
                    }
                });
            }

            if (rightCell) {
                g.hideEditCell(false, false, false, { move : true, cell : rightCell });
            }
        },

        /**
         * Initialize grid editing feature.
         */
        initGridEdit: function () {
            function startGridEditing (e, cell) {
                if (g.isCellEditActive) {
                    g.saveOrPostEditedCell();
                } else {
                    g.showEditCell(cell);
                }
                e.stopPropagation();
            }

            function handleCtrlNavigation (e) {
                if ((e.ctrlKey && e.which === 38) || (e.altKey && e.which === 38)) {
                    g.moveUp(e);
                } else if ((e.ctrlKey && e.which === 40)  || (e.altKey && e.which === 40)) {
                    g.moveDown(e);
                } else if ((e.ctrlKey && e.which === 37) || (e.altKey && e.which === 37)) {
                    g.moveLeft(e);
                } else if ((e.ctrlKey && e.which === 39)  || (e.altKey && e.which === 39)) {
                    g.moveRight(e);
                }
            }

            // create cell edit wrapper element
            g.cEditStd = document.createElement('div');
            g.cEdit = g.cEditStd;
            g.cEditTextarea = document.createElement('div');

            // adjust g.cEditStd
            g.cEditStd.className = 'cEdit';
            $(g.cEditStd).html('<input class="edit_box" rows="1"><div class="edit_area"></div>');
            $(g.cEditStd).hide();

            // adjust g.cEdit
            g.cEditTextarea.className = 'cEdit';
            $(g.cEditTextarea).html('<textarea class="edit_box" rows="1"></textarea><div class="edit_area"></div>');
            $(g.cEditTextarea).hide();

            // assign cell editing hint
            g.cellEditHint = Messages.strCellEditHint;
            g.saveCellWarning = Messages.strSaveCellWarning;
            g.alertNonUnique = Messages.strAlertNonUnique;
            g.gotoLinkText = Messages.strGoToLink;

            // initialize cell editing configuration
            g.saveCellsAtOnce = $(g.o).find('.save_cells_at_once').val();
            g.maxTruncatedLen = CommonParams.get('LimitChars');

            // register events
            $(g.t).find('td.data.click1')
                .on('click', function (e) {
                    startGridEditing(e, this);
                    // prevent default action when clicking on "link" in a table
                    if ($(e.target).is('.grid_edit a')) {
                        e.preventDefault();
                    }
                });

            $(g.t).find('td.data.click2')
                .on('click', function (e) {
                    var $cell = $(this);
                    // In the case of relational link, We want single click on the link
                    // to goto the link and double click to start grid-editing.
                    var $link = $(e.target);
                    if ($link.is('.grid_edit.relation a')) {
                        e.preventDefault();
                        // get the click count and increase
                        var clicks = $cell.data('clicks');
                        clicks = (typeof clicks === 'undefined') ? 1 : clicks + 1;

                        if (clicks === 1) {
                            // if there are no previous clicks,
                            // start the single click timer
                            var timer = setTimeout(function () {
                                // temporarily remove ajax class so the page loader will not handle it,
                                // submit and then add it back
                                $link.removeClass('ajax');
                                AJAX.requestHandler.call($link[0]);
                                $link.addClass('ajax');
                                $cell.data('clicks', 0);
                            }, 700);
                            $cell.data('clicks', clicks);
                            $cell.data('timer', timer);
                        } else {// When double clicking a link, switch to edit mode
                            // this is a double click, cancel the single click timer
                            // and make the click count 0
                            clearTimeout($cell.data('timer'));
                            $cell.data('clicks', 0);
                            // start grid-editing
                            startGridEditing(e, this);
                        }
                    }
                })
                .on('dblclick', function (e) {
                    if ($(e.target).is('.grid_edit a')) {
                        e.preventDefault();
                    } else {
                        startGridEditing(e, this);
                    }
                });

            $(g.cEditStd).on('keydown', 'input.edit_box, select', handleCtrlNavigation);

            $(g.cEditStd).find('.edit_box').on('focus', function () {
                g.showEditArea();
            });
            $(g.cEditStd).on('keydown', '.edit_box, select', function (e) {
                if (e.which === 13) {
                    // post on pressing "Enter"
                    e.preventDefault();
                    g.saveOrPostEditedCell();
                }
            });
            $(g.cEditStd).on('keydown', function (e) {
                if (!g.isEditCellTextEditable) {
                    // prevent text editing
                    e.preventDefault();
                }
            });

            $(g.cEditTextarea).on('keydown', 'textarea.edit_box, select', handleCtrlNavigation);

            $(g.cEditTextarea).find('.edit_box').on('focus', function () {
                g.showEditArea();
            });
            $(g.cEditTextarea).on('keydown', '.edit_box, select', function (e) {
                if (e.which === 13 && !e.shiftKey) {
                    // post on pressing "Enter"
                    e.preventDefault();
                    g.saveOrPostEditedCell();
                }
            });
            $(g.cEditTextarea).on('keydown', function (e) {
                if (!g.isEditCellTextEditable) {
                    // prevent text editing
                    e.preventDefault();
                }
            });
            $('html').on('click', function (e) {
                // hide edit cell if the click is not fromDat edit area
                if ($(e.target).parents().index($(g.cEdit)) === -1 &&
                    !$(e.target).parents('.ui-datepicker-header').length &&
                    !$('.browse_foreign_modal.ui-dialog:visible').length &&
                    !$(e.target).closest('.dismissable').length
                ) {
                    g.hideEditCell();
                }
            }).on('keydown', function (e) {
                if (e.which === 27 && g.isCellEditActive) {
                    // cancel on pressing "Esc"
                    g.hideEditCell(true);
                }
            });
            $(g.o).find('div.save_edited').on('click', function () {
                g.hideEditCell();
                g.postEditedCell();
            });
            $(window).on('beforeunload', function () {
                if (g.isCellEdited) {
                    return g.saveCellWarning;
                }
            });

            // attach to global div
            $(g.gDiv).append(g.cEditStd);
            $(g.gDiv).append(g.cEditTextarea);

            // add hint for grid editing feature when hovering "Edit" link in each table row
            if (Messages.strGridEditFeatureHint !== undefined) {
                Functions.tooltip(
                    $(g.t).find('.edit_row_anchor a'),
                    'a',
                    Messages.strGridEditFeatureHint
                );
            }
        }
    };

    /** ****************
     * Initialize grid
     ******************/

    // wrap all truncated data cells with span indicating the original length
    // todo update the original length after a grid edit
    $(t).find('td.data.truncated:not(:has(span))')
        .wrapInner(function () {
            return '<span title="' + Messages.strOriginalLength + ' ' +
                $(this).data('originallength') + '"></span>';
        });

    // wrap remaining cells, except actions cell, with span
    $(t).find('th, td:not(:has(span))')
        .wrapInner('<span></span>');

    // create grid elements
    g.gDiv = document.createElement('div');     // create global div

    // initialize the table variable
    g.t = t;

    // enclosing .sqlqueryresults div
    g.o = $(t).parents('.sqlqueryresults');

    // get data columns in the first row of the table
    var $firstRowCols = $(t).find('tr').first().find('th.draggable');

    // initialize visible headers count
    g.visibleHeadersCount = $firstRowCols.filter(':visible').length;

    // assign first column (actions) span
    if (! $(t).find('tr').first().find('th').first().hasClass('draggable')) {  // action header exist
        g.actionSpan = $(t).find('tr').first().find('th').first().prop('colspan');
    } else {
        g.actionSpan = 0;
    }

    // assign table create time
    // table_create_time will only available if we are in "Browse" tab
    g.tableCreateTime = $(g.o).find('.table_create_time').val();

    // assign the hints
    g.sortHint = Messages.strSortHint;
    g.strMultiSortHint = Messages.strMultiSortHint;
    g.markHint = Messages.strColMarkHint;
    g.copyHint = Messages.strColNameCopyHint;

    // assign common hidden inputs
    var $commonHiddenInputs = $(g.o).find('div.common_hidden_inputs');
    g.server = $commonHiddenInputs.find('input[name=server]').val();
    g.db = $commonHiddenInputs.find('input[name=db]').val();
    g.table = $commonHiddenInputs.find('input[name=table]').val();

    // add table class
    $(t).addClass('pma_table');

    // add relative position to global div so that resize handlers are correctly positioned
    $(g.gDiv).css('position', 'relative');

    // link the global div
    $(t).before(g.gDiv);
    $(g.gDiv).append(t);

    // FEATURES
    if (isResizeEnabled) {
        g.initColResize();
    }
    // disable reordering for result from EXPLAIN or SHOW syntax, which do not have a table navigation panel
    if (isReorderEnabled &&
        $(g.o).find('table.navigation').length > 0) {
        g.initColReorder();
    }
    if (isVisibEnabled) {
        g.initColVisib();
    }
    // make sure we have the ajax class
    if (isGridEditEnabled &&
        $(t).is('.ajax')) {
        g.initGridEdit();
    }

    // create tooltip for each <th> with draggable class
    Functions.tooltip(
        $(t).find('th.draggable'),
        'th',
        g.updateHint()
    );

    // register events for hint tooltip (anchors inside draggable th)
    $(t).find('th.draggable a')
        .on('mouseenter', function () {
            g.showSortHint = true;
            g.showMultiSortHint = true;
            $(t).find('th.draggable').uiTooltip('option', {
                content: g.updateHint()
            });
        })
        .on('mouseleave', function () {
            g.showSortHint = false;
            g.showMultiSortHint = false;
            $(t).find('th.draggable').uiTooltip('option', {
                content: g.updateHint()
            });
        });

    // register events for dragging-related feature
    if (isResizeEnabled || isReorderEnabled) {
        $(document).on('mousemove', function (e) {
            g.dragMove(e);
        });
        $(document).on('mouseup', function (e) {
            $(g.o).removeClass('turnOffSelect');
            g.dragEnd(e);
        });
    }

    // some adjustment
    $(t).removeClass('data');
    $(g.gDiv).addClass('data');
};

/**
 * jQuery plugin to cancel selection in HTML code.
 */
(function ($) {
    $.fn.noSelect = function (p) { // no select plugin by Paulo P.Marinas
        var prevent = (p === null) ? true : p;
        /* eslint-disable compat/compat */
        var isMsie = navigator.userAgent.indexOf('MSIE') > -1 || !!window.navigator.userAgent.match(/Trident.*rv:11\./);
        var isFirefox = navigator.userAgent.indexOf('Firefox') > -1;
        var isSafari = navigator.userAgent.indexOf('Safari') > -1;
        var isOpera = navigator.userAgent.indexOf('Presto') > -1;
        /* eslint-enable compat/compat */
        if (prevent) {
            return this.each(function () {
                if (isMsie || isSafari) {
                    $(this).on('selectstart', false);
                } else if (isFirefox) {
                    $(this).css('MozUserSelect', 'none');
                    $('body').trigger('focus');
                } else if (isOpera) {
                    $(this).on('mousedown', false);
                } else {
                    $(this).attr('unselectable', 'on');
                }
            });
        } else {
            return this.each(function () {
                if (isMsie || isSafari) {
                    $(this).off('selectstart');
                } else if (isFirefox) {
                    $(this).css('MozUserSelect', 'inherit');
                } else if (isOpera) {
                    $(this).off('mousedown');
                } else {
                    $(this).removeAttr('unselectable');
                }
            });
        }
    }; // end noSelect
}(jQuery));
¿Qué es la limpieza dental de perros? - Clínica veterinaria


Es la eliminación del sarro y la placa adherida a la superficie de los dientes mediante un equipo de ultrasonidos que garantiza la integridad de las piezas dentales a la vez que elimina en profundidad cualquier resto de suciedad.

A continuación se procede al pulido de los dientes mediante una fresa especial que elimina la placa bacteriana y devuelve a los dientes el aspecto sano que deben tener.

Una vez terminado todo el proceso, se mantiene al perro en observación hasta que se despierta de la anestesia, bajo la atenta supervisión de un veterinario.

¿Cada cuánto tiempo tengo que hacerle una limpieza dental a mi perro?

A partir de cierta edad, los perros pueden necesitar una limpieza dental anual o bianual. Depende de cada caso. En líneas generales, puede decirse que los perros de razas pequeñas suelen acumular más sarro y suelen necesitar una atención mayor en cuanto a higiene dental.


Riesgos de una mala higiene


Los riesgos más evidentes de una mala higiene dental en los perros son los siguientes:

  • Cuando la acumulación de sarro no se trata, se puede producir una inflamación y retracción de las encías que puede descalzar el diente y provocar caídas.
  • Mal aliento (halitosis).
  • Sarro perros
  • Puede ir a más
  • Las bacterias de la placa pueden trasladarse a través del torrente circulatorio a órganos vitales como el corazón ocasionando problemas de endocarditis en las válvulas. Las bacterias pueden incluso acantonarse en huesos (La osteomielitis es la infección ósea, tanto cortical como medular) provocando mucho dolor y una artritis séptica).

¿Cómo se forma el sarro?

El sarro es la calcificación de la placa dental. Los restos de alimentos, junto con las bacterias presentes en la boca, van a formar la placa bacteriana o placa dental. Si la placa no se retira, al mezclarse con la saliva y los minerales presentes en ella, reaccionará formando una costra. La placa se calcifica y se forma el sarro.

El sarro, cuando se forma, es de color blanquecino pero a medida que pasa el tiempo se va poniendo amarillo y luego marrón.

Síntomas de una pobre higiene dental
La señal más obvia de una mala salud dental canina es el mal aliento.

Sin embargo, a veces no es tan fácil de detectar
Y hay perros que no se dejan abrir la boca por su dueño. Por ejemplo…

Recientemente nos trajeron a la clínica a un perro que parpadeaba de un ojo y decía su dueño que le picaba un lado de la cara. Tenía molestias y dificultad para comer, lo que había llevado a sus dueños a comprarle comida blanda (que suele ser un poco más cara y llevar más contenido en grasa) durante medio año. Después de una exploración oftalmológica, nos dimos cuenta de que el ojo tenía una úlcera en la córnea probablemente de rascarse . Además, el canto lateral del ojo estaba inflamado. Tenía lo que en humanos llamamos flemón pero como era un perro de pelo largo, no se le notaba a simple vista. Al abrirle la boca nos llamó la atención el ver una muela llena de sarro. Le realizamos una radiografía y encontramos una fístula que llegaba hasta la parte inferior del ojo.

Le tuvimos que extraer la muela. Tras esto, el ojo se curó completamente con unos colirios y una lentilla protectora de úlcera. Afortunadamente, la úlcera no profundizó y no perforó el ojo. Ahora el perro come perfectamente a pesar de haber perdido una muela.

¿Cómo mantener la higiene dental de tu perro?
Hay varias maneras de prevenir problemas derivados de la salud dental de tu perro.

Limpiezas de dientes en casa
Es recomendable limpiar los dientes de tu perro semanal o diariamente si se puede. Existe una gran variedad de productos que se pueden utilizar:

Pastas de dientes.
Cepillos de dientes o dedales para el dedo índice, que hacen más fácil la limpieza.
Colutorios para echar en agua de bebida o directamente sobre el diente en líquido o en spray.

En la Clínica Tus Veterinarios enseñamos a nuestros clientes a tomar el hábito de limpiar los dientes de sus perros desde que son cachorros. Esto responde a nuestro compromiso con la prevención de enfermedades caninas.

Hoy en día tenemos muchos clientes que limpian los dientes todos los días a su mascota, y como resultado, se ahorran el dinero de hacer limpiezas dentales profesionales y consiguen una mejor salud de su perro.


Limpiezas dentales profesionales de perros y gatos

Recomendamos hacer una limpieza dental especializada anualmente. La realizamos con un aparato de ultrasonidos que utiliza agua para quitar el sarro. Después, procedemos a pulir los dientes con un cepillo de alta velocidad y una pasta especial. Hacemos esto para proteger el esmalte.

La frecuencia de limpiezas dentales necesaria varía mucho entre razas. En general, las razas grandes tienen buena calidad de esmalte, por lo que no necesitan hacerlo tan a menudo e incluso pueden pasarse la vida sin requerir una limpieza. Sin embargo, razas pequeñas como el Yorkshire o el Maltés, deben hacérselas todos los años desde cachorros si se quiere conservar sus piezas dentales.

Otro factor fundamental es la calidad del pienso. Algunas marcas han diseñado croquetas que limpian la superficie del diente y de la muela al masticarse.

Ultrasonido para perros

¿Se necesita anestesia para las limpiezas dentales de perros y gatos?

La limpieza dental en perros no es una técnica que pueda practicarse sin anestesia general , aunque hay veces que los propietarios no quieren anestesiar y si tiene poco sarro y el perro es muy bueno se puede intentar…… , pero no se va a poder pulir ni acceder a todas la zona de la boca …. Además los limpiadores dentales van a irrigar agua y hay riesgo de aspiración a vías respiratorias si no se realiza una anestesia correcta con intubación traqueal . En resumen , sin anestesia no se va hacer una correcta limpieza dental.

Tampoco sirve la sedación ya que necesitamos que el animal esté totalmente quieto, y el veterinario tenga un acceso completo a todas sus piezas dentales y encías.

Alimentos para la limpieza dental

Hay que tener cierto cuidado a la hora de comprar determinados alimentos porque no todos son saludables. Algunos tienen demasiado contenido graso, que en exceso puede causar problemas cardiovasculares y obesidad.

Los mejores alimentos para los dientes son aquellos que están elaborados por empresas farmacéuticas y llevan componentes químicos con tratamientos específicos para el diente del perro. Esto implica no solo limpieza a través de la acción mecánica de morder sino también un tratamiento antibacteriano para prevenir el sarro.

Conclusión

Si eres como la mayoría de dueños, por falta de tiempo , es probable que no estés prestando la suficiente atención a la limpieza dental de tu perro. Por eso te animamos a que comiences a limpiar los dientes de tu perro y consideres atender a su higiene bucal con frecuencia.

Estas simples medidas pueden conllevar a que tu perro tenga una vida más larga y mucho más saludable.

Si te resulta imposible introducir un cepillo de dientes a tu perro en la boca, pásate con él por clínica Tus Veterinarios y te explicamos cómo hacerlo.

Necesitas hacer una limpieza dental profesional a tu mascota?
Llámanos al 622575274 o contacta con nosotros

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

¡Hola!