2 * This file is internal to phpMyAdmin.
3 * @license see the main phpMyAdmin license.
5 * @fileoverview A jquery plugin that allows drag&drop sorting in tables.
6 * Coded because JQuery UI sortable doesn't support tables. Also it has no animation
8 * @name Sortable Table JQuery plugin
16 * $('table').sortableTable({
17 * ignoreRect: { top, left, width, height } - Relative coordinates on each element. If the user clicks
18 * in this area, it is not seen as a drag&drop request. Useful for toolbars etc.
20 * start: callback function when the user starts dragging
21 * drop: callback function after an element has been dropped
29 * $('table').sortableTable('init') - equivalent to $('table').sortableTable()
30 * $('table').sortableTable('refresh') - if the table has been changed, refresh correctly assigns all events again
31 * $('table').sortableTable('destroy') - removes all events from the table
37 * Can be applied on any table, there is just one convention.
38 * Each cell (<td>) has to contain one and only one element (preferably div or span)
39 * which is the actually draggable element.
42 jQuery.fn.sortableTable = function (method) {
44 init: function (options) {
45 var tb = new SortableTableInstance(this, options);
47 $(this).data('sortableTable', tb);
49 refresh: function () {
50 $(this).data('sortableTable').refresh();
52 destroy: function () {
53 $(this).data('sortableTable').destroy();
57 if (methods[method]) {
58 return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
59 } else if (typeof method === 'object' || !method) {
60 return methods.init.apply(this, arguments);
62 $.error('Method ' + method + ' does not exist on jQuery.sortableTable');
65 function SortableTableInstance (table, options = {}) {
72 /* Mouse handlers on the child elements */
73 var onMouseUp = function (e) {
74 dropAt(e.pageX, e.pageY);
77 var onMouseDown = function (e) {
78 $draggedEl = $(this).children();
79 if ($draggedEl.length === 0) {
82 if (options.ignoreRect && insideRect({ x: e.pageX - $draggedEl.offset().left, y: e.pageY - $draggedEl.offset().top }, options.ignoreRect)) {
89 if (options.events && options.events.start) {
90 options.events.start(this);
96 var globalMouseMove = function (e) {
98 move(e.pageX, e.pageY);
100 if (inside($(oldCell), e.pageX, e.pageY)) {
101 if (previewMove !== null) {
106 $(table).find('td').each(function () {
107 if (inside($(this), e.pageX, e.pageY)) {
108 if ($(previewMove).attr('class') !== $(this).children().first().attr('class')) {
109 if (previewMove !== null) {
112 previewMove = $(this).children().first();
113 if (previewMove.length > 0) {
114 moveTo($(previewMove), {
116 top: $(oldCell).offset().top - $(previewMove).parent().offset().top,
117 left: $(oldCell).offset().left - $(previewMove).parent().offset().left
132 var globalMouseOut = function () {
143 // Initialize sortable table
144 this.init = function () {
146 // Add some required css to each child element in the <td>s
147 $(table).find('td').children().each(function () {
148 // Remove any old occurrences of our added draggable-num class
149 $(this).attr('class', $(this).attr('class').replace(/\s*draggable-\d+/g, ''));
150 $(this).addClass('draggable-' + (id++));
154 $(table).find('td').on('mouseup', onMouseUp);
155 $(table).find('td').on('mousedown', onMouseDown);
157 $(document).on('mousemove', globalMouseMove);
158 $(document).on('mouseleave', globalMouseOut);
161 // Call this when the table has been updated
162 this.refresh = function () {
167 this.destroy = function () {
168 // Add some required css to each child element in the <td>s
169 $(table).find('td').children().each(function () {
170 // Remove any old occurrences of our added draggable-num class
171 $(this).attr('class', $(this).attr('class').replace(/\s*draggable-\d+/g, ''));
175 $(table).find('td').off('mouseup', onMouseUp);
176 $(table).find('td').off('mousedown', onMouseDown);
178 $(document).off('mousemove', globalMouseMove);
179 $(document).off('mouseleave', globalMouseOut);
182 function switchElement (drag, dropTo) {
184 left: $(drag).children().first().offset().left - $(dropTo).offset().left,
185 top: $(drag).children().first().offset().top - $(dropTo).offset().top
188 var dropPosDiff = null;
189 if ($(dropTo).children().length > 0) {
191 left: $(dropTo).children().first().offset().left - $(drag).offset().left,
192 top: $(dropTo).children().first().offset().top - $(drag).offset().top
196 /* I love you append(). It moves the DOM Elements so gracefully <3 */
197 // Put the element in the way to old place
198 $(drag).append($(dropTo).children().first()).children()
200 .on('mouseup', onMouseUp);
203 $(drag).append($(dropTo).children().first()).children()
204 .css('left', dropPosDiff.left + 'px')
205 .css('top', dropPosDiff.top + 'px');
208 // Put our dragged element into the space we just freed up
209 $(dropTo).append($(drag).children().first()).children()
210 .on('mouseup', onMouseUp)
211 .css('left', dragPosDiff.left + 'px')
212 .css('top', dragPosDiff.top + 'px');
214 moveTo($(dropTo).children().first(), { duration: 100 });
215 moveTo($(drag).children().first(), { duration: 100 });
217 if (options.events && options.events.drop) {
218 // Drop event. The drag child element is moved into the drop element
219 // and vice versa. So the parameters are switched.
221 // Calculate row and column index
222 const colIdx = $(dropTo).prevAll().length;
223 const rowIdx = $(dropTo).parent().prevAll().length;
225 options.events.drop(drag, dropTo, { col: colIdx, row: rowIdx });
229 function move (x, y) {
231 top: Math.min($(document).height(), Math.max(0, y - $draggedEl.height() / 2)),
232 left: Math.min($(document).width(), Math.max(0, x - $draggedEl.width() / 2))
236 function inside ($el, x, y) {
237 var off = $el.offset();
238 return y >= off.top && x >= off.left && x < off.left + $el.width() && y < off.top + $el.height();
241 function insideRect (pos, r) {
242 return pos.y > r.top && pos.x > r.left && pos.y < r.top + r.height && pos.x < r.left + r.width;
245 function dropAt (x, y) {
251 var switched = false;
253 $(table).find('td').each(function () {
254 if ($(this).children().first().attr('class') !== $(oldCell).children().first().attr('class') && inside($(this), x, y)) {
255 switchElement(oldCell, this);
270 function moveTo (elem, opts = {}) {
272 opts.pos = { left: 0, top: 0 };
274 if (!opts.duration) {
278 $(elem).css('position', 'relative');
279 $(elem).animate({ top: opts.pos.top, left: opts.pos.left }, {
280 duration: opts.duration,
281 complete: function () {
282 if (opts.pos.left === 0 && opts.pos.top === 0) {