Translated using Weblate (Bulgarian)
[phpmyadmin.git] / js / makegrid.js
blob25b6e615565e9c0a4a0fbce750ad6d75db6f0731
1 /* vim: set expandtab sw=4 ts=4 sts=4: */
2 /**
3  * Create advanced table (resize, reorder, and show/hide columns; and also grid editing).
4  * This function is designed mainly for table DOM generated from browsing a table in the database.
5  * For using this function in other table DOM, you may need to:
6  * - add "draggable" class in the table header <th>, in order to make it resizable, sortable or hidable
7  * - have at least one non-"draggable" header in the table DOM for placing column visibility drop-down arrow
8  * - pass the value "false" for the parameter "enableGridEdit"
9  * - adjust other parameter value, to select which features that will be enabled
10  *
11  * @param t the table DOM element
12  * @param enableResize Optional, if false, column resizing feature will be disabled
13  * @param enableReorder Optional, if false, column reordering feature will be disabled
14  * @param enableVisib Optional, if false, show/hide column feature will be disabled
15  * @param enableGridEdit Optional, if false, grid editing feature will be disabled
16  */
17 // eslint-disable-next-line no-unused-vars
18 var makeGrid = function (t, enableResize, enableReorder, enableVisib, enableGridEdit) {
19     var isResizeEnabled = enableResize === undefined ? true : enableResize;
20     var isReorderEnabled = enableReorder === undefined ? true : enableReorder;
21     var isVisibEnabled = enableVisib === undefined ? true : enableVisib;
22     var isGridEditEnabled = enableGridEdit === undefined ? true : enableGridEdit;
24     var g = {
25         /** *********
26          * Constant
27          ***********/
28         minColWidth: 15,
31         /** *********
32          * Variables, assigned with default value, changed later
33          ***********/
34         actionSpan: 5,              // number of colspan in Actions header in a table
35         tableCreateTime: null,      // table creation time, used for saving column order and visibility to server, only available in "Browse tab"
37         // Column reordering variables
38         colOrder: [],      // array of column order
40         // Column visibility variables
41         colVisib: [],      // array of column visibility
42         showAllColText: '',         // string, text for "show all" button under column visibility list
43         visibleHeadersCount: 0,     // number of visible data headers
45         // Table hint variables
46         reorderHint: '',            // string, hint for column reordering
47         sortHint: '',               // string, hint for column sorting
48         markHint: '',               // string, hint for column marking
49         copyHint: '',               // string, hint for copy column name
50         showReorderHint: false,
51         showSortHint: false,
52         showMarkHint: false,
54         // Grid editing
55         isCellEditActive: false,    // true if current focus is in edit cell
56         isEditCellTextEditable: false,  // true if current edit cell is editable in the text input box (not textarea)
57         currentEditCell: null,      // reference to <td> that currently being edited
58         cellEditHint: '',           // hint shown when doing grid edit
59         gotoLinkText: '',           // "Go to link" text
60         wasEditedCellNull: false,   // true if last value of the edited cell was NULL
61         maxTruncatedLen: 0,         // number of characters that can be displayed in a cell
62         saveCellsAtOnce: false,     // $cfg[saveCellsAtOnce]
63         isCellEdited: false,        // true if at least one cell has been edited
64         saveCellWarning: '',        // string, warning text when user want to leave a page with unsaved edited data
65         lastXHR : null,             // last XHR object used in AJAX request
66         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
67         alertNonUnique: '',         // string, alert shown when saving edited nonunique table
69         // Common hidden inputs
70         token: null,
71         server: null,
72         db: null,
73         table: null,
76         /** **********
77          * Functions
78          ************/
80         /**
81          * Start to resize column. Called when clicking on column separator.
82          *
83          * @param e event
84          * @param obj dragged div object
85          */
86         dragStartRsz: function (e, obj) {
87             var n = $(g.cRsz).find('div').index(obj);    // get the index of separator (i.e., column index)
88             $(obj).addClass('colborder_active');
89             g.colRsz = {
90                 x0: e.pageX,
91                 n: n,
92                 obj: obj,
93                 objLeft: $(obj).position().left,
94                 objWidth: $(g.t).find('th.draggable:visible:eq(' + n + ') span').outerWidth()
95             };
96             $(document.body).css('cursor', 'col-resize').noSelect();
97             if (g.isCellEditActive) {
98                 g.hideEditCell();
99             }
100         },
102         /**
103          * Start to reorder column. Called when clicking on table header.
104          *
105          * @param e event
106          * @param obj table header object
107          */
108         dragStartReorder: function (e, obj) {
109             // prepare the cCpy (column copy) and cPointer (column pointer) from the dragged column
110             $(g.cCpy).text($(obj).text());
111             var objPos = $(obj).position();
112             $(g.cCpy).css({
113                 top: objPos.top + 20,
114                 left: objPos.left,
115                 height: $(obj).height(),
116                 width: $(obj).width()
117             });
118             $(g.cPointer).css({
119                 top: objPos.top
120             });
122             // get the column index, zero-based
123             var n = g.getHeaderIdx(obj);
125             g.colReorder = {
126                 x0: e.pageX,
127                 y0: e.pageY,
128                 n: n,
129                 newn: n,
130                 obj: obj,
131                 objTop: objPos.top,
132                 objLeft: objPos.left
133             };
135             $(document.body).css('cursor', 'move').noSelect();
136             if (g.isCellEditActive) {
137                 g.hideEditCell();
138             }
139         },
141         /**
142          * Handle mousemove event when dragging.
143          *
144          * @param e event
145          */
146         dragMove: function (e) {
147             var dx;
148             if (g.colRsz) {
149                 dx = e.pageX - g.colRsz.x0;
150                 if (g.colRsz.objWidth + dx > g.minColWidth) {
151                     $(g.colRsz.obj).css('left', g.colRsz.objLeft + dx + 'px');
152                 }
153             } else if (g.colReorder) {
154                 // dragged column animation
155                 dx = e.pageX - g.colReorder.x0;
156                 $(g.cCpy)
157                     .css('left', g.colReorder.objLeft + dx)
158                     .show();
160                 // pointer animation
161                 var hoveredCol = g.getHoveredCol(e);
162                 if (hoveredCol) {
163                     var newn = g.getHeaderIdx(hoveredCol);
164                     g.colReorder.newn = newn;
165                     if (newn !== g.colReorder.n) {
166                         // show the column pointer in the right place
167                         var colPos = $(hoveredCol).position();
168                         var newleft = newn < g.colReorder.n ?
169                             colPos.left :
170                             colPos.left + $(hoveredCol).outerWidth();
171                         $(g.cPointer)
172                             .css({
173                                 left: newleft,
174                                 visibility: 'visible'
175                             });
176                     } else {
177                         // no movement to other column, hide the column pointer
178                         $(g.cPointer).css('visibility', 'hidden');
179                     }
180                 }
181             }
182         },
184         /**
185          * Stop the dragging action.
186          *
187          * @param e event
188          */
189         dragEnd: function (e) {
190             if (g.colRsz) {
191                 var dx = e.pageX - g.colRsz.x0;
192                 var nw = g.colRsz.objWidth + dx;
193                 if (nw < g.minColWidth) {
194                     nw = g.minColWidth;
195                 }
196                 var n = g.colRsz.n;
197                 // do the resizing
198                 g.resize(n, nw);
200                 g.reposRsz();
201                 g.reposDrop();
202                 g.colRsz = false;
203                 $(g.cRsz).find('div').removeClass('colborder_active');
204                 Sql.rearrangeStickyColumns($(t).prev('.sticky_columns'), $(t));
205             } else if (g.colReorder) {
206                 // shift columns
207                 if (g.colReorder.newn !== g.colReorder.n) {
208                     g.shiftCol(g.colReorder.n, g.colReorder.newn);
209                     // assign new position
210                     var objPos = $(g.colReorder.obj).position();
211                     g.colReorder.objTop = objPos.top;
212                     g.colReorder.objLeft = objPos.left;
213                     g.colReorder.n = g.colReorder.newn;
214                     // send request to server to remember the column order
215                     if (g.tableCreateTime) {
216                         g.sendColPrefs();
217                     }
218                     g.refreshRestoreButton();
219                 }
221                 // animate new column position
222                 $(g.cCpy).stop(true, true)
223                     .animate({
224                         top: g.colReorder.objTop,
225                         left: g.colReorder.objLeft
226                     }, 'fast')
227                     .fadeOut();
228                 $(g.cPointer).css('visibility', 'hidden');
230                 g.colReorder = false;
231                 Sql.rearrangeStickyColumns($(t).prev('.sticky_columns'), $(t));
232             }
233             $(document.body).css('cursor', 'inherit').noSelect(false);
234         },
236         /**
237          * Resize column n to new width "nw"
238          *
239          * @param n zero-based column index
240          * @param nw new width of the column in pixel
241          */
242         resize: function (n, nw) {
243             $(g.t).find('tr').each(function () {
244                 $(this).find('th.draggable:visible:eq(' + n + ') span,' +
245                              'td:visible:eq(' + (g.actionSpan + n) + ') span')
246                     .css('width', nw);
247             });
248         },
250         /**
251          * Reposition column resize bars.
252          */
253         reposRsz: function () {
254             $(g.cRsz).find('div').hide();
255             var $firstRowCols = $(g.t).find('tr:first th.draggable:visible');
256             var $resizeHandles = $(g.cRsz).find('div').removeClass('condition');
257             $(g.t).find('table.pma_table').find('thead th:first').removeClass('before-condition');
258             for (var n = 0, l = $firstRowCols.length; n < l; n++) {
259                 var $col = $($firstRowCols[n]);
260                 var colWidth;
261                 if (navigator.userAgent.toLowerCase().indexOf('safari') !== -1) {
262                     colWidth = $col.outerWidth();
263                 } else {
264                     colWidth = $col.outerWidth(true);
265                 }
266                 $($resizeHandles[n]).css('left', $col.position().left + colWidth)
267                     .show();
268                 if ($col.hasClass('condition')) {
269                     $($resizeHandles[n]).addClass('condition');
270                     if (n > 0) {
271                         $($resizeHandles[n - 1]).addClass('condition');
272                     }
273                 }
274             }
275             if ($($resizeHandles[0]).hasClass('condition')) {
276                 $(g.t).find('thead th:first').addClass('before-condition');
277             }
278             $(g.cRsz).css('height', $(g.t).height());
279         },
281         /**
282          * Shift column from index oldn to newn.
283          *
284          * @param oldn old zero-based column index
285          * @param newn new zero-based column index
286          */
287         shiftCol: function (oldn, newn) {
288             $(g.t).find('tr').each(function () {
289                 if (newn < oldn) {
290                     $(this).find('th.draggable:eq(' + newn + '),' +
291                                  'td:eq(' + (g.actionSpan + newn) + ')')
292                         .before($(this).find('th.draggable:eq(' + oldn + '),' +
293                                                 'td:eq(' + (g.actionSpan + oldn) + ')'));
294                 } else {
295                     $(this).find('th.draggable:eq(' + newn + '),' +
296                                  'td:eq(' + (g.actionSpan + newn) + ')')
297                         .after($(this).find('th.draggable:eq(' + oldn + '),' +
298                                                'td:eq(' + (g.actionSpan + oldn) + ')'));
299                 }
300             });
301             // reposition the column resize bars
302             g.reposRsz();
304             // adjust the column visibility list
305             if (newn < oldn) {
306                 $(g.cList).find('.lDiv div:eq(' + newn + ')')
307                     .before($(g.cList).find('.lDiv div:eq(' + oldn + ')'));
308             } else {
309                 $(g.cList).find('.lDiv div:eq(' + newn + ')')
310                     .after($(g.cList).find('.lDiv div:eq(' + oldn + ')'));
311             }
312             // adjust the colOrder
313             var tmp = g.colOrder[oldn];
314             g.colOrder.splice(oldn, 1);
315             g.colOrder.splice(newn, 0, tmp);
316             // adjust the colVisib
317             if (g.colVisib.length > 0) {
318                 tmp = g.colVisib[oldn];
319                 g.colVisib.splice(oldn, 1);
320                 g.colVisib.splice(newn, 0, tmp);
321             }
322         },
324         /**
325          * Find currently hovered table column's header (excluding actions column).
326          *
327          * @param e event
328          * @return the hovered column's th object or undefined if no hovered column found.
329          */
330         getHoveredCol: function (e) {
331             var hoveredCol;
332             var $headers = $(g.t).find('th.draggable:visible');
333             $headers.each(function () {
334                 var left = $(this).offset().left;
335                 var right = left + $(this).outerWidth();
336                 if (left <= e.pageX && e.pageX <= right) {
337                     hoveredCol = this;
338                 }
339             });
340             return hoveredCol;
341         },
343         /**
344          * Get a zero-based index from a <th class="draggable"> tag in a table.
345          *
346          * @param obj table header <th> object
347          * @return zero-based index of the specified table header in the set of table headers (visible or not)
348          */
349         getHeaderIdx: function (obj) {
350             return $(obj).parents('tr').find('th.draggable').index(obj);
351         },
353         /**
354          * Reposition the columns back to normal order.
355          */
356         restoreColOrder: function () {
357             // use insertion sort, since we already have shiftCol function
358             for (var i = 1; i < g.colOrder.length; i++) {
359                 var x = g.colOrder[i];
360                 var j = i - 1;
361                 while (j >= 0 && x < g.colOrder[j]) {
362                     j--;
363                 }
364                 if (j !== i - 1) {
365                     g.shiftCol(i, j + 1);
366                 }
367             }
368             if (g.tableCreateTime) {
369                 // send request to server to remember the column order
370                 g.sendColPrefs();
371             }
372             g.refreshRestoreButton();
373         },
375         /**
376          * Send column preferences (column order and visibility) to the server.
377          */
378         sendColPrefs: function () {
379             if ($(g.t).is('.ajax')) {   // only send preferences if ajax class
380                 var postParams = {
381                     'ajax_request': true,
382                     'db': g.db,
383                     'table': g.table,
384                     'token': g.token,
385                     'server': g.server,
386                     'set_col_prefs': true,
387                     'table_create_time': g.tableCreateTime
388                 };
389                 if (g.colOrder.length > 0) {
390                     $.extend(postParams, { 'col_order': g.colOrder.toString() });
391                 }
392                 if (g.colVisib.length > 0) {
393                     $.extend(postParams, { 'col_visib': g.colVisib.toString() });
394                 }
395                 $.post('sql.php', postParams, function (data) {
396                     if (data.success !== true) {
397                         var $tempDiv = $(document.createElement('div'));
398                         $tempDiv.html(data.error);
399                         $tempDiv.addClass('error');
400                         Functions.ajaxShowMessage($tempDiv, false);
401                     }
402                 });
403             }
404         },
406         /**
407          * Refresh restore button state.
408          * Make restore button disabled if the table is similar with initial state.
409          */
410         refreshRestoreButton: function () {
411             // check if table state is as initial state
412             var isInitial = true;
413             for (var i = 0; i < g.colOrder.length; i++) {
414                 if (g.colOrder[i] !== i) {
415                     isInitial = false;
416                     break;
417                 }
418             }
419             // check if only one visible column left
420             var isOneColumn = g.visibleHeadersCount === 1;
421             // enable or disable restore button
422             if (isInitial || isOneColumn) {
423                 $(g.o).find('div.restore_column').hide();
424             } else {
425                 $(g.o).find('div.restore_column').show();
426             }
427         },
429         /**
430          * Update current hint using the boolean values (showReorderHint, showSortHint, etc.).
431          *
432          */
433         updateHint: function () {
434             var text = '';
435             if (!g.colRsz && !g.colReorder) {     // if not resizing or dragging
436                 if (g.visibleHeadersCount > 1) {
437                     g.showReorderHint = true;
438                 }
439                 if ($(t).find('th.marker').length > 0) {
440                     g.showMarkHint = true;
441                 }
442                 if (g.showSortHint && g.sortHint) {
443                     text += text.length > 0 ? '<br>' : '';
444                     text += '- ' + g.sortHint;
445                 }
446                 if (g.showMultiSortHint && g.strMultiSortHint) {
447                     text += text.length > 0 ? '<br>' : '';
448                     text += '- ' + g.strMultiSortHint;
449                 }
450                 if (g.showMarkHint &&
451                     g.markHint &&
452                     ! g.showSortHint && // we do not show mark hint, when sort hint is shown
453                     g.showReorderHint &&
454                     g.reorderHint
455                 ) {
456                     text += text.length > 0 ? '<br>' : '';
457                     text += '- ' + g.reorderHint;
458                     text += text.length > 0 ? '<br>' : '';
459                     text += '- ' + g.markHint;
460                     text += text.length > 0 ? '<br>' : '';
461                     text += '- ' + g.copyHint;
462                 }
463             }
464             return text;
465         },
467         /**
468          * Toggle column's visibility.
469          * After calling this function and it returns true, afterToggleCol() must be called.
470          *
471          * @return boolean True if the column is toggled successfully.
472          */
473         toggleCol: function (n) {
474             if (g.colVisib[n]) {
475                 // can hide if more than one column is visible
476                 if (g.visibleHeadersCount > 1) {
477                     $(g.t).find('tr').each(function () {
478                         $(this).find('th.draggable:eq(' + n + '),' +
479                                      'td:eq(' + (g.actionSpan + n) + ')')
480                             .hide();
481                     });
482                     g.colVisib[n] = 0;
483                     $(g.cList).find('.lDiv div:eq(' + n + ') input').prop('checked', false);
484                 } else {
485                     // cannot hide, force the checkbox to stay checked
486                     $(g.cList).find('.lDiv div:eq(' + n + ') input').prop('checked', true);
487                     return false;
488                 }
489             } else {    // column n is not visible
490                 $(g.t).find('tr').each(function () {
491                     $(this).find('th.draggable:eq(' + n + '),' +
492                                  'td:eq(' + (g.actionSpan + n) + ')')
493                         .show();
494                 });
495                 g.colVisib[n] = 1;
496                 $(g.cList).find('.lDiv div:eq(' + n + ') input').prop('checked', true);
497             }
498             return true;
499         },
501         /**
502          * This must be called if toggleCol() returns is true.
503          *
504          * This function is separated from toggleCol because, sometimes, we want to toggle
505          * some columns together at one time and do just one adjustment after it, e.g. in showAllColumns().
506          */
507         afterToggleCol: function () {
508             // some adjustments after hiding column
509             g.reposRsz();
510             g.reposDrop();
511             g.sendColPrefs();
513             // check visible first row headers count
514             g.visibleHeadersCount = $(g.t).find('tr:first th.draggable:visible').length;
515             g.refreshRestoreButton();
516         },
518         /**
519          * Show columns' visibility list.
520          *
521          * @param obj The drop down arrow of column visibility list
522          */
523         showColList: function (obj) {
524             // only show when not resizing or reordering
525             if (!g.colRsz && !g.colReorder) {
526                 var pos = $(obj).position();
527                 // check if the list position is too right
528                 if (pos.left + $(g.cList).outerWidth(true) > $(document).width()) {
529                     pos.left = $(document).width() - $(g.cList).outerWidth(true);
530                 }
531                 $(g.cList).css({
532                     left: pos.left,
533                     top: pos.top + $(obj).outerHeight(true)
534                 })
535                     .show();
536                 $(obj).addClass('coldrop-hover');
537             }
538         },
540         /**
541          * Hide columns' visibility list.
542          */
543         hideColList: function () {
544             $(g.cList).hide();
545             $(g.cDrop).find('.coldrop-hover').removeClass('coldrop-hover');
546         },
548         /**
549          * Reposition the column visibility drop-down arrow.
550          */
551         reposDrop: function () {
552             var $th = $(t).find('th:not(.draggable)');
553             for (var i = 0; i < $th.length; i++) {
554                 var $cd = $(g.cDrop).find('div:eq(' + i + ')');   // column drop-down arrow
555                 var pos = $($th[i]).position();
556                 $cd.css({
557                     left: pos.left + $($th[i]).width() - $cd.width(),
558                     top: pos.top
559                 });
560             }
561         },
563         /**
564          * Show all hidden columns.
565          */
566         showAllColumns: function () {
567             for (var i = 0; i < g.colVisib.length; i++) {
568                 if (!g.colVisib[i]) {
569                     g.toggleCol(i);
570                 }
571             }
572             g.afterToggleCol();
573         },
575         /**
576          * Show edit cell, if it can be shown
577          *
578          * @param cell <td> element to be edited
579          */
580         showEditCell: function (cell) {
581             if ($(cell).is('.grid_edit') &&
582                 !g.colRsz && !g.colReorder) {
583                 if (!g.isCellEditActive) {
584                     var $cell = $(cell);
586                     if ('string' === $cell.attr('data-type') ||
587                         'blob' === $cell.attr('data-type') ||
588                         'json' === $cell.attr('data-type')
589                     ) {
590                         g.cEdit = g.cEditTextarea;
591                     } else {
592                         g.cEdit = g.cEditStd;
593                     }
595                     // remove all edit area and hide it
596                     $(g.cEdit).find('.edit_area').empty().hide();
597                     // reposition the cEdit element
598                     $(g.cEdit).css({
599                         top: $cell.position().top,
600                         left: $cell.position().left
601                     })
602                         .show()
603                         .find('.edit_box')
604                         .css({
605                             width: $cell.outerWidth(),
606                             height: $cell.outerHeight()
607                         });
608                     // fill the cell edit with text from <td>
609                     var value = Functions.getCellValue(cell);
610                     if ($cell.attr('data-type') === 'json' && $cell.is('.truncated') === false) {
611                         try {
612                             value = JSON.stringify(JSON.parse(value), null, 4);
613                         } catch (e) {
614                             // Show as is
615                         }
616                     }
617                     $(g.cEdit).find('.edit_box').val(value);
619                     g.currentEditCell = cell;
620                     $(g.cEdit).find('.edit_box').trigger('focus');
621                     moveCursorToEnd($(g.cEdit).find('.edit_box'));
622                     $(g.cEdit).find('*').prop('disabled', false);
623                 }
624             }
626             function moveCursorToEnd (input) {
627                 var originalValue = input.val();
628                 var originallength = originalValue.length;
629                 input.val('');
630                 input.trigger('blur').trigger('focus').val(originalValue);
631                 input[0].setSelectionRange(originallength, originallength);
632             }
633         },
635         /**
636          * Remove edit cell and the edit area, if it is shown.
637          *
638          * @param force Optional, force to hide edit cell without saving edited field.
639          * @param data  Optional, data from the POST AJAX request to save the edited field
640          *              or just specify "true", if we want to replace the edited field with the new value.
641          * @param field Optional, the edited <td>. If not specified, the function will
642          *              use currently edited <td> from g.currentEditCell.
643          * @param field Optional, this object contains a boolean named move (true, if called from move* functions)
644          *              and a <td> to which the grid_edit should move
645          */
646         hideEditCell: function (force, data, field, options) {
647             if (g.isCellEditActive && !force) {
648                 // cell is being edited, save or post the edited data
649                 if (options !== undefined) {
650                     g.saveOrPostEditedCell(options);
651                 } else {
652                     g.saveOrPostEditedCell();
653                 }
654                 return;
655             }
657             // cancel any previous request
658             if (g.lastXHR !== null) {
659                 g.lastXHR.abort();
660                 g.lastXHR = null;
661             }
663             if (data) {
664                 if (g.currentEditCell) {    // save value of currently edited cell
665                     // replace current edited field with the new value
666                     var $thisField = $(g.currentEditCell);
667                     var isNull = $thisField.data('value') === null;
668                     if (isNull) {
669                         $thisField.find('span').html('NULL');
670                         $thisField.addClass('null');
671                     } else {
672                         $thisField.removeClass('null');
673                         var value = data.isNeedToRecheck
674                             ? data.truncatableFieldValue
675                             : $thisField.data('value');
677                         // Truncates the text.
678                         $thisField.removeClass('truncated');
679                         if (CommonParams.get('pftext') === 'P' && value.length > g.maxTruncatedLen) {
680                             $thisField.addClass('truncated');
681                             value = value.substring(0, g.maxTruncatedLen) + '...';
682                         }
684                         // Add <br> before carriage return.
685                         var newHtml = Functions.escapeHtml(value);
686                         newHtml = newHtml.replace(/\n/g, '<br>\n');
688                         // remove decimal places if column type not supported
689                         if (($thisField.attr('data-decimals') === 0) && ($thisField.attr('data-type').indexOf('time') !== -1)) {
690                             newHtml = newHtml.substring(0, newHtml.indexOf('.'));
691                         }
693                         // remove addtional decimal places
694                         if (($thisField.attr('data-decimals') > 0) && ($thisField.attr('data-type').indexOf('time') !== -1)) {
695                             newHtml = newHtml.substring(0, newHtml.length - (6 - $thisField.attr('data-decimals')));
696                         }
698                         var selector = 'span';
699                         if ($thisField.hasClass('hex') && $thisField.find('a').length) {
700                             selector = 'a';
701                         }
703                         // Updates the code keeping highlighting (if any).
704                         var $target = $thisField.find(selector);
705                         if (!Functions.updateCode($target, newHtml, value)) {
706                             $target.html(newHtml);
707                         }
708                     }
709                     if ($thisField.is('.bit')) {
710                         $thisField.find('span').text($thisField.data('value'));
711                     }
712                 }
713                 if (data.transformations !== undefined) {
714                     $.each(data.transformations, function (cellIndex, value) {
715                         var $thisField = $(g.t).find('.to_be_saved:eq(' + cellIndex + ')');
716                         $thisField.find('span').html(value);
717                     });
718                 }
719                 if (data.relations !== undefined) {
720                     $.each(data.relations, function (cellIndex, value) {
721                         var $thisField = $(g.t).find('.to_be_saved:eq(' + cellIndex + ')');
722                         $thisField.find('span').html(value);
723                     });
724                 }
726                 // refresh the grid
727                 g.reposRsz();
728                 g.reposDrop();
729             }
731             // hide the cell editing area
732             $(g.cEdit).hide();
733             $(g.cEdit).find('.edit_box').trigger('blur');
734             g.isCellEditActive = false;
735             g.currentEditCell = null;
736             // destroy datepicker in edit area, if exist
737             var $dp = $(g.cEdit).find('.hasDatepicker');
738             if ($dp.length > 0) {
739                 // eslint-disable-next-line no-underscore-dangle
740                 $(document).on('mousedown', $.datepicker._checkExternalClick);
741                 $dp.datepicker('destroy');
742                 // change the cursor in edit box back to normal
743                 // (the cursor become a hand pointer when we add datepicker)
744                 $(g.cEdit).find('.edit_box').css('cursor', 'inherit');
745             }
746         },
748         /**
749          * Show drop-down edit area when edit cell is focused.
750          */
751         showEditArea: function () {
752             if (!g.isCellEditActive) {   // make sure the edit area has not been shown
753                 g.isCellEditActive = true;
754                 g.isEditCellTextEditable = false;
755                 /**
756                  * @var $td current edited cell
757                  */
758                 var $td = $(g.currentEditCell);
759                 /**
760                  * @var $editArea the editing area
761                  */
762                 var $editArea = $(g.cEdit).find('.edit_area');
763                 /**
764                  * @var where_clause WHERE clause for the edited cell
765                  */
766                 var whereClause = $td.parent('tr').find('.where_clause').val();
767                 /**
768                  * @var field_name  String containing the name of this field.
769                  * @see Sql.getFieldName()
770                  */
771                 var fieldName = Sql.getFieldName($(t), $td);
772                 /**
773                  * @var relation_curr_value String current value of the field (for fields that are foreign keyed).
774                  */
775                 var relationCurrValue = $td.text();
776                 /**
777                  * @var relation_key_or_display_column String relational key if in 'Relational display column' mode,
778                  * relational display column if in 'Relational key' mode (for fields that are foreign keyed).
779                  */
780                 var relationKeyOrDisplayColumn = $td.find('a').attr('title');
781                 /**
782                  * @var curr_value String current value of the field (for fields that are of type enum or set).
783                  */
784                 var currValue = $td.find('span').text();
786                 // empty all edit area, then rebuild it based on $td classes
787                 $editArea.empty();
789                 // remember this instead of testing more than once
790                 var isNull = $td.is('.null');
792                 // add goto link, if this cell contains a link
793                 if ($td.find('a').length > 0) {
794                     var gotoLink = document.createElement('div');
795                     gotoLink.className = 'goto_link';
796                     $(gotoLink).append(g.gotoLinkText + ' ').append($td.find('a').clone());
797                     $editArea.append(gotoLink);
798                 }
800                 g.wasEditedCellNull = false;
801                 if ($td.is(':not(.not_null)')) {
802                     // append a null checkbox
803                     $editArea.append('<div class="null_div"><label>Null:<input type="checkbox"></label></div>');
805                     var $checkbox = $editArea.find('.null_div input');
806                     // check if current <td> is NULL
807                     if (isNull) {
808                         $checkbox.prop('checked', true);
809                         g.wasEditedCellNull = true;
810                     }
812                     // if the select/editor is changed un-check the 'checkbox_null_<field_name>_<row_index>'.
813                     if ($td.is('.enum, .set')) {
814                         $editArea.on('change', 'select', function () {
815                             $checkbox.prop('checked', false);
816                         });
817                     } else if ($td.is('.relation')) {
818                         $editArea.on('change', 'select', function () {
819                             $checkbox.prop('checked', false);
820                         });
821                         $editArea.on('click', '.browse_foreign', function () {
822                             $checkbox.prop('checked', false);
823                         });
824                     } else {
825                         $(g.cEdit).on('keypress change paste', '.edit_box', function () {
826                             $checkbox.prop('checked', false);
827                         });
828                         // Capture ctrl+v (on IE and Chrome)
829                         $(g.cEdit).on('keydown', '.edit_box', function (e) {
830                             if (e.ctrlKey && e.which === 86) {
831                                 $checkbox.prop('checked', false);
832                             }
833                         });
834                         $editArea.on('keydown', 'textarea', function () {
835                             $checkbox.prop('checked', false);
836                         });
837                     }
838                     // if some text is written in textbox automatically unmark the null checkbox and if it is emptied again mark the checkbox.
839                     $(g.cEdit).find('.edit_box').on('input', function () {
840                         if ($(g.cEdit).find('.edit_box').val() !== '') {
841                             $checkbox.prop('checked', false);
842                         } else {
843                             $checkbox.prop('checked', true);
844                         }
845                     });
846                     // if null checkbox is clicked empty the corresponding select/editor.
847                     $checkbox.on('click', function () {
848                         if ($td.is('.enum')) {
849                             $editArea.find('select').val('');
850                         } else if ($td.is('.set')) {
851                             $editArea.find('select').find('option').each(function () {
852                                 var $option = $(this);
853                                 $option.prop('selected', false);
854                             });
855                         } else if ($td.is('.relation')) {
856                             // if the dropdown is there to select the foreign value
857                             if ($editArea.find('select').length > 0) {
858                                 $editArea.find('select').val('');
859                             }
860                         } else {
861                             $editArea.find('textarea').val('');
862                         }
863                         $(g.cEdit).find('.edit_box').val('');
864                     });
865                 }
867                 // reset the position of the edit_area div after closing datetime picker
868                 $(g.cEdit).find('.edit_area').css({ 'top' :'0','position':'' });
870                 var postParams;
871                 if ($td.is('.relation')) {
872                     // handle relations
873                     $editArea.addClass('edit_area_loading');
875                     // initialize the original data
876                     $td.data('original_data', null);
878                     /**
879                      * @var post_params Object containing parameters for the POST request
880                      */
881                     postParams = {
882                         'ajax_request' : true,
883                         'get_relational_values' : true,
884                         'server' : g.server,
885                         'db' : g.db,
886                         'table' : g.table,
887                         'column' : fieldName,
888                         'curr_value' : relationCurrValue,
889                         'relation_key_or_display_column' : relationKeyOrDisplayColumn
890                     };
892                     g.lastXHR = $.post('sql.php', postParams, function (data) {
893                         g.lastXHR = null;
894                         $editArea.removeClass('edit_area_loading');
895                         if ($(data.dropdown).is('select')) {
896                             // save original_data
897                             var value = $(data.dropdown).val();
898                             $td.data('original_data', value);
899                             // update the text input field, in case where the "Relational display column" is checked
900                             $(g.cEdit).find('.edit_box').val(value);
901                         }
903                         $editArea.append(data.dropdown);
904                         $editArea.append('<div class="cell_edit_hint">' + g.cellEditHint + '</div>');
906                         // for 'Browse foreign values' options,
907                         // hide the value next to 'Browse foreign values' link
908                         $editArea.find('span.curr_value').hide();
909                         // handle update for new values selected from new window
910                         $editArea.find('span.curr_value').on('change', function () {
911                             $(g.cEdit).find('.edit_box').val($(this).text());
912                         });
913                     }); // end $.post()
915                     $editArea.show();
916                     $editArea.on('change', 'select', function () {
917                         $(g.cEdit).find('.edit_box').val($(this).val());
918                     });
919                     g.isEditCellTextEditable = true;
920                 } else if ($td.is('.enum')) {
921                     // handle enum fields
922                     $editArea.addClass('edit_area_loading');
924                     /**
925                      * @var post_params Object containing parameters for the POST request
926                      */
927                     postParams = {
928                         'ajax_request' : true,
929                         'get_enum_values' : true,
930                         'server' : g.server,
931                         'db' : g.db,
932                         'table' : g.table,
933                         'column' : fieldName,
934                         'curr_value' : currValue
935                     };
936                     g.lastXHR = $.post('sql.php', postParams, function (data) {
937                         g.lastXHR = null;
938                         $editArea.removeClass('edit_area_loading');
939                         $editArea.append(data.dropdown);
940                         $editArea.append('<div class="cell_edit_hint">' + g.cellEditHint + '</div>');
941                     }); // end $.post()
943                     $editArea.show();
944                     $editArea.on('change', 'select', function () {
945                         $(g.cEdit).find('.edit_box').val($(this).val());
946                     });
947                 } else if ($td.is('.set')) {
948                     // handle set fields
949                     $editArea.addClass('edit_area_loading');
951                     // if the data is truncated, get the full data
952                     if ($td.is('.truncated')) {
953                         postParams = {
954                             'ajax_request': true,
955                             'get_set_values': true,
956                             'server': g.server,
957                             'db': g.db,
958                             'table': g.table,
959                             'column': fieldName,
960                             'curr_value': currValue,
961                             'get_full_values': true,
962                             'where_clause': whereClause
963                         };
964                     } else {
965                         postParams = {
966                             'ajax_request': true,
967                             'get_set_values': true,
968                             'server': g.server,
969                             'db': g.db,
970                             'table': g.table,
971                             'column': fieldName,
972                             'curr_value': currValue
973                         };
974                     }
976                     g.lastXHR = $.post('sql.php', postParams, function (data) {
977                         g.lastXHR = null;
978                         $editArea.removeClass('edit_area_loading');
979                         $editArea.append(data.select);
980                         $td.data('original_data', $(data.select).val().join());
981                         $editArea.append('<div class="cell_edit_hint">' + g.cellEditHint + '</div>');
982                     }); // end $.post()
984                     $editArea.show();
985                     $editArea.on('change', 'select', function () {
986                         $(g.cEdit).find('.edit_box').val($(this).val());
987                     });
988                 } else if ($td.is('.truncated, .transformed')) {
989                     if ($td.is('.to_be_saved')) {   // cell has been edited
990                         var value = $td.data('value');
991                         $(g.cEdit).find('.edit_box').val(value);
992                         $editArea.append('<textarea></textarea>');
993                         $editArea.find('textarea').val(value);
994                         $editArea
995                             .on('keyup', 'textarea', function () {
996                                 $(g.cEdit).find('.edit_box').val($(this).val());
997                             });
998                         $(g.cEdit).on('keyup', '.edit_box', function () {
999                             $editArea.find('textarea').val($(this).val());
1000                         });
1001                         $editArea.append('<div class="cell_edit_hint">' + g.cellEditHint + '</div>');
1002                     } else {
1003                         // handle truncated/transformed values values
1004                         $editArea.addClass('edit_area_loading');
1006                         // initialize the original data
1007                         $td.data('original_data', null);
1009                         /**
1010                          * @var sql_query   String containing the SQL query used to retrieve value of truncated/transformed data
1011                          */
1012                         var sqlQuery = 'SELECT `' + fieldName + '` FROM `' + g.table + '` WHERE ' + whereClause;
1014                         // Make the Ajax call and get the data, wrap it and insert it
1015                         g.lastXHR = $.post('sql.php', {
1016                             'server' : g.server,
1017                             'db' : g.db,
1018                             'ajax_request' : true,
1019                             'sql_query' : sqlQuery,
1020                             'grid_edit' : true
1021                         }, function (data) {
1022                             g.lastXHR = null;
1023                             $editArea.removeClass('edit_area_loading');
1024                             if (typeof data !== 'undefined' && data.success === true) {
1025                                 if ($td.attr('data-type') === 'json') {
1026                                     try {
1027                                         data.value = JSON.stringify(JSON.parse(data.value), null, 4);
1028                                     } catch (e) {
1029                                         // Show as is
1030                                     }
1031                                 }
1032                                 $td.data('original_data', data.value);
1033                                 $(g.cEdit).find('.edit_box').val(data.value);
1034                             } else {
1035                                 Functions.ajaxShowMessage(data.error, false);
1036                             }
1037                         }); // end $.post()
1038                     }
1039                     g.isEditCellTextEditable = true;
1040                 } else if ($td.is('.timefield, .datefield, .datetimefield, .timestampfield')) {
1041                     var $inputField = $(g.cEdit).find('.edit_box');
1043                     // remember current datetime value in $input_field, if it is not null
1044                     var datetimeValue = !isNull ? $inputField.val() : '';
1046                     var showMillisec = false;
1047                     var showMicrosec = false;
1048                     var timeFormat = 'HH:mm:ss';
1049                     // check for decimal places of seconds
1050                     if (($td.attr('data-decimals') > 0) && ($td.attr('data-type').indexOf('time') !== -1)) {
1051                         if (datetimeValue && datetimeValue.indexOf('.') === false) {
1052                             datetimeValue += '.';
1053                         }
1054                         if ($td.attr('data-decimals') > 3) {
1055                             showMillisec = true;
1056                             showMicrosec = true;
1057                             timeFormat = 'HH:mm:ss.lc';
1059                             if (datetimeValue) {
1060                                 datetimeValue += '000000';
1061                                 datetimeValue = datetimeValue.substring(0, datetimeValue.indexOf('.') + 7);
1062                                 $inputField.val(datetimeValue);
1063                             }
1064                         } else {
1065                             showMillisec = true;
1066                             timeFormat = 'HH:mm:ss.l';
1068                             if (datetimeValue) {
1069                                 datetimeValue += '000';
1070                                 datetimeValue = datetimeValue.substring(0, datetimeValue.indexOf('.') + 4);
1071                                 $inputField.val(datetimeValue);
1072                             }
1073                         }
1074                     }
1076                     // add datetime picker
1077                     Functions.addDatepicker($inputField, $td.attr('data-type'), {
1078                         showMillisec: showMillisec,
1079                         showMicrosec: showMicrosec,
1080                         timeFormat: timeFormat
1081                     });
1083                     $inputField.on('keyup', function (e) {
1084                         if (e.which === 13) {
1085                             // post on pressing "Enter"
1086                             e.preventDefault();
1087                             e.stopPropagation();
1088                             g.saveOrPostEditedCell();
1089                         } else if (e.which !== 27) {
1090                             Functions.toggleDatepickerIfInvalid($td, $inputField);
1091                         }
1092                     });
1094                     $inputField.datepicker('show');
1095                     Functions.toggleDatepickerIfInvalid($td, $inputField);
1097                     // unbind the mousedown event to prevent the problem of
1098                     // datepicker getting closed, needs to be checked for any
1099                     // change in names when updating
1100                     // eslint-disable-next-line no-underscore-dangle
1101                     $(document).off('mousedown', $.datepicker._checkExternalClick);
1103                     // move ui-datepicker-div inside cEdit div
1104                     var datepickerDiv = $('#ui-datepicker-div');
1105                     datepickerDiv.css({ 'top': 0, 'left': 0, 'position': 'relative' });
1106                     $(g.cEdit).append(datepickerDiv);
1108                     // cancel any click on the datepicker element
1109                     $editArea.find('> *').on('click', function (e) {
1110                         e.stopPropagation();
1111                     });
1113                     g.isEditCellTextEditable = true;
1114                 } else {
1115                     g.isEditCellTextEditable = true;
1116                     // only append edit area hint if there is a null checkbox
1117                     if ($editArea.children().length > 0) {
1118                         $editArea.append('<div class="cell_edit_hint">' + g.cellEditHint + '</div>');
1119                     }
1120                 }
1121                 if ($editArea.children().length > 0) {
1122                     $editArea.show();
1123                 }
1124             }
1125         },
1127         /**
1128          * Post the content of edited cell.
1129          *
1130          * @param field Optional, this object contains a boolean named move (true, if called from move* functions)
1131          *              and a <td> to which the grid_edit should move
1132          */
1133         postEditedCell: function (options) {
1134             if (g.isSaving) {
1135                 return;
1136             }
1137             g.isSaving = true;
1138             /**
1139              * @var relation_fields Array containing the name/value pairs of relational fields
1140              */
1141             var relationFields = {};
1142             /**
1143              * @var relational_display string 'K' if relational key, 'D' if relational display column
1144              */
1145             var relationalDisplay = $(g.o).find('input[name=relational_display]:checked').val();
1146             /**
1147              * @var transform_fields    Array containing the name/value pairs for transformed fields
1148              */
1149             var transformFields = {};
1150             /**
1151              * @var transformation_fields   Boolean, if there are any transformed fields in the edited cells
1152              */
1153             var transformationFields = false;
1154             /**
1155              * @var full_sql_query String containing the complete SQL query to update this table
1156              */
1157             var fullSqlQuery = '';
1158             /**
1159              * @var rel_fields_list  String, url encoded representation of {@link relations_fields}
1160              */
1161             var relFieldsList = '';
1162             /**
1163              * @var transform_fields_list  String, url encoded representation of {@link transformFields}
1164              */
1165             var transformFieldsList = '';
1166             /**
1167              * @var where_clause Array containing where clause for updated fields
1168              */
1169             var fullWhereClause = [];
1170             /**
1171              * @var is_unique   Boolean, whether the rows in this table is unique or not
1172              */
1173             var isUnique = $(g.t).find('td.edit_row_anchor').is('.nonunique') ? 0 : 1;
1174             /**
1175              * multi edit variables
1176              */
1177             var multiEditFieldsName = [];
1178             var multiEditFieldsType = [];
1179             var multiEditFields = [];
1180             var multiEditFieldsNull = [];
1182             // alert user if edited table is not unique
1183             if (!isUnique) {
1184                 alert(g.alertNonUnique);
1185             }
1187             // loop each edited row
1188             $(g.t).find('td.to_be_saved').parents('tr').each(function () {
1189                 var $tr = $(this);
1190                 var whereClause = $tr.find('.where_clause').val();
1191                 if (typeof whereClause === 'undefined') {
1192                     whereClause = '';
1193                 }
1194                 fullWhereClause.push(whereClause);
1195                 var conditionArray = JSON.parse($tr.find('.condition_array').val());
1197                 /**
1198                  * multi edit variables, for current row
1199                  * @TODO array indices are still not correct, they should be md5 of field's name
1200                  */
1201                 var fieldsName = [];
1202                 var fieldsType = [];
1203                 var fields = [];
1204                 var fieldsNull = [];
1206                 // loop each edited cell in a row
1207                 $tr.find('.to_be_saved').each(function () {
1208                     /**
1209                      * @var $this_field    Object referring to the td that is being edited
1210                      */
1211                     var $thisField = $(this);
1213                     /**
1214                      * @var field_name  String containing the name of this field.
1215                      * @see Sql.getFieldName()
1216                      */
1217                     var fieldName = Sql.getFieldName($(g.t), $thisField);
1219                     /**
1220                      * @var this_field_params   Array temporary storage for the name/value of current field
1221                      */
1222                     var thisFieldParams = {};
1224                     if ($thisField.is('.transformed')) {
1225                         transformationFields =  true;
1226                     }
1227                     thisFieldParams[fieldName] = $thisField.data('value');
1229                     /**
1230                      * @var is_null String capturing whether 'checkbox_null_<field_name>_<row_index>' is checked.
1231                      */
1232                     var isNull = thisFieldParams[fieldName] === null;
1234                     fieldsName.push(fieldName);
1236                     if (isNull) {
1237                         fieldsNull.push('on');
1238                         fields.push('');
1239                     } else {
1240                         if ($thisField.is('.bit')) {
1241                             fieldsType.push('bit');
1242                         } else if ($thisField.hasClass('hex')) {
1243                             fieldsType.push('hex');
1244                         }
1245                         fieldsNull.push('');
1246                         // Convert \n to \r\n to be consistent with form submitted value.
1247                         // The internal browser representation has to be just \n
1248                         // while form submitted value \r\n, see specification:
1249                         // https://www.w3.org/TR/html5/forms.html#the-textarea-element
1250                         fields.push($thisField.data('value').replace(/\n/g, '\r\n'));
1252                         var cellIndex = $thisField.index('.to_be_saved');
1253                         if ($thisField.is(':not(.relation, .enum, .set, .bit)')) {
1254                             if ($thisField.is('.transformed')) {
1255                                 transformFields[cellIndex] = {};
1256                                 $.extend(transformFields[cellIndex], thisFieldParams);
1257                             }
1258                         } else if ($thisField.is('.relation')) {
1259                             relationFields[cellIndex] = {};
1260                             $.extend(relationFields[cellIndex], thisFieldParams);
1261                         }
1262                     }
1263                     // check if edited field appears in WHERE clause
1264                     if (whereClause.indexOf(Sql.urlEncode(fieldName)) > -1) {
1265                         var fieldStr = '`' + g.table + '`.' + '`' + fieldName + '`';
1266                         for (var field in conditionArray) {
1267                             if (field.indexOf(fieldStr) > -1) {
1268                                 conditionArray[field] = isNull ? 'IS NULL' : '= \'' + thisFieldParams[fieldName].replace(/'/g, '\'\'') + '\'';
1269                                 break;
1270                             }
1271                         }
1272                     }
1273                 }); // end of loop for every edited cells in a row
1275                 // save new_clause
1276                 var newClause = '';
1277                 for (var field in conditionArray) {
1278                     newClause += field + ' ' + conditionArray[field] + ' AND ';
1279                 }
1280                 newClause = newClause.substring(0, newClause.length - 5); // remove the last AND
1281                 $tr.data('new_clause', newClause);
1282                 // save condition_array
1283                 $tr.find('.condition_array').val(JSON.stringify(conditionArray));
1285                 multiEditFieldsName.push(fieldsName);
1286                 multiEditFieldsType.push(fieldsType);
1287                 multiEditFields.push(fields);
1288                 multiEditFieldsNull.push(fieldsNull);
1289             }); // end of loop for every edited rows
1291             relFieldsList = $.param(relationFields);
1292             transformFieldsList = $.param(transformFields);
1294             // Make the Ajax post after setting all parameters
1295             /**
1296              * @var post_params Object containing parameters for the POST request
1297              */
1298             var postParams = { 'ajax_request' : true,
1299                 'sql_query' : fullSqlQuery,
1300                 'server' : g.server,
1301                 'db' : g.db,
1302                 'table' : g.table,
1303                 'clause_is_unique' : isUnique,
1304                 'where_clause' : fullWhereClause,
1305                 'fields[multi_edit]' : multiEditFields,
1306                 'fields_name[multi_edit]' : multiEditFieldsName,
1307                 'fields_type[multi_edit]' : multiEditFieldsType,
1308                 'fields_null[multi_edit]' : multiEditFieldsNull,
1309                 'rel_fields_list' : relFieldsList,
1310                 'do_transformations' : transformationFields,
1311                 'transform_fields_list' : transformFieldsList,
1312                 'relational_display' : relationalDisplay,
1313                 'goto' : 'sql.php',
1314                 'submit_type' : 'save'
1315             };
1317             if (!g.saveCellsAtOnce) {
1318                 $(g.cEdit).find('*').prop('disabled', true);
1319                 $(g.cEdit).find('.edit_box').addClass('edit_box_posting');
1320             } else {
1321                 $(g.o).find('div.save_edited').addClass('saving_edited_data')
1322                     .find('input').prop('disabled', true);    // disable the save button
1323             }
1325             $.ajax({
1326                 type: 'POST',
1327                 url: 'tbl_replace.php',
1328                 data: postParams,
1329                 success:
1330                     function (data) {
1331                         g.isSaving = false;
1332                         if (!g.saveCellsAtOnce) {
1333                             $(g.cEdit).find('*').prop('disabled', false);
1334                             $(g.cEdit).find('.edit_box').removeClass('edit_box_posting');
1335                         } else {
1336                             $(g.o).find('div.save_edited').removeClass('saving_edited_data')
1337                                 .find('input').prop('disabled', false);  // enable the save button back
1338                         }
1339                         if (typeof data !== 'undefined' && data.success === true) {
1340                             if (typeof options === 'undefined' || ! options.move) {
1341                                 Functions.ajaxShowMessage(data.message);
1342                             }
1344                             // update where_clause related data in each edited row
1345                             $(g.t).find('td.to_be_saved').parents('tr').each(function () {
1346                                 var newClause = $(this).data('new_clause');
1347                                 var $whereClause = $(this).find('.where_clause');
1348                                 var oldClause = $whereClause.val();
1349                                 var decodedOldClause = oldClause;
1350                                 var decodedNewClause = newClause;
1352                                 $whereClause.val(newClause);
1353                                 // update Edit, Copy, and Delete links also
1354                                 $(this).find('a').each(function () {
1355                                     $(this).attr('href', $(this).attr('href').replace(oldClause, newClause));
1356                                     // update delete confirmation in Delete link
1357                                     if ($(this).attr('href').indexOf('DELETE') > -1) {
1358                                         $(this).removeAttr('onclick')
1359                                             .off('click')
1360                                             .on('click', function () {
1361                                                 return Functions.confirmLink(this, 'DELETE FROM `' + g.db + '`.`' + g.table + '` WHERE ' +
1362                                                        decodedNewClause + (isUnique ? '' : ' LIMIT 1'));
1363                                             });
1364                                     }
1365                                 });
1366                                 // update the multi edit checkboxes
1367                                 $(this).find('input[type=checkbox]').each(function () {
1368                                     var $checkbox = $(this);
1369                                     var checkboxName = $checkbox.attr('name');
1370                                     var checkboxValue = $checkbox.val();
1372                                     $checkbox.attr('name', checkboxName.replace(oldClause, newClause));
1373                                     $checkbox.val(checkboxValue.replace(decodedOldClause, decodedNewClause));
1374                                 });
1375                             });
1376                             // update the display of executed SQL query command
1377                             if (typeof data.sql_query !== 'undefined') {
1378                                 // extract query box
1379                                 var $resultQuery = $($.parseHTML(data.sql_query));
1380                                 var sqlOuter = $resultQuery.find('.sqlOuter').wrap('<p>').parent().html();
1381                                 var tools = $resultQuery.find('.tools').wrap('<p>').parent().html();
1382                                 // sqlOuter and tools will not be present if 'Show SQL queries' configuration is off
1383                                 if (typeof sqlOuter !== 'undefined' && typeof tools !== 'undefined') {
1384                                     $(g.o).find('.result_query:not(:last)').remove();
1385                                     var $existingQuery = $(g.o).find('.result_query');
1386                                     // If two query box exists update query in second else add a second box
1387                                     if ($existingQuery.find('div.sqlOuter').length > 1) {
1388                                         $existingQuery.children(':nth-child(4)').remove();
1389                                         $existingQuery.children(':nth-child(4)').remove();
1390                                         $existingQuery.append(sqlOuter + tools);
1391                                     } else {
1392                                         $existingQuery.append(sqlOuter + tools);
1393                                     }
1394                                     Functions.highlightSql($existingQuery);
1395                                 }
1396                             }
1397                             // hide and/or update the successfully saved cells
1398                             g.hideEditCell(true, data);
1400                             // remove the "Save edited cells" button
1401                             $(g.o).find('div.save_edited').hide();
1402                             // update saved fields
1403                             $(g.t).find('.to_be_saved')
1404                                 .removeClass('to_be_saved')
1405                                 .data('value', null)
1406                                 .data('original_data', null);
1408                             g.isCellEdited = false;
1409                         } else {
1410                             Functions.ajaxShowMessage(data.error, false);
1411                             if (!g.saveCellsAtOnce) {
1412                                 $(g.t).find('.to_be_saved')
1413                                     .removeClass('to_be_saved');
1414                             }
1415                         }
1416                     }
1417             }).done(function () {
1418                 if (options !== undefined && options.move) {
1419                     g.showEditCell(options.cell);
1420                 }
1421             }); // end $.ajax()
1422         },
1424         /**
1425          * Save edited cell, so it can be posted later.
1426          */
1427         saveEditedCell: function () {
1428             /**
1429              * @var $this_field    Object referring to the td that is being edited
1430              */
1431             var $thisField = $(g.currentEditCell);
1432             var $testElement = ''; // to test the presence of a element
1434             var needToPost = false;
1436             /**
1437              * @var field_name  String containing the name of this field.
1438              * @see Sql.getFieldName()
1439              */
1440             var fieldName = Sql.getFieldName($(g.t), $thisField);
1442             /**
1443              * @var this_field_params   Array temporary storage for the name/value of current field
1444              */
1445             var thisFieldParams = {};
1447             /**
1448              * @var is_null String capturing whether 'checkbox_null_<field_name>_<row_index>' is checked.
1449              */
1450             var isNull = $(g.cEdit).find('input:checkbox').is(':checked');
1452             if ($(g.cEdit).find('.edit_area').is('.edit_area_loading')) {
1453                 // the edit area is still loading (retrieving cell data), no need to post
1454                 needToPost = false;
1455             } else if (isNull) {
1456                 if (!g.wasEditedCellNull) {
1457                     thisFieldParams[fieldName] = null;
1458                     needToPost = true;
1459                 }
1460             } else {
1461                 if ($thisField.is('.bit')) {
1462                     thisFieldParams[fieldName] = $(g.cEdit).find('.edit_box').val();
1463                 } else if ($thisField.is('.set')) {
1464                     $testElement = $(g.cEdit).find('select');
1465                     thisFieldParams[fieldName] = $testElement.map(function () {
1466                         return $(this).val();
1467                     }).get().join(',');
1468                 } else if ($thisField.is('.relation, .enum')) {
1469                     // for relation and enumeration, take the results from edit box value,
1470                     // because selected value from drop-down, new window or multiple
1471                     // selection list will always be updated to the edit box
1472                     thisFieldParams[fieldName] = $(g.cEdit).find('.edit_box').val();
1473                 } else if ($thisField.hasClass('hex')) {
1474                     if ($(g.cEdit).find('.edit_box').val().match(/^(0x)?[a-f0-9]*$/i) !== null) {
1475                         thisFieldParams[fieldName] = $(g.cEdit).find('.edit_box').val();
1476                     } else {
1477                         var hexError = '<div class="error">' + Messages.strEnterValidHex + '</div>';
1478                         Functions.ajaxShowMessage(hexError, false);
1479                         thisFieldParams[fieldName] = Functions.getCellValue(g.currentEditCell);
1480                     }
1481                 } else {
1482                     thisFieldParams[fieldName] = $(g.cEdit).find('.edit_box').val();
1483                 }
1484                 if (g.wasEditedCellNull || thisFieldParams[fieldName] !== Functions.getCellValue(g.currentEditCell)) {
1485                     needToPost = true;
1486                 }
1487             }
1489             if (needToPost) {
1490                 $(g.currentEditCell).addClass('to_be_saved')
1491                     .data('value', thisFieldParams[fieldName]);
1492                 if (g.saveCellsAtOnce) {
1493                     $(g.o).find('div.save_edited').show();
1494                 }
1495                 g.isCellEdited = true;
1496             }
1498             return needToPost;
1499         },
1501         /**
1502          * Save or post currently edited cell, depending on the "saveCellsAtOnce" configuration.
1503          *
1504          * @param field Optional, this object contains a boolean named move (true, if called from move* functions)
1505          *              and a <td> to which the grid_edit should move
1506          */
1507         saveOrPostEditedCell: function (options) {
1508             var saved = g.saveEditedCell();
1509             // Check if $cfg['SaveCellsAtOnce'] is false
1510             if (!g.saveCellsAtOnce) {
1511                 // Check if need_to_post is true
1512                 if (saved) {
1513                     // Check if this function called from 'move' functions
1514                     if (options !== undefined && options.move) {
1515                         g.postEditedCell(options);
1516                     } else {
1517                         g.postEditedCell();
1518                     }
1519                 // need_to_post is false
1520                 } else {
1521                     // Check if this function called from 'move' functions
1522                     if (options !== undefined && options.move) {
1523                         g.hideEditCell(true);
1524                         g.showEditCell(options.cell);
1525                     // NOT called from 'move' functions
1526                     } else {
1527                         g.hideEditCell(true);
1528                     }
1529                 }
1530             // $cfg['SaveCellsAtOnce'] is true
1531             } else {
1532                 // If need_to_post
1533                 if (saved) {
1534                     // If this function called from 'move' functions
1535                     if (options !== undefined && options.move) {
1536                         g.hideEditCell(true, true, false, options);
1537                         g.showEditCell(options.cell);
1538                     // NOT called from 'move' functions
1539                     } else {
1540                         g.hideEditCell(true, true);
1541                     }
1542                 } else {
1543                     // If this function called from 'move' functions
1544                     if (options !== undefined && options.move) {
1545                         g.hideEditCell(true, false, false, options);
1546                         g.showEditCell(options.cell);
1547                     // NOT called from 'move' functions
1548                     } else {
1549                         g.hideEditCell(true);
1550                     }
1551                 }
1552             }
1553         },
1555         /**
1556          * Initialize column resize feature.
1557          */
1558         initColResize: function () {
1559             // create column resizer div
1560             g.cRsz = document.createElement('div');
1561             g.cRsz.className = 'cRsz';
1563             // get data columns in the first row of the table
1564             var $firstRowCols = $(g.t).find('tr:first th.draggable');
1566             // create column borders
1567             $firstRowCols.each(function () {
1568                 var cb = document.createElement('div'); // column border
1569                 $(cb).addClass('colborder')
1570                     .on('mousedown', function (e) {
1571                         g.dragStartRsz(e, this);
1572                     });
1573                 $(g.cRsz).append(cb);
1574             });
1575             g.reposRsz();
1577             // attach to global div
1578             $(g.gDiv).prepend(g.cRsz);
1579         },
1581         /**
1582          * Initialize column reordering feature.
1583          */
1584         initColReorder: function () {
1585             g.cCpy = document.createElement('div');     // column copy, to store copy of dragged column header
1586             g.cPointer = document.createElement('div'); // column pointer, used when reordering column
1588             // adjust g.cCpy
1589             g.cCpy.className = 'cCpy';
1590             $(g.cCpy).hide();
1592             // adjust g.cPointer
1593             g.cPointer.className = 'cPointer';
1594             $(g.cPointer).css('visibility', 'hidden');  // set visibility to hidden instead of calling hide() to force browsers to cache the image in cPointer class
1596             // assign column reordering hint
1597             g.reorderHint = Messages.strColOrderHint;
1599             // get data columns in the first row of the table
1600             var $firstRowCols = $(g.t).find('tr:first th.draggable');
1602             // initialize column order
1603             var $colOrder = $(g.o).find('.col_order');   // check if column order is passed from PHP
1604             var i;
1605             if ($colOrder.length > 0) {
1606                 g.colOrder = $colOrder.val().split(',');
1607                 for (i = 0; i < g.colOrder.length; i++) {
1608                     g.colOrder[i] = parseInt(g.colOrder[i], 10);
1609                 }
1610             } else {
1611                 g.colOrder = [];
1612                 for (i = 0; i < $firstRowCols.length; i++) {
1613                     g.colOrder.push(i);
1614                 }
1615             }
1617             // register events
1618             $(g.t).find('th.draggable')
1619                 .on('mousedown', function (e) {
1620                     $(g.o).addClass('turnOffSelect');
1621                     if (g.visibleHeadersCount > 1) {
1622                         g.dragStartReorder(e, this);
1623                     }
1624                 })
1625                 .on('mouseenter', function () {
1626                     if (g.visibleHeadersCount > 1) {
1627                         $(this).css('cursor', 'move');
1628                     } else {
1629                         $(this).css('cursor', 'inherit');
1630                     }
1631                 })
1632                 .on('mouseleave', function () {
1633                     g.showReorderHint = false;
1634                     $(this).tooltip('option', {
1635                         content: g.updateHint()
1636                     });
1637                 })
1638                 .on('dblclick', function (e) {
1639                     e.preventDefault();
1640                     $('<div></div>')
1641                         .prop('title', Messages.strColNameCopyTitle)
1642                         .addClass('modal-copy')
1643                         .text(Messages.strColNameCopyText)
1644                         .append(
1645                             $('<input>')
1646                                 .prop('readonly', true)
1647                                 .val($(this).data('column'))
1648                         )
1649                         .dialog({
1650                             resizable: false,
1651                             modal: true
1652                         })
1653                         .find('input').trigger('focus').trigger('select');
1654                 });
1655             $(g.t).find('th.draggable a')
1656                 .on('dblclick', function (e) {
1657                     e.stopPropagation();
1658                 });
1659             // restore column order when the restore button is clicked
1660             $(g.o).find('div.restore_column').on('click', function () {
1661                 g.restoreColOrder();
1662             });
1664             // attach to global div
1665             $(g.gDiv).append(g.cPointer);
1666             $(g.gDiv).append(g.cCpy);
1668             // prevent default "dragstart" event when dragging a link
1669             $(g.t).find('th a').on('dragstart', function () {
1670                 return false;
1671             });
1673             // refresh the restore column button state
1674             g.refreshRestoreButton();
1675         },
1677         /**
1678          * Initialize column visibility feature.
1679          */
1680         initColVisib: function () {
1681             g.cDrop = document.createElement('div');    // column drop-down arrows
1682             g.cList = document.createElement('div');    // column visibility list
1684             // adjust g.cDrop
1685             g.cDrop.className = 'cDrop';
1687             // adjust g.cList
1688             g.cList.className = 'cList';
1689             $(g.cList).hide();
1691             // assign column visibility related hints
1692             g.showAllColText = Messages.strShowAllCol;
1694             // get data columns in the first row of the table
1695             var $firstRowCols = $(g.t).find('tr:first th.draggable');
1697             var i;
1698             // initialize column visibility
1699             var $colVisib = $(g.o).find('.col_visib');   // check if column visibility is passed from PHP
1700             if ($colVisib.length > 0) {
1701                 g.colVisib = $colVisib.val().split(',');
1702                 for (i = 0; i < g.colVisib.length; i++) {
1703                     g.colVisib[i] = parseInt(g.colVisib[i], 10);
1704                 }
1705             } else {
1706                 g.colVisib = [];
1707                 for (i = 0; i < $firstRowCols.length; i++) {
1708                     g.colVisib.push(1);
1709                 }
1710             }
1712             // make sure we have more than one column
1713             if ($firstRowCols.length > 1) {
1714                 var $colVisibTh = $(g.t).find('th:not(.draggable)');
1715                 Functions.tooltip(
1716                     $colVisibTh,
1717                     'th',
1718                     Messages.strColVisibHint
1719                 );
1721                 // create column visibility drop-down arrow(s)
1722                 $colVisibTh.each(function () {
1723                     var cd = document.createElement('div'); // column drop-down arrow
1724                     $(cd).addClass('coldrop')
1725                         .on('click', function () {
1726                             if (g.cList.style.display === 'none') {
1727                                 g.showColList(this);
1728                             } else {
1729                                 g.hideColList();
1730                             }
1731                         });
1732                     $(g.cDrop).append(cd);
1733                 });
1735                 // add column visibility control
1736                 g.cList.innerHTML = '<div class="lDiv"></div>';
1737                 var $listDiv = $(g.cList).find('div');
1739                 var tempClick = function () {
1740                     if (g.toggleCol($(this).index())) {
1741                         g.afterToggleCol();
1742                     }
1743                 };
1745                 for (i = 0; i < $firstRowCols.length; i++) {
1746                     var currHeader = $firstRowCols[i];
1747                     var listElmt = document.createElement('div');
1748                     $(listElmt).text($(currHeader).text())
1749                         .prepend('<input type="checkbox" ' + (g.colVisib[i] ? 'checked="checked" ' : '') + '>');
1750                     $listDiv.append(listElmt);
1751                     // add event on click
1752                     $(listElmt).on('click', tempClick);
1753                 }
1754                 // add "show all column" button
1755                 var showAll = document.createElement('div');
1756                 $(showAll).addClass('showAllColBtn')
1757                     .text(g.showAllColText);
1758                 $(g.cList).append(showAll);
1759                 $(showAll).on('click', function () {
1760                     g.showAllColumns();
1761                 });
1762                 // prepend "show all column" button at top if the list is too long
1763                 if ($firstRowCols.length > 10) {
1764                     var clone = showAll.cloneNode(true);
1765                     $(g.cList).prepend(clone);
1766                     $(clone).on('click', function () {
1767                         g.showAllColumns();
1768                     });
1769                 }
1770             }
1772             // hide column visibility list if we move outside the list
1773             $(g.t).find('td, th.draggable').on('mouseenter', function () {
1774                 g.hideColList();
1775             });
1777             // attach to global div
1778             $(g.gDiv).append(g.cDrop);
1779             $(g.gDiv).append(g.cList);
1781             // some adjustment
1782             g.reposDrop();
1783         },
1785         /**
1786          * Move currently Editing Cell to Up
1787          */
1788         moveUp: function (e) {
1789             e.preventDefault();
1790             var $thisField = $(g.currentEditCell);
1791             var fieldName = Sql.getFieldName($(g.t), $thisField);
1793             var whereClause = $thisField.parents('tr').first().find('.where_clause').val();
1794             if (typeof whereClause === 'undefined') {
1795                 whereClause = '';
1796             }
1797             var found = false;
1798             var $prevRow;
1800             $thisField.parents('tr').first().parents('tbody').children().each(function () {
1801                 if ($(this).find('.where_clause').val() === whereClause) {
1802                     found = true;
1803                 }
1804                 if (!found) {
1805                     $prevRow = $(this);
1806                 }
1807             });
1809             var newCell;
1811             if (found && $prevRow) {
1812                 $prevRow.children('td').each(function () {
1813                     if (Sql.getFieldName($(g.t), $(this)) === fieldName) {
1814                         newCell = this;
1815                     }
1816                 });
1817             }
1819             if (newCell) {
1820                 g.hideEditCell(false, false, false, { move : true, cell : newCell });
1821             }
1822         },
1824         /**
1825          * Move currently Editing Cell to Down
1826          */
1827         moveDown: function (e) {
1828             e.preventDefault();
1830             var $thisField = $(g.currentEditCell);
1831             var fieldName = Sql.getFieldName($(g.t), $thisField);
1833             var whereClause = $thisField.parents('tr').first().find('.where_clause').val();
1834             if (typeof whereClause === 'undefined') {
1835                 whereClause = '';
1836             }
1837             var found = false;
1838             var $nextRow;
1839             var j = 0;
1840             var nextRowFound = false;
1841             $thisField.parents('tr').first().parents('tbody').children().each(function () {
1842                 if ($(this).find('.where_clause').val() === whereClause) {
1843                     found = true;
1844                 }
1845                 if (found) {
1846                     if (j >= 1 && ! nextRowFound) {
1847                         $nextRow = $(this);
1848                         nextRowFound = true;
1849                     } else {
1850                         j++;
1851                     }
1852                 }
1853             });
1855             var newCell;
1856             if (found && $nextRow) {
1857                 $nextRow.children('td').each(function () {
1858                     if (Sql.getFieldName($(g.t), $(this)) === fieldName) {
1859                         newCell = this;
1860                     }
1861                 });
1862             }
1864             if (newCell) {
1865                 g.hideEditCell(false, false, false, { move : true, cell : newCell });
1866             }
1867         },
1869         /**
1870          * Move currently Editing Cell to Left
1871          */
1872         moveLeft: function (e) {
1873             e.preventDefault();
1875             var $thisField = $(g.currentEditCell);
1876             var fieldName = Sql.getFieldName($(g.t), $thisField);
1878             var whereClause = $thisField.parents('tr').first().find('.where_clause').val();
1879             if (typeof whereClause === 'undefined') {
1880                 whereClause = '';
1881             }
1882             var found = false;
1883             var $foundRow;
1884             $thisField.parents('tr').first().parents('tbody').children().each(function () {
1885                 if ($(this).find('.where_clause').val() === whereClause) {
1886                     found = true;
1887                     $foundRow = $(this);
1888                 }
1889             });
1891             var leftCell;
1892             var cellFound = false;
1893             if (found) {
1894                 $foundRow.children('td.grid_edit').each(function () {
1895                     if (Sql.getFieldName($(g.t), $(this)) === fieldName) {
1896                         cellFound = true;
1897                     }
1898                     if (!cellFound) {
1899                         leftCell = this;
1900                     }
1901                 });
1902             }
1904             if (leftCell) {
1905                 g.hideEditCell(false, false, false, { move : true, cell : leftCell });
1906             }
1907         },
1909         /**
1910          * Move currently Editing Cell to Right
1911          */
1912         moveRight: function (e) {
1913             e.preventDefault();
1915             var $thisField = $(g.currentEditCell);
1916             var fieldName = Sql.getFieldName($(g.t), $thisField);
1918             var whereClause = $thisField.parents('tr').first().find('.where_clause').val();
1919             if (typeof whereClause === 'undefined') {
1920                 whereClause = '';
1921             }
1922             var found = false;
1923             var $foundRow;
1924             var j = 0;
1925             $thisField.parents('tr').first().parents('tbody').children().each(function () {
1926                 if ($(this).find('.where_clause').val() === whereClause) {
1927                     found = true;
1928                     $foundRow = $(this);
1929                 }
1930             });
1932             var rightCell;
1933             var cellFound = false;
1934             var nextCellFound = false;
1935             if (found) {
1936                 $foundRow.children('td.grid_edit').each(function () {
1937                     if (Sql.getFieldName($(g.t), $(this)) === fieldName) {
1938                         cellFound = true;
1939                     }
1940                     if (cellFound) {
1941                         if (j >= 1 && ! nextCellFound) {
1942                             rightCell = this;
1943                             nextCellFound = true;
1944                         } else {
1945                             j++;
1946                         }
1947                     }
1948                 });
1949             }
1951             if (rightCell) {
1952                 g.hideEditCell(false, false, false, { move : true, cell : rightCell });
1953             }
1954         },
1956         /**
1957          * Initialize grid editing feature.
1958          */
1959         initGridEdit: function () {
1960             function startGridEditing (e, cell) {
1961                 if (g.isCellEditActive) {
1962                     g.saveOrPostEditedCell();
1963                 } else {
1964                     g.showEditCell(cell);
1965                 }
1966                 e.stopPropagation();
1967             }
1969             function handleCtrlNavigation (e) {
1970                 if ((e.ctrlKey && e.which === 38) || (e.altKey && e.which === 38)) {
1971                     g.moveUp(e);
1972                 } else if ((e.ctrlKey && e.which === 40)  || (e.altKey && e.which === 40)) {
1973                     g.moveDown(e);
1974                 } else if ((e.ctrlKey && e.which === 37) || (e.altKey && e.which === 37)) {
1975                     g.moveLeft(e);
1976                 } else if ((e.ctrlKey && e.which === 39)  || (e.altKey && e.which === 39)) {
1977                     g.moveRight(e);
1978                 }
1979             }
1981             // create cell edit wrapper element
1982             g.cEditStd = document.createElement('div');
1983             g.cEdit = g.cEditStd;
1984             g.cEditTextarea = document.createElement('div');
1986             // adjust g.cEditStd
1987             g.cEditStd.className = 'cEdit';
1988             $(g.cEditStd).html('<input class="edit_box" rows="1"><div class="edit_area"></div>');
1989             $(g.cEditStd).hide();
1991             // adjust g.cEdit
1992             g.cEditTextarea.className = 'cEdit';
1993             $(g.cEditTextarea).html('<textarea class="edit_box" rows="1"></textarea><div class="edit_area"></div>');
1994             $(g.cEditTextarea).hide();
1996             // assign cell editing hint
1997             g.cellEditHint = Messages.strCellEditHint;
1998             g.saveCellWarning = Messages.strSaveCellWarning;
1999             g.alertNonUnique = Messages.strAlertNonUnique;
2000             g.gotoLinkText = Messages.strGoToLink;
2002             // initialize cell editing configuration
2003             g.saveCellsAtOnce = $(g.o).find('.save_cells_at_once').val();
2004             g.maxTruncatedLen = CommonParams.get('LimitChars');
2006             // register events
2007             $(g.t).find('td.data.click1')
2008                 .on('click', function (e) {
2009                     startGridEditing(e, this);
2010                     // prevent default action when clicking on "link" in a table
2011                     if ($(e.target).is('.grid_edit a')) {
2012                         e.preventDefault();
2013                     }
2014                 });
2016             $(g.t).find('td.data.click2')
2017                 .on('click', function (e) {
2018                     var $cell = $(this);
2019                     // In the case of relational link, We want single click on the link
2020                     // to goto the link and double click to start grid-editing.
2021                     var $link = $(e.target);
2022                     if ($link.is('.grid_edit.relation a')) {
2023                         e.preventDefault();
2024                         // get the click count and increase
2025                         var clicks = $cell.data('clicks');
2026                         clicks = (typeof clicks === 'undefined') ? 1 : clicks + 1;
2028                         if (clicks === 1) {
2029                             // if there are no previous clicks,
2030                             // start the single click timer
2031                             var timer = setTimeout(function () {
2032                                 // temporarily remove ajax class so the page loader will not handle it,
2033                                 // submit and then add it back
2034                                 $link.removeClass('ajax');
2035                                 AJAX.requestHandler.call($link[0]);
2036                                 $link.addClass('ajax');
2037                                 $cell.data('clicks', 0);
2038                             }, 700);
2039                             $cell.data('clicks', clicks);
2040                             $cell.data('timer', timer);
2041                         } else {// When double clicking a link, switch to edit mode
2042                             // this is a double click, cancel the single click timer
2043                             // and make the click count 0
2044                             clearTimeout($cell.data('timer'));
2045                             $cell.data('clicks', 0);
2046                             // start grid-editing
2047                             startGridEditing(e, this);
2048                         }
2049                     } else {// If it is not a link or it is a double tap then call startGridEditing
2050                         // this is a double click, cancel the single click timer
2051                         // and make the click count 0
2052                         clearTimeout($cell.data('timer'));
2053                         $cell.data('clicks', 0);
2054                         // start grid-editing
2055                         startGridEditing(e, this);
2056                     }
2057                 })
2058                 .on('dblclick', function (e) {
2059                     if ($(e.target).is('.grid_edit a')) {
2060                         e.preventDefault();
2061                     } else {
2062                         startGridEditing(e, this);
2063                     }
2064                 });
2066             $(g.cEditStd).on('keydown', 'input.edit_box, select', handleCtrlNavigation);
2068             $(g.cEditStd).find('.edit_box').on('focus', function () {
2069                 g.showEditArea();
2070             });
2071             $(g.cEditStd).on('keydown', '.edit_box, select', function (e) {
2072                 if (e.which === 13) {
2073                     // post on pressing "Enter"
2074                     e.preventDefault();
2075                     g.saveOrPostEditedCell();
2076                 }
2077             });
2078             $(g.cEditStd).on('keydown', function (e) {
2079                 if (!g.isEditCellTextEditable) {
2080                     // prevent text editing
2081                     e.preventDefault();
2082                 }
2083             });
2085             $(g.cEditTextarea).on('keydown', 'textarea.edit_box, select', handleCtrlNavigation);
2087             $(g.cEditTextarea).find('.edit_box').on('focus', function () {
2088                 g.showEditArea();
2089             });
2090             $(g.cEditTextarea).on('keydown', '.edit_box, select', function (e) {
2091                 if (e.which === 13 && !e.shiftKey) {
2092                     // post on pressing "Enter"
2093                     e.preventDefault();
2094                     g.saveOrPostEditedCell();
2095                 }
2096             });
2097             $(g.cEditTextarea).on('keydown', function (e) {
2098                 if (!g.isEditCellTextEditable) {
2099                     // prevent text editing
2100                     e.preventDefault();
2101                 }
2102             });
2103             $('html').on('click', function (e) {
2104                 // hide edit cell if the click is not fromDat edit area
2105                 if ($(e.target).parents().index($(g.cEdit)) === -1 &&
2106                     !$(e.target).parents('.ui-datepicker-header').length &&
2107                     !$('.browse_foreign_modal.ui-dialog:visible').length &&
2108                     !$(e.target).closest('.dismissable').length
2109                 ) {
2110                     g.hideEditCell();
2111                 }
2112             }).on('keydown', function (e) {
2113                 if (e.which === 27 && g.isCellEditActive) {
2114                     // cancel on pressing "Esc"
2115                     g.hideEditCell(true);
2116                 }
2117             });
2118             $(g.o).find('div.save_edited').on('click', function () {
2119                 g.hideEditCell();
2120                 g.postEditedCell();
2121             });
2122             $(window).on('beforeunload', function () {
2123                 if (g.isCellEdited) {
2124                     return g.saveCellWarning;
2125                 }
2126             });
2128             // attach to global div
2129             $(g.gDiv).append(g.cEditStd);
2130             $(g.gDiv).append(g.cEditTextarea);
2132             // add hint for grid editing feature when hovering "Edit" link in each table row
2133             if (Messages.strGridEditFeatureHint !== undefined) {
2134                 Functions.tooltip(
2135                     $(g.t).find('.edit_row_anchor a'),
2136                     'a',
2137                     Messages.strGridEditFeatureHint
2138                 );
2139             }
2140         }
2141     };
2143     /** ****************
2144      * Initialize grid
2145      ******************/
2147     // wrap all truncated data cells with span indicating the original length
2148     // todo update the original length after a grid edit
2149     $(t).find('td.data.truncated:not(:has(span))')
2150         .wrapInner(function () {
2151             return '<span title="' + Messages.strOriginalLength + ' ' +
2152                 $(this).data('originallength') + '"></span>';
2153         });
2155     // wrap remaining cells, except actions cell, with span
2156     $(t).find('th, td:not(:has(span))')
2157         .wrapInner('<span></span>');
2159     // create grid elements
2160     g.gDiv = document.createElement('div');     // create global div
2162     // initialize the table variable
2163     g.t = t;
2165     // enclosing .sqlqueryresults div
2166     g.o = $(t).parents('.sqlqueryresults');
2168     // get data columns in the first row of the table
2169     var $firstRowCols = $(t).find('tr:first th.draggable');
2171     // initialize visible headers count
2172     g.visibleHeadersCount = $firstRowCols.filter(':visible').length;
2174     // assign first column (actions) span
2175     if (! $(t).find('tr:first th:first').hasClass('draggable')) {  // action header exist
2176         g.actionSpan = $(t).find('tr:first th:first').prop('colspan');
2177     } else {
2178         g.actionSpan = 0;
2179     }
2181     // assign table create time
2182     // table_create_time will only available if we are in "Browse" tab
2183     g.tableCreateTime = $(g.o).find('.table_create_time').val();
2185     // assign the hints
2186     g.sortHint = Messages.strSortHint;
2187     g.strMultiSortHint = Messages.strMultiSortHint;
2188     g.markHint = Messages.strColMarkHint;
2189     g.copyHint = Messages.strColNameCopyHint;
2191     // assign common hidden inputs
2192     var $commonHiddenInputs = $(g.o).find('div.common_hidden_inputs');
2193     g.server = $commonHiddenInputs.find('input[name=server]').val();
2194     g.db = $commonHiddenInputs.find('input[name=db]').val();
2195     g.table = $commonHiddenInputs.find('input[name=table]').val();
2197     // add table class
2198     $(t).addClass('pma_table');
2200     // add relative position to global div so that resize handlers are correctly positioned
2201     $(g.gDiv).css('position', 'relative');
2203     // link the global div
2204     $(t).before(g.gDiv);
2205     $(g.gDiv).append(t);
2207     // FEATURES
2208     if (isResizeEnabled) {
2209         g.initColResize();
2210     }
2211     // disable reordering for result from EXPLAIN or SHOW syntax, which do not have a table navigation panel
2212     if (isReorderEnabled &&
2213         $(g.o).find('table.navigation').length > 0) {
2214         g.initColReorder();
2215     }
2216     if (isVisibEnabled) {
2217         g.initColVisib();
2218     }
2219     // make sure we have the ajax class
2220     if (isGridEditEnabled &&
2221         $(t).is('.ajax')) {
2222         g.initGridEdit();
2223     }
2225     // create tooltip for each <th> with draggable class
2226     Functions.tooltip(
2227         $(t).find('th.draggable'),
2228         'th',
2229         g.updateHint()
2230     );
2232     // register events for hint tooltip (anchors inside draggable th)
2233     $(t).find('th.draggable a')
2234         .on('mouseenter', function () {
2235             g.showSortHint = true;
2236             g.showMultiSortHint = true;
2237             $(t).find('th.draggable').tooltip('option', {
2238                 content: g.updateHint()
2239             });
2240         })
2241         .on('mouseleave', function () {
2242             g.showSortHint = false;
2243             g.showMultiSortHint = false;
2244             $(t).find('th.draggable').tooltip('option', {
2245                 content: g.updateHint()
2246             });
2247         });
2249     // register events for dragging-related feature
2250     if (isResizeEnabled || isReorderEnabled) {
2251         $(document).on('mousemove', function (e) {
2252             g.dragMove(e);
2253         });
2254         $(document).on('mouseup', function (e) {
2255             $(g.o).removeClass('turnOffSelect');
2256             g.dragEnd(e);
2257         });
2258     }
2260     // some adjustment
2261     $(t).removeClass('data');
2262     $(g.gDiv).addClass('data');
2266  * jQuery plugin to cancel selection in HTML code.
2267  */
2268 (function ($) {
2269     $.fn.noSelect = function (p) { // no select plugin by Paulo P.Marinas
2270         var prevent = (p === null) ? true : p;
2271         var isMsie = navigator.userAgent.indexOf('MSIE') > -1 || !!window.navigator.userAgent.match(/Trident.*rv:11\./);
2272         var isFirefox = navigator.userAgent.indexOf('Firefox') > -1;
2273         var isSafari = navigator.userAgent.indexOf('Safari') > -1;
2274         var isOpera = navigator.userAgent.indexOf('Presto') > -1;
2275         if (prevent) {
2276             return this.each(function () {
2277                 if (isMsie || isSafari) {
2278                     $(this).on('selectstart', false);
2279                 } else if (isFirefox) {
2280                     $(this).css('MozUserSelect', 'none');
2281                     $('body').trigger('focus');
2282                 } else if (isOpera) {
2283                     $(this).on('mousedown', false);
2284                 } else {
2285                     $(this).attr('unselectable', 'on');
2286                 }
2287             });
2288         } else {
2289             return this.each(function () {
2290                 if (isMsie || isSafari) {
2291                     $(this).off('selectstart');
2292                 } else if (isFirefox) {
2293                     $(this).css('MozUserSelect', 'inherit');
2294                 } else if (isOpera) {
2295                     $(this).off('mousedown');
2296                 } else {
2297                     $(this).removeAttr('unselectable');
2298                 }
2299             });
2300         }
2301     }; // end noSelect
2302 }(jQuery));