jquery.ui.droppable.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. /*!
  2. * jQuery UI Droppable @VERSION
  3. * http://jqueryui.com
  4. *
  5. * Copyright 2013 jQuery Foundation and other contributors
  6. * Released under the MIT license.
  7. * http://jquery.org/license
  8. *
  9. * http://api.jqueryui.com/droppable/
  10. *
  11. * Depends:
  12. * jquery.ui.core.js
  13. * jquery.ui.widget.js
  14. * jquery.ui.mouse.js
  15. * jquery.ui.draggable.js
  16. */
  17. (function( $, undefined ) {
  18. function isOverAxis( x, reference, size ) {
  19. return ( x > reference ) && ( x < ( reference + size ) );
  20. }
  21. $.widget("ui.droppable", {
  22. version: "@VERSION",
  23. widgetEventPrefix: "drop",
  24. options: {
  25. accept: "*",
  26. activeClass: false,
  27. addClasses: true,
  28. greedy: false,
  29. hoverClass: false,
  30. scope: "default",
  31. tolerance: "intersect",
  32. // callbacks
  33. activate: null,
  34. deactivate: null,
  35. drop: null,
  36. out: null,
  37. over: null
  38. },
  39. _create: function() {
  40. var o = this.options,
  41. accept = o.accept;
  42. this.isover = false;
  43. this.isout = true;
  44. this.accept = $.isFunction(accept) ? accept : function(d) {
  45. return d.is(accept);
  46. };
  47. //Store the droppable's proportions
  48. this.proportions = { width: this.element[0].offsetWidth, height: this.element[0].offsetHeight };
  49. // Add the reference and positions to the manager
  50. $.ui.ddmanager.droppables[o.scope] = $.ui.ddmanager.droppables[o.scope] || [];
  51. $.ui.ddmanager.droppables[o.scope].push(this);
  52. (o.addClasses && this.element.addClass("ui-droppable"));
  53. },
  54. _destroy: function() {
  55. var i = 0,
  56. drop = $.ui.ddmanager.droppables[this.options.scope];
  57. for ( ; i < drop.length; i++ ) {
  58. if ( drop[i] === this ) {
  59. drop.splice(i, 1);
  60. }
  61. }
  62. this.element.removeClass("ui-droppable ui-droppable-disabled");
  63. },
  64. _setOption: function(key, value) {
  65. if(key === "accept") {
  66. this.accept = $.isFunction(value) ? value : function(d) {
  67. return d.is(value);
  68. };
  69. }
  70. $.Widget.prototype._setOption.apply(this, arguments);
  71. },
  72. _activate: function(event) {
  73. var draggable = $.ui.ddmanager.current;
  74. if(this.options.activeClass) {
  75. this.element.addClass(this.options.activeClass);
  76. }
  77. if(draggable){
  78. this._trigger("activate", event, this.ui(draggable));
  79. }
  80. },
  81. _deactivate: function(event) {
  82. var draggable = $.ui.ddmanager.current;
  83. if(this.options.activeClass) {
  84. this.element.removeClass(this.options.activeClass);
  85. }
  86. if(draggable){
  87. this._trigger("deactivate", event, this.ui(draggable));
  88. }
  89. },
  90. _over: function(event) {
  91. var draggable = $.ui.ddmanager.current;
  92. // Bail if draggable and droppable are same element
  93. if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) {
  94. return;
  95. }
  96. if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
  97. if(this.options.hoverClass) {
  98. this.element.addClass(this.options.hoverClass);
  99. }
  100. this._trigger("over", event, this.ui(draggable));
  101. }
  102. },
  103. _out: function(event) {
  104. var draggable = $.ui.ddmanager.current;
  105. // Bail if draggable and droppable are same element
  106. if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) {
  107. return;
  108. }
  109. if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
  110. if(this.options.hoverClass) {
  111. this.element.removeClass(this.options.hoverClass);
  112. }
  113. this._trigger("out", event, this.ui(draggable));
  114. }
  115. },
  116. _drop: function(event,custom) {
  117. var draggable = custom || $.ui.ddmanager.current,
  118. childrenIntersection = false;
  119. // Bail if draggable and droppable are same element
  120. if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) {
  121. return false;
  122. }
  123. this.element.find(":data(ui-droppable)").not(".ui-draggable-dragging").each(function() {
  124. var inst = $.data(this, "ui-droppable");
  125. if(
  126. inst.options.greedy &&
  127. !inst.options.disabled &&
  128. inst.options.scope === draggable.options.scope &&
  129. inst.accept.call(inst.element[0], (draggable.currentItem || draggable.element)) &&
  130. $.ui.intersect(draggable, $.extend(inst, { offset: inst.element.offset() }), inst.options.tolerance)
  131. ) { childrenIntersection = true; return false; }
  132. });
  133. if(childrenIntersection) {
  134. return false;
  135. }
  136. if(this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
  137. if(this.options.activeClass) {
  138. this.element.removeClass(this.options.activeClass);
  139. }
  140. if(this.options.hoverClass) {
  141. this.element.removeClass(this.options.hoverClass);
  142. }
  143. this._trigger("drop", event, this.ui(draggable));
  144. return this.element;
  145. }
  146. return false;
  147. },
  148. ui: function(c) {
  149. return {
  150. draggable: (c.currentItem || c.element),
  151. helper: c.helper,
  152. position: c.position,
  153. offset: c.positionAbs
  154. };
  155. }
  156. });
  157. $.ui.intersect = function(draggable, droppable, toleranceMode) {
  158. if (!droppable.offset) {
  159. return false;
  160. }
  161. var draggableLeft, draggableTop,
  162. x1 = (draggable.positionAbs || draggable.position.absolute).left, x2 = x1 + draggable.helperProportions.width,
  163. y1 = (draggable.positionAbs || draggable.position.absolute).top, y2 = y1 + draggable.helperProportions.height,
  164. l = droppable.offset.left, r = l + droppable.proportions.width,
  165. t = droppable.offset.top, b = t + droppable.proportions.height;
  166. switch (toleranceMode) {
  167. case "fit":
  168. return (l <= x1 && x2 <= r && t <= y1 && y2 <= b);
  169. case "intersect":
  170. return (l < x1 + (draggable.helperProportions.width / 2) && // Right Half
  171. x2 - (draggable.helperProportions.width / 2) < r && // Left Half
  172. t < y1 + (draggable.helperProportions.height / 2) && // Bottom Half
  173. y2 - (draggable.helperProportions.height / 2) < b ); // Top Half
  174. case "pointer":
  175. draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left);
  176. draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top);
  177. return isOverAxis( draggableTop, t, droppable.proportions.height ) && isOverAxis( draggableLeft, l, droppable.proportions.width );
  178. case "touch":
  179. return (
  180. (y1 >= t && y1 <= b) || // Top edge touching
  181. (y2 >= t && y2 <= b) || // Bottom edge touching
  182. (y1 < t && y2 > b) // Surrounded vertically
  183. ) && (
  184. (x1 >= l && x1 <= r) || // Left edge touching
  185. (x2 >= l && x2 <= r) || // Right edge touching
  186. (x1 < l && x2 > r) // Surrounded horizontally
  187. );
  188. default:
  189. return false;
  190. }
  191. };
  192. /*
  193. This manager tracks offsets of draggables and droppables
  194. */
  195. $.ui.ddmanager = {
  196. current: null,
  197. droppables: { "default": [] },
  198. prepareOffsets: function(t, event) {
  199. var i, j,
  200. m = $.ui.ddmanager.droppables[t.options.scope] || [],
  201. type = event ? event.type : null, // workaround for #2317
  202. list = (t.currentItem || t.element).find(":data(ui-droppable)").addBack();
  203. droppablesLoop: for (i = 0; i < m.length; i++) {
  204. //No disabled and non-accepted
  205. if(m[i].options.disabled || (t && !m[i].accept.call(m[i].element[0],(t.currentItem || t.element)))) {
  206. continue;
  207. }
  208. // Filter out elements in the current dragged item
  209. for (j=0; j < list.length; j++) {
  210. if(list[j] === m[i].element[0]) {
  211. m[i].proportions.height = 0;
  212. continue droppablesLoop;
  213. }
  214. }
  215. m[i].visible = m[i].element.css("display") !== "none";
  216. if(!m[i].visible) {
  217. continue;
  218. }
  219. //Activate the droppable if used directly from draggables
  220. if(type === "mousedown") {
  221. m[i]._activate.call(m[i], event);
  222. }
  223. m[i].offset = m[i].element.offset();
  224. m[i].proportions = { width: m[i].element[0].offsetWidth, height: m[i].element[0].offsetHeight };
  225. }
  226. },
  227. drop: function(draggable, event) {
  228. var dropped = false;
  229. // Create a copy of the droppables in case the list changes during the drop (#9116)
  230. $.each(($.ui.ddmanager.droppables[draggable.options.scope] || []).slice(), function() {
  231. if(!this.options) {
  232. return;
  233. }
  234. if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance)) {
  235. dropped = this._drop.call(this, event) || dropped;
  236. }
  237. if (!this.options.disabled && this.visible && this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
  238. this.isout = true;
  239. this.isover = false;
  240. this._deactivate.call(this, event);
  241. }
  242. });
  243. return dropped;
  244. },
  245. dragStart: function( draggable, event ) {
  246. //Listen for scrolling so that if the dragging causes scrolling the position of the droppables can be recalculated (see #5003)
  247. draggable.element.parentsUntil( "body" ).bind( "scroll.droppable", function() {
  248. if( !draggable.options.refreshPositions ) {
  249. $.ui.ddmanager.prepareOffsets( draggable, event );
  250. }
  251. });
  252. },
  253. drag: function(draggable, event) {
  254. //If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse.
  255. if(draggable.options.refreshPositions) {
  256. $.ui.ddmanager.prepareOffsets(draggable, event);
  257. }
  258. //Run through all droppables and check their positions based on specific tolerance options
  259. $.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() {
  260. if(this.options.disabled || this.greedyChild || !this.visible) {
  261. return;
  262. }
  263. var parentInstance, scope, parent,
  264. intersects = $.ui.intersect(draggable, this, this.options.tolerance),
  265. c = !intersects && this.isover ? "isout" : (intersects && !this.isover ? "isover" : null);
  266. if(!c) {
  267. return;
  268. }
  269. if (this.options.greedy) {
  270. // find droppable parents with same scope
  271. scope = this.options.scope;
  272. parent = this.element.parents(":data(ui-droppable)").filter(function () {
  273. return $.data(this, "ui-droppable").options.scope === scope;
  274. });
  275. if (parent.length) {
  276. parentInstance = $.data(parent[0], "ui-droppable");
  277. parentInstance.greedyChild = (c === "isover");
  278. }
  279. }
  280. // we just moved into a greedy child
  281. if (parentInstance && c === "isover") {
  282. parentInstance.isover = false;
  283. parentInstance.isout = true;
  284. parentInstance._out.call(parentInstance, event);
  285. }
  286. this[c] = true;
  287. this[c === "isout" ? "isover" : "isout"] = false;
  288. this[c === "isover" ? "_over" : "_out"].call(this, event);
  289. // we just moved out of a greedy child
  290. if (parentInstance && c === "isout") {
  291. parentInstance.isout = false;
  292. parentInstance.isover = true;
  293. parentInstance._over.call(parentInstance, event);
  294. }
  295. });
  296. },
  297. dragStop: function( draggable, event ) {
  298. draggable.element.parentsUntil( "body" ).unbind( "scroll.droppable" );
  299. //Call prepareOffsets one final time since IE does not fire return scroll events when overflow was caused by drag (see #5003)
  300. if( !draggable.options.refreshPositions ) {
  301. $.ui.ddmanager.prepareOffsets( draggable, event );
  302. }
  303. }
  304. };
  305. })(jQuery);