1 /* vim: set expandtab sw=4 ts=4 sts=4: */
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
6 * @name Sortable Table JQuery plugin
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.
18 start: callback function when the user starts dragging
19 drop: callback function after an element has been dropped
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
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.
39 jQuery
.fn
.sortableTable = function(method
) {
42 init : function(options
) {
43 var tb
= new sortableTableInstance(this, options
);
45 $(this).data('sortableTable',tb
);
47 refresh : function( ) {
48 $(this).data('sortableTable').refresh();
50 destroy : function( ) {
51 $(this).data('sortableTable').destroy();
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
);
60 $.error( 'Method ' + method
+ ' does not exist on jQuery.sortableTable' );
63 function sortableTableInstance(table
, options
) {
65 var $draggedEl
, oldCell
, previewMove
, id
;
67 if(!options
) options
= {};
69 /* Mouse handlers on the child elements */
70 var onMouseUp = function(e
) {
71 dropAt(e
.pageX
, e
.pageY
);
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;
81 //move(e.pageX,e.pageY);
83 if(options
.events
&& options
.events
.start
)
84 options
.events
.start(this);
89 var globalMouseMove = function(e
) {
91 move(e
.pageX
,e
.pageY
);
93 if(inside($(oldCell
), e
.pageX
, e
.pageY
)) {
94 if(previewMove
!= null) {
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
119 var globalMouseOut = function() {
122 if(previewMove
) moveTo(previewMove
);
128 // Initialize sortable table
129 this.init = function() {
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
++));
139 $(table
).find('td').bind('mouseup',onMouseUp
);
140 $(table
).find('td').bind('mousedown',onMouseDown
);
142 $(document
).mousemove(globalMouseMove
);
143 $(document
).bind('mouseleave', globalMouseOut
);
146 // Call this when the table has been updated
147 this.refresh = function() {
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,''));
160 $(table
).find('td').unbind('mouseup',onMouseUp
)
161 $(table
).find('td').unbind('mousedown',onMouseDown
);
163 $(document
).unbind('mousemove',globalMouseMove
);
164 $(document
).unbind('mouseleave',globalMouseOut
);
167 function switchElement(drag
, dropTo
) {
169 left
: $(drag
).children().first().offset().left
- $(dropTo
).offset().left
,
170 top
: $(drag
).children().first().offset().top
- $(dropTo
).offset().top
173 var dropPosDiff
= null;
174 if($(dropTo
).children().length
> 0) {
176 left
: $(dropTo
).children().first().offset().left
- $(drag
).offset().left
,
177 top
: $(dropTo
).children().first().offset().top
- $(drag
).offset().top
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()
185 .bind('mouseup',onMouseUp
);
188 $(drag
).append($(dropTo
).children().first()).children()
189 .css('left',dropPosDiff
.left
+ 'px')
190 .css('top',dropPosDiff
.top
+ 'px');
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');
198 moveTo($(dropTo
).children().first(), { duration
: 100 });
199 moveTo($(drag
).children().first(), { duration
: 100 });
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.
205 // Calculate row and column index
206 colIdx
= $(dropTo
).prevAll().length
;
207 rowIdx
= $(dropTo
).parent().prevAll().length
;
209 options
.events
.drop(drag
,dropTo
, { col
: colIdx
, row
: rowIdx
});
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))
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();
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
;
229 function dropAt(x
,y
) {
233 var switched
= false;
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);
244 if(previewMove
) moveTo(previewMove
);
251 function moveTo(elem
, opts
) {
253 if(!opts
.pos
) opts
.pos
= { left
: 0, top
: 0 };
254 if(!opts
.duration
) opts
.duration
= 200;
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) {