Merge branch 'QA_3_4'
[phpmyadmin.git] / js / jquery / jquery.sortableTable.js
blob1f4fc91db836029edacf1f243741714d9d716b3a
1 /* vim: set expandtab sw=4 ts=4 sts=4: */
2 /**
3  * @fileoverview    A jquery plugin that allows drag&drop sorting in tables.
4  *                  Coded because JQuery UI sortable doesn't support tables. Also it has no animation
5  *
6  * @name            Sortable Table JQuery plugin
7  *
8  * @requires    jQuery
9  *
10  */
12 /* Options:
14 $('table').sortableTable({
15     ignoreRect: { top, left, width, height }  - relative coordinates on each element. If the user clicks 
16                                                in this area, it is not seen as a drag&drop request. Useful for toolbars etc.
17     events: {
18        start: callback function when the user starts dragging
19        drop: callback function after an element has been dropped
20     }
24 /* Commands:
26 $('table').sortableTable('init')      - equivalent to $('table').sortableTable()
27 $('table').sortableTable('refresh')   - if the table has been changed, refresh correctly assigns all events again
28 $('table').sortableTable('destroy')   - removes all events from the table
30 */ 
32 /* Setup: 
34   Can be applied on any table, there is just one convention. 
35   Each cell (<td>) has to contain one and only one element (preferably div or span) 
36   which is the actually draggable element.
38 (function($) {
39         jQuery.fn.sortableTable = function(method) {
40         
41                 var methods = {
42                         init : function(options) {
43                                 var tb = new sortableTableInstance(this, options);
44                                 tb.init();
45                                 $(this).data('sortableTable',tb);
46                         },
47                         refresh : function( ) { 
48                                 $(this).data('sortableTable').refresh();
49                         },
50                         destroy : function( ) { 
51                                 $(this).data('sortableTable').destroy();
52                         }
53                 };
55                 if ( methods[method] ) {
56                         return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
57                 } else if ( typeof method === 'object' || ! method ) {
58                         return methods.init.apply( this, arguments );
59                 } else {
60                         $.error( 'Method ' +  method + ' does not exist on jQuery.sortableTable' );
61                 }    
62         
63                 function sortableTableInstance(table, options) {
64                         var down = false;
65                         var     $draggedEl, oldCell, previewMove, id;
66                         
67                         if(!options) options = {};
68                         
69                         /* Mouse handlers on the child elements */
70                         var onMouseUp = function(e) { 
71                                 dropAt(e.pageX, e.pageY); 
72                         }
73                         
74                         var onMouseDown = function(e) {
75                                 $draggedEl = $(this).children();
76                                 if($draggedEl.length == 0) return;
77                                 if(options.ignoreRect && insideRect({x: e.pageX - $draggedEl.offset().left, y: e.pageY - $draggedEl.offset().top}, options.ignoreRect)) return;
78                                 
79                                 down = true;
80                                 oldCell = this;
81                                 //move(e.pageX,e.pageY);
82                                 
83                                 if(options.events && options.events.start)
84                                         options.events.start(this);
86                                 return false;
87                         }
88                         
89                         var globalMouseMove = function(e) {
90                                 if(down) {
91                                         move(e.pageX,e.pageY);
93                                         if(inside($(oldCell), e.pageX, e.pageY)) {
94                                                 if(previewMove != null) {
95                                                         moveTo(previewMove);
96                                                         previewMove = null;                                     
97                                                 }
98                                         } else
99                                                 $(table).find('td').each(function() {
100                                                         if(inside($(this), e.pageX, e.pageY)) {
101                                                                 if($(previewMove).attr('class') != $(this).children().first().attr('class')) {
102                                                                         if(previewMove != null) moveTo(previewMove);
103                                                                         previewMove = $(this).children().first();
104                                                                         if(previewMove.length > 0)
105                                                                                 moveTo($(previewMove), { pos: {
106                                                                                         top: $(oldCell).offset().top - $(previewMove).parent().offset().top,
107                                                                                         left: $(oldCell).offset().left - $(previewMove).parent().offset().left
108                                                                                 } });
109                                                                 }
110                                                                 
111                                                                 return false;
112                                                         }
113                                                 });
114                                 }
115                                 
116                                 return false;
117                         }
118                         
119                         var globalMouseOut = function() {
120                                 if(down) {
121                                         down = false;
122                                         if(previewMove) moveTo(previewMove);
123                                         moveTo($draggedEl);
124                                         previewMove = null;
125                                 }
126                         }
127                         
128                         // Initialize sortable table
129                         this.init = function() {
130                                 id = 1;
131                                 // Add some required css to each child element in the <td>s
132                                 $(table).find('td').children().each(function() {
133                                         // Remove any old occurences of our added draggable-num class
134                                         $(this).attr('class',$(this).attr('class').replace(/\s*draggable\-\d+/g,''));
135                                         $(this).addClass('draggable-' + (id++));
136                                 });
137                                 
138                                 // Mouse events
139                                 $(table).find('td').bind('mouseup',onMouseUp);
140                                 $(table).find('td').bind('mousedown',onMouseDown);
142                                 $(document).mousemove(globalMouseMove);
143                                 $(document).bind('mouseleave', globalMouseOut);
144                         }
145                         
146                         // Call this when the table has been updated
147                         this.refresh = function() {
148                                 this.destroy();
149                                 this.init();
150                         }
151                         
152                         this.destroy = function() {
153                                 // Add some required css to each child element in the <td>s
154                                 $(table).find('td').children().each(function() {
155                                         // Remove any old occurences of our added draggable-num class
156                                         $(this).attr('class',$(this).attr('class').replace(/\s*draggable\-\d+/g,''));
157                                 });
158                                 
159                                 // Mouse events
160                                 $(table).find('td').unbind('mouseup',onMouseUp)
161                                 $(table).find('td').unbind('mousedown',onMouseDown);
162                                         
163                                 $(document).unbind('mousemove',globalMouseMove);
164                                 $(document).unbind('mouseleave',globalMouseOut);
165                         }
166                         
167                         function switchElement(drag, dropTo) {
168                                 var dragPosDiff = { 
169                                         left: $(drag).children().first().offset().left - $(dropTo).offset().left, 
170                                         top:  $(drag).children().first().offset().top - $(dropTo).offset().top 
171                                 };
172                                 
173                                 var dropPosDiff = null;
174                                 if($(dropTo).children().length > 0) {
175                                         dropPosDiff = {
176                                                 left: $(dropTo).children().first().offset().left - $(drag).offset().left,
177                                                 top:  $(dropTo).children().first().offset().top - $(drag).offset().top 
178                                         };
179                                 }
180                                 
181                                 /* I love you append(). It moves the DOM Elements so gracefully <3 */
182                                 // Put the element in the way to old place
183                                 $(drag).append($(dropTo).children().first()).children()
184                                         .stop(true,true)
185                                         .bind('mouseup',onMouseUp);
186                                 
187                                 if(dropPosDiff)
188                                         $(drag).append($(dropTo).children().first()).children()
189                                                 .css('left',dropPosDiff.left + 'px')
190                                                 .css('top',dropPosDiff.top + 'px');
191                                         
192                                 // Put our dragged element into the space we just freed up
193                                 $(dropTo).append($(drag).children().first()).children()
194                                         .bind('mouseup',onMouseUp)
195                                         .css('left',dragPosDiff.left + 'px')
196                                         .css('top',dragPosDiff.top + 'px');
197                                 
198                                 moveTo($(dropTo).children().first(), { duration: 100 });
199                                 moveTo($(drag).children().first(), { duration: 100 });
200                                         
201                                 if(options.events && options.events.drop) {
202                                         // Drop event. The drag child element is moved into the drop element
203                                         // and vice versa. So the parameters are switched.
204                                         
205                                         // Calculate row and column index
206                                         colIdx = $(dropTo).prevAll().length;
207                                         rowIdx = $(dropTo).parent().prevAll().length;
208                                         
209                                         options.events.drop(drag,dropTo, { col: colIdx, row: rowIdx });
210                                 }
211                         }
212                         
213                         function move(x,y) {
214                                 $draggedEl.offset({
215                                         top: Math.min($(document).height(), Math.max(0, y - $draggedEl.height()/2)), 
216                                         left: Math.min($(document).width(), Math.max(0, x - $draggedEl.width()/2))
217                                 });
218                         }
219                         
220                         function inside($el, x,y) {
221                                 var off = $el.offset();
222                                 return y >= off.top && x >= off.left && x < off.left + $el.width() && y < off.top + $el.height();
223                         }
224                         
225                         function insideRect(pos, r) {
226                                 return pos.y > r.top && pos.x > r.left && pos.y < r.top + r.height && pos.x < r.left + r.width;
227                         }
228                         
229                         function dropAt(x,y) {
230                                 if(!down) return;
231                                 down = false;
232                                 
233                                 var switched = false;
234                                 
235                                 $(table).find('td').each(function() {
236                                         if($(this).children().first().attr('class') != $(oldCell).children().first().attr('class') && inside($(this), x, y)) {
237                                                 switchElement(oldCell, this);
238                                                 switched = true;
239                                                 return;
240                                         }
241                                 });
243                                 if(!switched) {
244                                         if(previewMove) moveTo(previewMove);
245                                         moveTo($draggedEl);
246                                 }
247                                 
248                                 previewMove = null;
249                         }
250                         
251                         function moveTo(elem, opts) {
252                                 if(!opts) opts = {};
253                                 if(!opts.pos) opts.pos = { left: 0, top: 0 };
254                                 if(!opts.duration) opts.duration = 200;
255                                 
256                                 $(elem).css('position','relative');
257                                 $(elem).animate({ top: opts.pos.top, left: opts.pos.left }, {
258                                         duration: opts.duration,
259                                         complete: function() {
260                                                 if(opts.pos.left == 0 && opts.pos.top == 0) {
261                                                         $(elem)
262                                                                 .css('position','')
263                                                                 .css('left','')
264                                                                 .css('top','');
265                                                 }
266                                         }
267                                 });
268                         }
269                 }
270         }
271         
272 })( jQuery );