drag.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. /* Drag and drop tools by Tim Taylor. Code is under public license.
  2. http://tool-man.org/examples/sorting.html */
  3. var Coordinates = {
  4. ORIGIN : new Coordinate(0, 0),
  5. northwestPosition : function(element) {
  6. var x = parseInt(element.style.left);
  7. var y = parseInt(element.style.top);
  8. return new Coordinate(isNaN(x) ? 0 : x, isNaN(y) ? 0 : y);
  9. },
  10. southeastPosition : function(element) {
  11. return Coordinates.northwestPosition(element).plus(
  12. new Coordinate(element.offsetWidth, element.offsetHeight));
  13. },
  14. northwestOffset : function(element, isRecursive) {
  15. var offset = new Coordinate(element.offsetLeft, element.offsetTop);
  16. if (!isRecursive) return offset;
  17. var parent = element.offsetParent;
  18. while (parent) {
  19. offset = offset.plus(
  20. new Coordinate(parent.offsetLeft, parent.offsetTop));
  21. parent = parent.offsetParent;
  22. }
  23. return offset;
  24. },
  25. southeastOffset : function(element, isRecursive) {
  26. return Coordinates.northwestOffset(element, isRecursive).plus(
  27. new Coordinate(element.offsetWidth, element.offsetHeight));
  28. },
  29. fixEvent : function(event) {
  30. event.windowCoordinate = new Coordinate(event.clientX, event.clientY);
  31. }
  32. };
  33. function Coordinate(x, y) {
  34. this.x = x;
  35. this.y = y;
  36. }
  37. Coordinate.prototype.toString = function() {
  38. return "(" + this.x + "," + this.y + ")";
  39. }
  40. Coordinate.prototype.plus = function(that) {
  41. return new Coordinate(this.x + that.x, this.y + that.y);
  42. }
  43. Coordinate.prototype.minus = function(that) {
  44. return new Coordinate(this.x - that.x, this.y - that.y);
  45. }
  46. Coordinate.prototype.distance = function(that) {
  47. var deltaX = this.x - that.x;
  48. var deltaY = this.y - that.y;
  49. return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
  50. }
  51. Coordinate.prototype.max = function(that) {
  52. var x = Math.max(this.x, that.x);
  53. var y = Math.max(this.y, that.y);
  54. return new Coordinate(x, y);
  55. }
  56. Coordinate.prototype.constrain = function(min, max) {
  57. if (min.x > max.x || min.y > max.y) return this;
  58. var x = this.x;
  59. var y = this.y;
  60. if (min.x != null) x = Math.max(x, min.x);
  61. if (max.x != null) x = Math.min(x, max.x);
  62. if (min.y != null) y = Math.max(y, min.y);
  63. if (max.y != null) y = Math.min(y, max.y);
  64. return new Coordinate(x, y);
  65. }
  66. Coordinate.prototype.reposition = function(element) {
  67. element.style["top"] = this.y + "px";
  68. element.style["left"] = this.x + "px";
  69. }
  70. Coordinate.prototype.equals = function(that) {
  71. if (this == that) return true;
  72. if (!that || that == null) return false;
  73. return this.x == that.x && this.y == that.y;
  74. }
  75. /*
  76. * drag.js - click & drag DOM elements
  77. *
  78. * originally based on Youngpup's dom-drag.js, www.youngpup.net
  79. */
  80. var Drag = {
  81. BIG_Z_INDEX : 10000,
  82. group : null,
  83. isDragging : false,
  84. makeDraggable : function(group) {
  85. group.handle = group;
  86. group.handle.group = group;
  87. group.minX = null;
  88. group.minY = null;
  89. group.maxX = null;
  90. group.maxY = null;
  91. group.threshold = 0;
  92. group.thresholdY = 0;
  93. group.thresholdX = 0;
  94. group.onDragStart = new Function();
  95. group.onDragEnd = new Function();
  96. group.onDrag = new Function();
  97. // TODO: use element.prototype.myFunc
  98. group.setDragHandle = Drag.setDragHandle;
  99. group.setDragThreshold = Drag.setDragThreshold;
  100. group.setDragThresholdX = Drag.setDragThresholdX;
  101. group.setDragThresholdY = Drag.setDragThresholdY;
  102. group.constrain = Drag.constrain;
  103. group.constrainVertical = Drag.constrainVertical;
  104. group.constrainHorizontal = Drag.constrainHorizontal;
  105. group.onmousedown = Drag.onMouseDown;
  106. },
  107. constrainVertical : function() {
  108. var nwOffset = Coordinates.northwestOffset(this, true);
  109. this.minX = nwOffset.x;
  110. this.maxX = nwOffset.x;
  111. },
  112. constrainHorizontal : function() {
  113. var nwOffset = Coordinates.northwestOffset(this, true);
  114. this.minY = nwOffset.y;
  115. this.maxY = nwOffset.y;
  116. },
  117. constrain : function(nwPosition, sePosition) {
  118. this.minX = nwPosition.x;
  119. this.minY = nwPosition.y;
  120. this.maxX = sePosition.x;
  121. this.maxY = sePosition.y;
  122. },
  123. setDragHandle : function(handle) {
  124. if (handle && handle != null)
  125. this.handle = handle;
  126. else
  127. this.handle = this;
  128. this.handle.group = this;
  129. this.onmousedown = null;
  130. this.handle.onmousedown = Drag.onMouseDown;
  131. },
  132. setDragThreshold : function(threshold) {
  133. if (isNaN(parseInt(threshold))) return;
  134. this.threshold = threshold;
  135. },
  136. setDragThresholdX : function(threshold) {
  137. if (isNaN(parseInt(threshold))) return;
  138. this.thresholdX = threshold;
  139. },
  140. setDragThresholdY : function(threshold) {
  141. if (isNaN(parseInt(threshold))) return;
  142. this.thresholdY = threshold;
  143. },
  144. onMouseDown : function(event) {
  145. event = Drag.fixEvent(event);
  146. Drag.group = this.group;
  147. var group = this.group;
  148. var mouse = event.windowCoordinate;
  149. var nwOffset = Coordinates.northwestOffset(group, true);
  150. var nwPosition = Coordinates.northwestPosition(group);
  151. var sePosition = Coordinates.southeastPosition(group);
  152. var seOffset = Coordinates.southeastOffset(group, true);
  153. group.originalOpacity = group.style.opacity;
  154. group.originalZIndex = group.style.zIndex;
  155. group.initialWindowCoordinate = mouse;
  156. // TODO: need a better name, but don't yet understand how it
  157. // participates in the magic while dragging
  158. group.dragCoordinate = mouse;
  159. Drag.showStatus(mouse, nwPosition, sePosition, nwOffset, seOffset);
  160. group.onDragStart(nwPosition, sePosition, nwOffset, seOffset);
  161. // TODO: need better constraint API
  162. if (group.minX != null)
  163. group.minMouseX = mouse.x - nwPosition.x +
  164. group.minX - nwOffset.x;
  165. if (group.maxX != null)
  166. group.maxMouseX = group.minMouseX + group.maxX - group.minX;
  167. if (group.minY != null)
  168. group.minMouseY = mouse.y - nwPosition.y +
  169. group.minY - nwOffset.y;
  170. if (group.maxY != null)
  171. group.maxMouseY = group.minMouseY + group.maxY - group.minY;
  172. group.mouseMin = new Coordinate(group.minMouseX, group.minMouseY);
  173. group.mouseMax = new Coordinate(group.maxMouseX, group.maxMouseY);
  174. document.onmousemove = Drag.onMouseMove;
  175. document.onmouseup = Drag.onMouseUp;
  176. return false;
  177. },
  178. showStatus : function(mouse, nwPosition, sePosition, nwOffset, seOffset) {
  179. window.status =
  180. "mouse: " + mouse.toString() + " " +
  181. "NW pos: " + nwPosition.toString() + " " +
  182. "SE pos: " + sePosition.toString() + " " +
  183. "NW offset: " + nwOffset.toString() + " " +
  184. "SE offset: " + seOffset.toString();
  185. },
  186. onMouseMove : function(event) {
  187. event = Drag.fixEvent(event);
  188. var group = Drag.group;
  189. var mouse = event.windowCoordinate;
  190. var nwOffset = Coordinates.northwestOffset(group, true);
  191. var nwPosition = Coordinates.northwestPosition(group);
  192. var sePosition = Coordinates.southeastPosition(group);
  193. var seOffset = Coordinates.southeastOffset(group, true);
  194. Drag.showStatus(mouse, nwPosition, sePosition, nwOffset, seOffset);
  195. if (!Drag.isDragging) {
  196. if (group.threshold > 0) {
  197. var distance = group.initialWindowCoordinate.distance(
  198. mouse);
  199. if (distance < group.threshold) return true;
  200. } else if (group.thresholdY > 0) {
  201. var deltaY = Math.abs(group.initialWindowCoordinate.y - mouse.y);
  202. if (deltaY < group.thresholdY) return true;
  203. } else if (group.thresholdX > 0) {
  204. var deltaX = Math.abs(group.initialWindowCoordinate.x - mouse.x);
  205. if (deltaX < group.thresholdX) return true;
  206. }
  207. Drag.isDragging = true;
  208. group.style["zIndex"] = Drag.BIG_Z_INDEX;
  209. group.style["opacity"] = 0.75;
  210. }
  211. // TODO: need better constraint API
  212. var adjusted = mouse.constrain(group.mouseMin, group.mouseMax);
  213. nwPosition = nwPosition.plus(adjusted.minus(group.dragCoordinate));
  214. nwPosition.reposition(group);
  215. group.dragCoordinate = adjusted;
  216. // once dragging has started, the position of the group
  217. // relative to the mouse should stay fixed. They can get out
  218. // of sync if the DOM is manipulated while dragging, so we
  219. // correct the error here
  220. //
  221. // TODO: what we really want to do is find the offset from
  222. // our corner to the mouse coordinate and adjust to keep it
  223. // the same
  224. var offsetBefore = Coordinates.northwestOffset(group);
  225. group.onDrag(nwPosition, sePosition, nwOffset, seOffset);
  226. var offsetAfter = Coordinates.northwestOffset(group);
  227. if (!offsetBefore.equals(offsetAfter)) {
  228. var errorDelta = offsetBefore.minus(offsetAfter);
  229. nwPosition = Coordinates.northwestPosition(group).plus(errorDelta);
  230. nwPosition.reposition(group);
  231. }
  232. return false;
  233. },
  234. onMouseUp : function(event) {
  235. event = Drag.fixEvent(event);
  236. var group = Drag.group;
  237. var mouse = event.windowCoordinate;
  238. var nwOffset = Coordinates.northwestOffset(group, true);
  239. var nwPosition = Coordinates.northwestPosition(group);
  240. var sePosition = Coordinates.southeastPosition(group);
  241. var seOffset = Coordinates.southeastOffset(group, true);
  242. document.onmousemove = null;
  243. document.onmouseup = null;
  244. group.onDragEnd(nwPosition, sePosition, nwOffset, seOffset);
  245. if (Drag.isDragging) {
  246. // restoring zIndex before opacity avoids visual flicker in Firefox
  247. group.style["zIndex"] = group.originalZIndex;
  248. group.style["opacity"] = group.originalOpacity;
  249. }
  250. Drag.group = null;
  251. Drag.isDragging = false;
  252. return false;
  253. },
  254. fixEvent : function(event) {
  255. if (typeof event == 'undefined') event = window.event;
  256. Coordinates.fixEvent(event);
  257. return event;
  258. }
  259. };