jquery.sortableTable.js 8.1 KB

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