jquery.dragsort-0.5.1.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. // jQuery List DragSort v0.5.1
  2. // Website: http://dragsort.codeplex.com/
  3. // License: http://dragsort.codeplex.com/license
  4. (function($) {
  5. $.fn.dragsort = function(options) {
  6. if (options == "destroy") {
  7. $(this.selector).trigger("dragsort-uninit");
  8. return;
  9. }
  10. var opts = $.extend({}, $.fn.dragsort.defaults, options);
  11. var lists = [];
  12. var list = null, lastPos = null;
  13. this.each(function(i, cont) {
  14. //if list container is table, the browser automatically wraps rows in tbody if not specified so change list container to tbody so that children returns rows as user expected
  15. if ($(cont).is("table") && $(cont).children().size() == 1 && $(cont).children().is("tbody"))
  16. cont = $(cont).children().get(0);
  17. var newList = {
  18. draggedItem: null,
  19. placeHolderItem: null,
  20. pos: null,
  21. offset: null,
  22. offsetLimit: null,
  23. scroll: null,
  24. container: cont,
  25. init: function() {
  26. //set options to default values if not set
  27. var tagName = $(this.container).children().size() == 0 ? "li" : $(this.container).children(":first").get(0).tagName.toLowerCase();
  28. if (opts.itemSelector == "")
  29. opts.itemSelector = tagName;
  30. if (opts.dragSelector == "")
  31. opts.dragSelector = tagName;
  32. if (opts.placeHolderTemplate == "")
  33. opts.placeHolderTemplate = "<" + tagName + ">&nbsp;</" + tagName + ">";
  34. //listidx allows reference back to correct list variable instance
  35. $(this.container).attr("data-listidx", i).mousedown(this.grabItem).bind("dragsort-uninit", this.uninit);
  36. this.styleDragHandlers(true);
  37. },
  38. uninit: function() {
  39. var list = lists[$(this).attr("data-listidx")];
  40. $(list.container).unbind("mousedown", list.grabItem).unbind("dragsort-uninit");
  41. list.styleDragHandlers(false);
  42. },
  43. getItems: function() {
  44. return $(this.container).children(opts.itemSelector);
  45. },
  46. styleDragHandlers: function(cursor) {
  47. this.getItems().map(function() { return $(this).is(opts.dragSelector) ? this : $(this).find(opts.dragSelector).get(); }).css("cursor", cursor ? "pointer" : "");
  48. },
  49. grabItem: function(e) {
  50. //if not left click or if clicked on excluded element (e.g. text box) or not a moveable list item return
  51. if (e.which != 1 || $(e.target).is(opts.dragSelectorExclude) || $(e.target).closest(opts.dragSelectorExclude).size() > 0 || $(e.target).closest(opts.itemSelector).size() == 0)
  52. return;
  53. //prevents selection, stops issue on Fx where dragging hyperlink doesn't work and on IE where it triggers mousemove even though mouse hasn't moved,
  54. //does also stop being able to click text boxes hence dragging on text boxes by default is disabled in dragSelectorExclude
  55. e.preventDefault();
  56. //change cursor to move while dragging
  57. var dragHandle = e.target;
  58. while (!$(dragHandle).is(opts.dragSelector)) {
  59. if (dragHandle == this) return;
  60. dragHandle = dragHandle.parentNode;
  61. }
  62. $(dragHandle).attr("data-cursor", $(dragHandle).css("cursor"));
  63. $(dragHandle).css("cursor", "move");
  64. //on mousedown wait for movement of mouse before triggering dragsort script (dragStart) to allow clicking of hyperlinks to work
  65. var list = lists[$(this).attr("data-listidx")];
  66. var item = this;
  67. var trigger = function() {
  68. list.dragStart.call(item, e);
  69. $(list.container).unbind("mousemove", trigger);
  70. };
  71. $(list.container).mousemove(trigger).mouseup(function() { $(list.container).unbind("mousemove", trigger); $(dragHandle).css("cursor", $(dragHandle).attr("data-cursor")); });
  72. },
  73. dragStart: function(e) {
  74. if (list != null && list.draggedItem != null)
  75. list.dropItem();
  76. list = lists[$(this).attr("data-listidx")];
  77. list.draggedItem = $(e.target).closest(opts.itemSelector);
  78. //record current position so on dragend we know if the dragged item changed position or not
  79. list.draggedItem.attr("data-origpos", $(this).attr("data-listidx") + "-" + list.getItems().index(list.draggedItem));
  80. //calculate mouse offset relative to draggedItem
  81. var mt = parseInt(list.draggedItem.css("marginTop"));
  82. var ml = parseInt(list.draggedItem.css("marginLeft"));
  83. list.offset = list.draggedItem.offset();
  84. list.offset.top = e.pageY - list.offset.top + (isNaN(mt) ? 0 : mt) - 1;
  85. list.offset.left = e.pageX - list.offset.left + (isNaN(ml) ? 0 : ml) - 1;
  86. //calculate box the dragged item can't be dragged outside of
  87. if (!opts.dragBetween) {
  88. var containerHeight = $(list.container).outerHeight() == 0 ? Math.max(1, Math.round(0.5 + list.getItems().size() * list.draggedItem.outerWidth() / $(list.container).outerWidth())) * list.draggedItem.outerHeight() : $(list.container).outerHeight();
  89. list.offsetLimit = $(list.container).offset();
  90. list.offsetLimit.right = list.offsetLimit.left + $(list.container).outerWidth() - list.draggedItem.outerWidth();
  91. list.offsetLimit.bottom = list.offsetLimit.top + containerHeight - list.draggedItem.outerHeight();
  92. }
  93. //create placeholder item
  94. var h = list.draggedItem.height();
  95. var w = list.draggedItem.width();
  96. if (opts.itemSelector == "tr") {
  97. list.draggedItem.children().each(function() { $(this).width($(this).width()); });
  98. list.placeHolderItem = list.draggedItem.clone().attr("data-placeholder", true);
  99. list.draggedItem.after(list.placeHolderItem);
  100. list.placeHolderItem.children().each(function() { $(this).css({ borderWidth:0, width: $(this).width() + 1, height: $(this).height() + 1 }).html("&nbsp;"); });
  101. } else {
  102. list.draggedItem.after(opts.placeHolderTemplate);
  103. list.placeHolderItem = list.draggedItem.next().css({ height: h, width: w }).attr("data-placeholder", true);
  104. }
  105. if (opts.itemSelector == "td") {
  106. var listTable = list.draggedItem.closest("table").get(0);
  107. $("<table id='" + listTable.id + "' style='border-width: 0px;' class='dragSortItem " + listTable.className + "'><tr></tr></table>").appendTo("body").children().append(list.draggedItem);
  108. }
  109. //style draggedItem while dragging
  110. var orig = list.draggedItem.attr("style");
  111. list.draggedItem.attr("data-origstyle", orig ? orig : "");
  112. list.draggedItem.css({ position: "absolute", opacity: 0.8, "z-index": 999, height: h, width: w });
  113. //auto-scroll setup
  114. list.scroll = { moveX: 0, moveY: 0, maxX: $(document).width() - $(window).width(), maxY: $(document).height() - $(window).height() };
  115. list.scroll.scrollY = window.setInterval(function() {
  116. if (opts.scrollContainer != window) {
  117. $(opts.scrollContainer).scrollTop($(opts.scrollContainer).scrollTop() + list.scroll.moveY);
  118. return;
  119. }
  120. var t = $(opts.scrollContainer).scrollTop();
  121. if (list.scroll.moveY > 0 && t < list.scroll.maxY || list.scroll.moveY < 0 && t > 0) {
  122. $(opts.scrollContainer).scrollTop(t + list.scroll.moveY);
  123. list.draggedItem.css("top", list.draggedItem.offset().top + list.scroll.moveY + 1);
  124. }
  125. }, 10);
  126. list.scroll.scrollX = window.setInterval(function() {
  127. if (opts.scrollContainer != window) {
  128. $(opts.scrollContainer).scrollLeft($(opts.scrollContainer).scrollLeft() + list.scroll.moveX);
  129. return;
  130. }
  131. var l = $(opts.scrollContainer).scrollLeft();
  132. if (list.scroll.moveX > 0 && l < list.scroll.maxX || list.scroll.moveX < 0 && l > 0) {
  133. $(opts.scrollContainer).scrollLeft(l + list.scroll.moveX);
  134. list.draggedItem.css("left", list.draggedItem.offset().left + list.scroll.moveX + 1);
  135. }
  136. }, 10);
  137. //misc
  138. $(lists).each(function(i, l) { l.createDropTargets(); l.buildPositionTable(); });
  139. list.setPos(e.pageX, e.pageY);
  140. $(document).bind("mousemove", list.swapItems);
  141. $(document).bind("mouseup", list.dropItem);
  142. if (opts.scrollContainer != window)
  143. $(window).bind("DOMMouseScroll mousewheel", list.wheel);
  144. },
  145. //set position of draggedItem
  146. setPos: function(x, y) {
  147. //remove mouse offset so mouse cursor remains in same place on draggedItem instead of top left corner
  148. var top = y - this.offset.top;
  149. var left = x - this.offset.left;
  150. //limit top, left to within box draggedItem can't be dragged outside of
  151. if (!opts.dragBetween) {
  152. top = Math.min(this.offsetLimit.bottom, Math.max(top, this.offsetLimit.top));
  153. left = Math.min(this.offsetLimit.right, Math.max(left, this.offsetLimit.left));
  154. }
  155. //adjust top, left calculations to parent element instead of window if it's relative or absolute
  156. this.draggedItem.parents().each(function() {
  157. if ($(this).css("position") != "static" && (!$.browser.mozilla || $(this).css("display") != "table")) {
  158. var offset = $(this).offset();
  159. top -= offset.top;
  160. left -= offset.left;
  161. return false;
  162. }
  163. });
  164. //set x or y auto-scroll amount
  165. if (opts.scrollContainer == window) {
  166. y -= $(window).scrollTop();
  167. x -= $(window).scrollLeft();
  168. y = Math.max(0, y - $(window).height() + 5) + Math.min(0, y - 5);
  169. x = Math.max(0, x - $(window).width() + 5) + Math.min(0, x - 5);
  170. } else {
  171. var cont = $(opts.scrollContainer);
  172. var offset = cont.offset();
  173. y = Math.max(0, y - cont.height() - offset.top) + Math.min(0, y - offset.top);
  174. x = Math.max(0, x - cont.width() - offset.left) + Math.min(0, x - offset.left);
  175. }
  176. list.scroll.moveX = x == 0 ? 0 : x * opts.scrollSpeed / Math.abs(x);
  177. list.scroll.moveY = y == 0 ? 0 : y * opts.scrollSpeed / Math.abs(y);
  178. //move draggedItem to new mouse cursor location
  179. this.draggedItem.css({ top: top, left: left });
  180. },
  181. //if scroll container is a div allow mouse wheel to scroll div instead of window when mouse is hovering over
  182. wheel: function(e) {
  183. if (($.browser.safari || $.browser.mozilla) && list && opts.scrollContainer != window) {
  184. var cont = $(opts.scrollContainer);
  185. var offset = cont.offset();
  186. if (e.pageX > offset.left && e.pageX < offset.left + cont.width() && e.pageY > offset.top && e.pageY < offset.top + cont.height()) {
  187. var delta = e.detail ? e.detail * 5 : e.wheelDelta / -2;
  188. cont.scrollTop(cont.scrollTop() + delta);
  189. e.preventDefault();
  190. }
  191. }
  192. },
  193. //build a table recording all the positions of the moveable list items
  194. buildPositionTable: function() {
  195. var pos = [];
  196. this.getItems().not([list.draggedItem[0], list.placeHolderItem[0]]).each(function(i) {
  197. var loc = $(this).offset();
  198. loc.right = loc.left + $(this).outerWidth();
  199. loc.bottom = loc.top + $(this).outerHeight();
  200. loc.elm = this;
  201. pos[i] = loc;
  202. });
  203. this.pos = pos;
  204. },
  205. dropItem: function() {
  206. if (list.draggedItem == null)
  207. return;
  208. //list.draggedItem.attr("style", "") doesn't work on IE8 and jQuery 1.5 or lower
  209. //list.draggedItem.removeAttr("style") doesn't work on chrome and jQuery 1.6 (works jQuery 1.5 or lower)
  210. var orig = list.draggedItem.attr("data-origstyle");
  211. list.draggedItem.attr("style", orig);
  212. if (orig == "")
  213. list.draggedItem.removeAttr("style");
  214. list.draggedItem.removeAttr("data-origstyle");
  215. list.styleDragHandlers(true);
  216. list.placeHolderItem.before(list.draggedItem);
  217. list.placeHolderItem.remove();
  218. $("[data-droptarget], .dragSortItem").remove();
  219. window.clearInterval(list.scroll.scrollY);
  220. window.clearInterval(list.scroll.scrollX);
  221. //if position changed call dragEnd
  222. if (list.draggedItem.attr("data-origpos") != $(lists).index(list) + "-" + list.getItems().index(list.draggedItem))
  223. opts.dragEnd.apply(list.draggedItem);
  224. list.draggedItem.removeAttr("data-origpos");
  225. list.draggedItem = null;
  226. $(document).unbind("mousemove", list.swapItems);
  227. $(document).unbind("mouseup", list.dropItem);
  228. if (opts.scrollContainer != window)
  229. $(window).unbind("DOMMouseScroll mousewheel", list.wheel);
  230. return false;
  231. },
  232. //swap the draggedItem (represented visually by placeholder) with the list item the it has been dragged on top of
  233. swapItems: function(e) {
  234. if (list.draggedItem == null)
  235. return false;
  236. //move draggedItem to mouse location
  237. list.setPos(e.pageX, e.pageY);
  238. //retrieve list and item position mouse cursor is over
  239. var ei = list.findPos(e.pageX, e.pageY);
  240. var nlist = list;
  241. for (var i = 0; ei == -1 && opts.dragBetween && i < lists.length; i++) {
  242. ei = lists[i].findPos(e.pageX, e.pageY);
  243. nlist = lists[i];
  244. }
  245. //if not over another moveable list item return
  246. if (ei == -1)
  247. return false;
  248. //save fixed items locations
  249. var children = function() { return $(nlist.container).children().not(nlist.draggedItem); };
  250. var fixed = children().not(opts.itemSelector).each(function(i) { this.idx = children().index(this); });
  251. //if moving draggedItem up or left place placeHolder before list item the dragged item is hovering over otherwise place it after
  252. if (lastPos == null || lastPos.top > list.draggedItem.offset().top || lastPos.left > list.draggedItem.offset().left)
  253. $(nlist.pos[ei].elm).before(list.placeHolderItem);
  254. else
  255. $(nlist.pos[ei].elm).after(list.placeHolderItem);
  256. //restore fixed items location
  257. fixed.each(function() {
  258. var elm = children().eq(this.idx).get(0);
  259. if (this != elm && children().index(this) < this.idx)
  260. $(this).insertAfter(elm);
  261. else if (this != elm)
  262. $(this).insertBefore(elm);
  263. });
  264. //misc
  265. $(lists).each(function(i, l) { l.createDropTargets(); l.buildPositionTable(); });
  266. lastPos = list.draggedItem.offset();
  267. return false;
  268. },
  269. //returns the index of the list item the mouse is over
  270. findPos: function(x, y) {
  271. for (var i = 0; i < this.pos.length; i++) {
  272. if (this.pos[i].left < x && this.pos[i].right > x && this.pos[i].top < y && this.pos[i].bottom > y)
  273. return i;
  274. }
  275. return -1;
  276. },
  277. //create drop targets which are placeholders at the end of other lists to allow dragging straight to the last position
  278. createDropTargets: function() {
  279. if (!opts.dragBetween)
  280. return;
  281. $(lists).each(function() {
  282. var ph = $(this.container).find("[data-placeholder]");
  283. var dt = $(this.container).find("[data-droptarget]");
  284. if (ph.size() > 0 && dt.size() > 0)
  285. dt.remove();
  286. else if (ph.size() == 0 && dt.size() == 0) {
  287. if (opts.itemSelector == "td")
  288. $(opts.placeHolderTemplate).attr("data-droptarget", true).appendTo(this.container);
  289. else
  290. //list.placeHolderItem.clone().removeAttr("data-placeholder") crashes in IE7 and jquery 1.5.1 (doesn't in jquery 1.4.2 or IE8)
  291. $(this.container).append(list.placeHolderItem.removeAttr("data-placeholder").clone().attr("data-droptarget", true));
  292. list.placeHolderItem.attr("data-placeholder", true);
  293. }
  294. });
  295. }
  296. };
  297. newList.init();
  298. lists.push(newList);
  299. });
  300. return this;
  301. };
  302. $.fn.dragsort.defaults = {
  303. itemSelector: "",
  304. dragSelector: "",
  305. dragSelectorExclude: "input, textarea",
  306. dragEnd: function() { },
  307. dragBetween: false,
  308. placeHolderTemplate: "",
  309. scrollContainer: window,
  310. scrollSpeed: 5
  311. };
  312. })(jQuery);