sortable.js 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315
  1. /*!
  2. * jQuery UI Sortable 1.11.4
  3. * http://jqueryui.com
  4. *
  5. * Copyright jQuery Foundation and other contributors
  6. * Released under the MIT license.
  7. * http://jquery.org/license
  8. *
  9. * http://api.jqueryui.com/sortable/
  10. */
  11. (function( factory ) {
  12. if ( typeof define === "function" && define.amd ) {
  13. // AMD. Register as an anonymous module.
  14. define([
  15. "jquery",
  16. "./core",
  17. "./mouse",
  18. "./widget"
  19. ], factory );
  20. } else {
  21. // Browser globals
  22. factory( jQuery );
  23. }
  24. }(function( $ ) {
  25. return $.widget("ui.sortable", $.ui.mouse, {
  26. version: "1.11.4",
  27. widgetEventPrefix: "sort",
  28. ready: false,
  29. options: {
  30. appendTo: "parent",
  31. axis: false,
  32. connectWith: false,
  33. containment: false,
  34. cursor: "auto",
  35. cursorAt: false,
  36. dropOnEmpty: true,
  37. forcePlaceholderSize: false,
  38. forceHelperSize: false,
  39. grid: false,
  40. handle: false,
  41. helper: "original",
  42. items: "> *",
  43. opacity: false,
  44. placeholder: false,
  45. revert: false,
  46. scroll: true,
  47. scrollSensitivity: 20,
  48. scrollSpeed: 20,
  49. scope: "default",
  50. tolerance: "intersect",
  51. zIndex: 1000,
  52. // callbacks
  53. activate: null,
  54. beforeStop: null,
  55. change: null,
  56. deactivate: null,
  57. out: null,
  58. over: null,
  59. receive: null,
  60. remove: null,
  61. sort: null,
  62. start: null,
  63. stop: null,
  64. update: null
  65. },
  66. _isOverAxis: function( x, reference, size ) {
  67. return ( x >= reference ) && ( x < ( reference + size ) );
  68. },
  69. _isFloating: function( item ) {
  70. return (/left|right/).test(item.css("float")) || (/inline|table-cell/).test(item.css("display"));
  71. },
  72. _create: function() {
  73. this.containerCache = {};
  74. this.element.addClass("ui-sortable");
  75. //Get the items
  76. this.refresh();
  77. //Let's determine the parent's offset
  78. this.offset = this.element.offset();
  79. //Initialize mouse events for interaction
  80. this._mouseInit();
  81. this._setHandleClassName();
  82. //We're ready to go
  83. this.ready = true;
  84. },
  85. _setOption: function( key, value ) {
  86. this._super( key, value );
  87. if ( key === "handle" ) {
  88. this._setHandleClassName();
  89. }
  90. },
  91. _setHandleClassName: function() {
  92. this.element.find( ".ui-sortable-handle" ).removeClass( "ui-sortable-handle" );
  93. $.each( this.items, function() {
  94. ( this.instance.options.handle ?
  95. this.item.find( this.instance.options.handle ) : this.item )
  96. .addClass( "ui-sortable-handle" );
  97. });
  98. },
  99. _destroy: function() {
  100. this.element
  101. .removeClass( "ui-sortable ui-sortable-disabled" )
  102. .find( ".ui-sortable-handle" )
  103. .removeClass( "ui-sortable-handle" );
  104. this._mouseDestroy();
  105. for ( var i = this.items.length - 1; i >= 0; i-- ) {
  106. this.items[i].item.removeData(this.widgetName + "-item");
  107. }
  108. return this;
  109. },
  110. _mouseCapture: function(event, overrideHandle) {
  111. var currentItem = null,
  112. validHandle = false,
  113. that = this;
  114. if (this.reverting) {
  115. return false;
  116. }
  117. if(this.options.disabled || this.options.type === "static") {
  118. return false;
  119. }
  120. //We have to refresh the items data once first
  121. this._refreshItems(event);
  122. //Find out if the clicked node (or one of its parents) is a actual item in this.items
  123. $(event.target).parents().each(function() {
  124. if($.data(this, that.widgetName + "-item") === that) {
  125. currentItem = $(this);
  126. return false;
  127. }
  128. });
  129. if($.data(event.target, that.widgetName + "-item") === that) {
  130. currentItem = $(event.target);
  131. }
  132. if(!currentItem) {
  133. return false;
  134. }
  135. if(this.options.handle && !overrideHandle) {
  136. $(this.options.handle, currentItem).find("*").addBack().each(function() {
  137. if(this === event.target) {
  138. validHandle = true;
  139. }
  140. });
  141. if(!validHandle) {
  142. return false;
  143. }
  144. }
  145. this.currentItem = currentItem;
  146. this._removeCurrentsFromItems();
  147. return true;
  148. },
  149. _mouseStart: function(event, overrideHandle, noActivation) {
  150. var i, body,
  151. o = this.options;
  152. this.currentContainer = this;
  153. //We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture
  154. this.refreshPositions();
  155. //Create and append the visible helper
  156. this.helper = this._createHelper(event);
  157. //Cache the helper size
  158. this._cacheHelperProportions();
  159. /*
  160. * - Position generation -
  161. * This block generates everything position related - it's the core of draggables.
  162. */
  163. //Cache the margins of the original element
  164. this._cacheMargins();
  165. //Get the next scrolling parent
  166. this.scrollParent = this.helper.scrollParent();
  167. //The element's absolute position on the page minus margins
  168. this.offset = this.currentItem.offset();
  169. this.offset = {
  170. top: this.offset.top - this.margins.top,
  171. left: this.offset.left - this.margins.left
  172. };
  173. $.extend(this.offset, {
  174. click: { //Where the click happened, relative to the element
  175. left: event.pageX - this.offset.left,
  176. top: event.pageY - this.offset.top
  177. },
  178. parent: this._getParentOffset(),
  179. relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
  180. });
  181. // Only after we got the offset, we can change the helper's position to absolute
  182. // TODO: Still need to figure out a way to make relative sorting possible
  183. this.helper.css("position", "absolute");
  184. this.cssPosition = this.helper.css("position");
  185. //Generate the original position
  186. this.originalPosition = this._generatePosition(event);
  187. this.originalPageX = event.pageX;
  188. this.originalPageY = event.pageY;
  189. //Adjust the mouse offset relative to the helper if "cursorAt" is supplied
  190. (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
  191. //Cache the former DOM position
  192. this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] };
  193. //If the helper is not the original, hide the original so it's not playing any role during the drag, won't cause anything bad this way
  194. if(this.helper[0] !== this.currentItem[0]) {
  195. this.currentItem.hide();
  196. }
  197. //Create the placeholder
  198. this._createPlaceholder();
  199. //Set a containment if given in the options
  200. if(o.containment) {
  201. this._setContainment();
  202. }
  203. if( o.cursor && o.cursor !== "auto" ) { // cursor option
  204. body = this.document.find( "body" );
  205. // support: IE
  206. this.storedCursor = body.css( "cursor" );
  207. body.css( "cursor", o.cursor );
  208. this.storedStylesheet = $( "<style>*{ cursor: "+o.cursor+" !important; }</style>" ).appendTo( body );
  209. }
  210. if(o.opacity) { // opacity option
  211. if (this.helper.css("opacity")) {
  212. this._storedOpacity = this.helper.css("opacity");
  213. }
  214. this.helper.css("opacity", o.opacity);
  215. }
  216. if(o.zIndex) { // zIndex option
  217. if (this.helper.css("zIndex")) {
  218. this._storedZIndex = this.helper.css("zIndex");
  219. }
  220. this.helper.css("zIndex", o.zIndex);
  221. }
  222. //Prepare scrolling
  223. if(this.scrollParent[0] !== this.document[0] && this.scrollParent[0].tagName !== "HTML") {
  224. this.overflowOffset = this.scrollParent.offset();
  225. }
  226. //Call callbacks
  227. this._trigger("start", event, this._uiHash());
  228. //Recache the helper size
  229. if(!this._preserveHelperProportions) {
  230. this._cacheHelperProportions();
  231. }
  232. //Post "activate" events to possible containers
  233. if( !noActivation ) {
  234. for ( i = this.containers.length - 1; i >= 0; i-- ) {
  235. this.containers[ i ]._trigger( "activate", event, this._uiHash( this ) );
  236. }
  237. }
  238. //Prepare possible droppables
  239. if($.ui.ddmanager) {
  240. $.ui.ddmanager.current = this;
  241. }
  242. if ($.ui.ddmanager && !o.dropBehaviour) {
  243. $.ui.ddmanager.prepareOffsets(this, event);
  244. }
  245. this.dragging = true;
  246. this.helper.addClass("ui-sortable-helper");
  247. this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position
  248. return true;
  249. },
  250. _mouseDrag: function(event) {
  251. var i, item, itemElement, intersection,
  252. o = this.options,
  253. scrolled = false;
  254. //Compute the helpers position
  255. this.position = this._generatePosition(event);
  256. this.positionAbs = this._convertPositionTo("absolute");
  257. if (!this.lastPositionAbs) {
  258. this.lastPositionAbs = this.positionAbs;
  259. }
  260. //Do scrolling
  261. if(this.options.scroll) {
  262. if(this.scrollParent[0] !== this.document[0] && this.scrollParent[0].tagName !== "HTML") {
  263. if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) {
  264. this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
  265. } else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity) {
  266. this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;
  267. }
  268. if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) {
  269. this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
  270. } else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity) {
  271. this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;
  272. }
  273. } else {
  274. if(event.pageY - this.document.scrollTop() < o.scrollSensitivity) {
  275. scrolled = this.document.scrollTop(this.document.scrollTop() - o.scrollSpeed);
  276. } else if(this.window.height() - (event.pageY - this.document.scrollTop()) < o.scrollSensitivity) {
  277. scrolled = this.document.scrollTop(this.document.scrollTop() + o.scrollSpeed);
  278. }
  279. if(event.pageX - this.document.scrollLeft() < o.scrollSensitivity) {
  280. scrolled = this.document.scrollLeft(this.document.scrollLeft() - o.scrollSpeed);
  281. } else if(this.window.width() - (event.pageX - this.document.scrollLeft()) < o.scrollSensitivity) {
  282. scrolled = this.document.scrollLeft(this.document.scrollLeft() + o.scrollSpeed);
  283. }
  284. }
  285. if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) {
  286. $.ui.ddmanager.prepareOffsets(this, event);
  287. }
  288. }
  289. //Regenerate the absolute position used for position checks
  290. this.positionAbs = this._convertPositionTo("absolute");
  291. //Set the helper position
  292. if(!this.options.axis || this.options.axis !== "y") {
  293. this.helper[0].style.left = this.position.left+"px";
  294. }
  295. if(!this.options.axis || this.options.axis !== "x") {
  296. this.helper[0].style.top = this.position.top+"px";
  297. }
  298. //Rearrange
  299. for (i = this.items.length - 1; i >= 0; i--) {
  300. //Cache variables and intersection, continue if no intersection
  301. item = this.items[i];
  302. itemElement = item.item[0];
  303. intersection = this._intersectsWithPointer(item);
  304. if (!intersection) {
  305. continue;
  306. }
  307. // Only put the placeholder inside the current Container, skip all
  308. // items from other containers. This works because when moving
  309. // an item from one container to another the
  310. // currentContainer is switched before the placeholder is moved.
  311. //
  312. // Without this, moving items in "sub-sortables" can cause
  313. // the placeholder to jitter between the outer and inner container.
  314. if (item.instance !== this.currentContainer) {
  315. continue;
  316. }
  317. // cannot intersect with itself
  318. // no useless actions that have been done before
  319. // no action if the item moved is the parent of the item checked
  320. if (itemElement !== this.currentItem[0] &&
  321. this.placeholder[intersection === 1 ? "next" : "prev"]()[0] !== itemElement &&
  322. !$.contains(this.placeholder[0], itemElement) &&
  323. (this.options.type === "semi-dynamic" ? !$.contains(this.element[0], itemElement) : true)
  324. ) {
  325. this.direction = intersection === 1 ? "down" : "up";
  326. if (this.options.tolerance === "pointer" || this._intersectsWithSides(item)) {
  327. this._rearrange(event, item);
  328. } else {
  329. break;
  330. }
  331. this._trigger("change", event, this._uiHash());
  332. break;
  333. }
  334. }
  335. //Post events to containers
  336. this._contactContainers(event);
  337. //Interconnect with droppables
  338. if($.ui.ddmanager) {
  339. $.ui.ddmanager.drag(this, event);
  340. }
  341. //Call callbacks
  342. this._trigger("sort", event, this._uiHash());
  343. this.lastPositionAbs = this.positionAbs;
  344. return false;
  345. },
  346. _mouseStop: function(event, noPropagation) {
  347. if(!event) {
  348. return;
  349. }
  350. //If we are using droppables, inform the manager about the drop
  351. if ($.ui.ddmanager && !this.options.dropBehaviour) {
  352. $.ui.ddmanager.drop(this, event);
  353. }
  354. if(this.options.revert) {
  355. var that = this,
  356. cur = this.placeholder.offset(),
  357. axis = this.options.axis,
  358. animation = {};
  359. if ( !axis || axis === "x" ) {
  360. animation.left = cur.left - this.offset.parent.left - this.margins.left + (this.offsetParent[0] === this.document[0].body ? 0 : this.offsetParent[0].scrollLeft);
  361. }
  362. if ( !axis || axis === "y" ) {
  363. animation.top = cur.top - this.offset.parent.top - this.margins.top + (this.offsetParent[0] === this.document[0].body ? 0 : this.offsetParent[0].scrollTop);
  364. }
  365. this.reverting = true;
  366. $(this.helper).animate( animation, parseInt(this.options.revert, 10) || 500, function() {
  367. that._clear(event);
  368. });
  369. } else {
  370. this._clear(event, noPropagation);
  371. }
  372. return false;
  373. },
  374. cancel: function() {
  375. if(this.dragging) {
  376. this._mouseUp({ target: null });
  377. if(this.options.helper === "original") {
  378. this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
  379. } else {
  380. this.currentItem.show();
  381. }
  382. //Post deactivating events to containers
  383. for (var i = this.containers.length - 1; i >= 0; i--){
  384. this.containers[i]._trigger("deactivate", null, this._uiHash(this));
  385. if(this.containers[i].containerCache.over) {
  386. this.containers[i]._trigger("out", null, this._uiHash(this));
  387. this.containers[i].containerCache.over = 0;
  388. }
  389. }
  390. }
  391. if (this.placeholder) {
  392. //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
  393. if(this.placeholder[0].parentNode) {
  394. this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
  395. }
  396. if(this.options.helper !== "original" && this.helper && this.helper[0].parentNode) {
  397. this.helper.remove();
  398. }
  399. $.extend(this, {
  400. helper: null,
  401. dragging: false,
  402. reverting: false,
  403. _noFinalSort: null
  404. });
  405. if(this.domPosition.prev) {
  406. $(this.domPosition.prev).after(this.currentItem);
  407. } else {
  408. $(this.domPosition.parent).prepend(this.currentItem);
  409. }
  410. }
  411. return this;
  412. },
  413. serialize: function(o) {
  414. var items = this._getItemsAsjQuery(o && o.connected),
  415. str = [];
  416. o = o || {};
  417. $(items).each(function() {
  418. var res = ($(o.item || this).attr(o.attribute || "id") || "").match(o.expression || (/(.+)[\-=_](.+)/));
  419. if (res) {
  420. str.push((o.key || res[1]+"[]")+"="+(o.key && o.expression ? res[1] : res[2]));
  421. }
  422. });
  423. if(!str.length && o.key) {
  424. str.push(o.key + "=");
  425. }
  426. return str.join("&");
  427. },
  428. toArray: function(o) {
  429. var items = this._getItemsAsjQuery(o && o.connected),
  430. ret = [];
  431. o = o || {};
  432. items.each(function() { ret.push($(o.item || this).attr(o.attribute || "id") || ""); });
  433. return ret;
  434. },
  435. /* Be careful with the following core functions */
  436. _intersectsWith: function(item) {
  437. var x1 = this.positionAbs.left,
  438. x2 = x1 + this.helperProportions.width,
  439. y1 = this.positionAbs.top,
  440. y2 = y1 + this.helperProportions.height,
  441. l = item.left,
  442. r = l + item.width,
  443. t = item.top,
  444. b = t + item.height,
  445. dyClick = this.offset.click.top,
  446. dxClick = this.offset.click.left,
  447. isOverElementHeight = ( this.options.axis === "x" ) || ( ( y1 + dyClick ) > t && ( y1 + dyClick ) < b ),
  448. isOverElementWidth = ( this.options.axis === "y" ) || ( ( x1 + dxClick ) > l && ( x1 + dxClick ) < r ),
  449. isOverElement = isOverElementHeight && isOverElementWidth;
  450. if ( this.options.tolerance === "pointer" ||
  451. this.options.forcePointerForContainers ||
  452. (this.options.tolerance !== "pointer" && this.helperProportions[this.floating ? "width" : "height"] > item[this.floating ? "width" : "height"])
  453. ) {
  454. return isOverElement;
  455. } else {
  456. return (l < x1 + (this.helperProportions.width / 2) && // Right Half
  457. x2 - (this.helperProportions.width / 2) < r && // Left Half
  458. t < y1 + (this.helperProportions.height / 2) && // Bottom Half
  459. y2 - (this.helperProportions.height / 2) < b ); // Top Half
  460. }
  461. },
  462. _intersectsWithPointer: function(item) {
  463. var isOverElementHeight = (this.options.axis === "x") || this._isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height),
  464. isOverElementWidth = (this.options.axis === "y") || this._isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width),
  465. isOverElement = isOverElementHeight && isOverElementWidth,
  466. verticalDirection = this._getDragVerticalDirection(),
  467. horizontalDirection = this._getDragHorizontalDirection();
  468. if (!isOverElement) {
  469. return false;
  470. }
  471. return this.floating ?
  472. ( ((horizontalDirection && horizontalDirection === "right") || verticalDirection === "down") ? 2 : 1 )
  473. : ( verticalDirection && (verticalDirection === "down" ? 2 : 1) );
  474. },
  475. _intersectsWithSides: function(item) {
  476. var isOverBottomHalf = this._isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height),
  477. isOverRightHalf = this._isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width),
  478. verticalDirection = this._getDragVerticalDirection(),
  479. horizontalDirection = this._getDragHorizontalDirection();
  480. if (this.floating && horizontalDirection) {
  481. return ((horizontalDirection === "right" && isOverRightHalf) || (horizontalDirection === "left" && !isOverRightHalf));
  482. } else {
  483. return verticalDirection && ((verticalDirection === "down" && isOverBottomHalf) || (verticalDirection === "up" && !isOverBottomHalf));
  484. }
  485. },
  486. _getDragVerticalDirection: function() {
  487. var delta = this.positionAbs.top - this.lastPositionAbs.top;
  488. return delta !== 0 && (delta > 0 ? "down" : "up");
  489. },
  490. _getDragHorizontalDirection: function() {
  491. var delta = this.positionAbs.left - this.lastPositionAbs.left;
  492. return delta !== 0 && (delta > 0 ? "right" : "left");
  493. },
  494. refresh: function(event) {
  495. this._refreshItems(event);
  496. this._setHandleClassName();
  497. this.refreshPositions();
  498. return this;
  499. },
  500. _connectWith: function() {
  501. var options = this.options;
  502. return options.connectWith.constructor === String ? [options.connectWith] : options.connectWith;
  503. },
  504. _getItemsAsjQuery: function(connected) {
  505. var i, j, cur, inst,
  506. items = [],
  507. queries = [],
  508. connectWith = this._connectWith();
  509. if(connectWith && connected) {
  510. for (i = connectWith.length - 1; i >= 0; i--){
  511. cur = $(connectWith[i], this.document[0]);
  512. for ( j = cur.length - 1; j >= 0; j--){
  513. inst = $.data(cur[j], this.widgetFullName);
  514. if(inst && inst !== this && !inst.options.disabled) {
  515. queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), inst]);
  516. }
  517. }
  518. }
  519. }
  520. queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), this]);
  521. function addItems() {
  522. items.push( this );
  523. }
  524. for (i = queries.length - 1; i >= 0; i--){
  525. queries[i][0].each( addItems );
  526. }
  527. return $(items);
  528. },
  529. _removeCurrentsFromItems: function() {
  530. var list = this.currentItem.find(":data(" + this.widgetName + "-item)");
  531. this.items = $.grep(this.items, function (item) {
  532. for (var j=0; j < list.length; j++) {
  533. if(list[j] === item.item[0]) {
  534. return false;
  535. }
  536. }
  537. return true;
  538. });
  539. },
  540. _refreshItems: function(event) {
  541. this.items = [];
  542. this.containers = [this];
  543. var i, j, cur, inst, targetData, _queries, item, queriesLength,
  544. items = this.items,
  545. queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]],
  546. connectWith = this._connectWith();
  547. if(connectWith && this.ready) { //Shouldn't be run the first time through due to massive slow-down
  548. for (i = connectWith.length - 1; i >= 0; i--){
  549. cur = $(connectWith[i], this.document[0]);
  550. for (j = cur.length - 1; j >= 0; j--){
  551. inst = $.data(cur[j], this.widgetFullName);
  552. if(inst && inst !== this && !inst.options.disabled) {
  553. queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]);
  554. this.containers.push(inst);
  555. }
  556. }
  557. }
  558. }
  559. for (i = queries.length - 1; i >= 0; i--) {
  560. targetData = queries[i][1];
  561. _queries = queries[i][0];
  562. for (j=0, queriesLength = _queries.length; j < queriesLength; j++) {
  563. item = $(_queries[j]);
  564. item.data(this.widgetName + "-item", targetData); // Data for target checking (mouse manager)
  565. items.push({
  566. item: item,
  567. instance: targetData,
  568. width: 0, height: 0,
  569. left: 0, top: 0
  570. });
  571. }
  572. }
  573. },
  574. refreshPositions: function(fast) {
  575. // Determine whether items are being displayed horizontally
  576. this.floating = this.items.length ?
  577. this.options.axis === "x" || this._isFloating( this.items[ 0 ].item ) :
  578. false;
  579. //This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change
  580. if(this.offsetParent && this.helper) {
  581. this.offset.parent = this._getParentOffset();
  582. }
  583. var i, item, t, p;
  584. for (i = this.items.length - 1; i >= 0; i--){
  585. item = this.items[i];
  586. //We ignore calculating positions of all connected containers when we're not over them
  587. if(item.instance !== this.currentContainer && this.currentContainer && item.item[0] !== this.currentItem[0]) {
  588. continue;
  589. }
  590. t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item;
  591. if (!fast) {
  592. item.width = t.outerWidth();
  593. item.height = t.outerHeight();
  594. }
  595. p = t.offset();
  596. item.left = p.left;
  597. item.top = p.top;
  598. }
  599. if(this.options.custom && this.options.custom.refreshContainers) {
  600. this.options.custom.refreshContainers.call(this);
  601. } else {
  602. for (i = this.containers.length - 1; i >= 0; i--){
  603. p = this.containers[i].element.offset();
  604. this.containers[i].containerCache.left = p.left;
  605. this.containers[i].containerCache.top = p.top;
  606. this.containers[i].containerCache.width = this.containers[i].element.outerWidth();
  607. this.containers[i].containerCache.height = this.containers[i].element.outerHeight();
  608. }
  609. }
  610. return this;
  611. },
  612. _createPlaceholder: function(that) {
  613. that = that || this;
  614. var className,
  615. o = that.options;
  616. if(!o.placeholder || o.placeholder.constructor === String) {
  617. className = o.placeholder;
  618. o.placeholder = {
  619. element: function() {
  620. var nodeName = that.currentItem[0].nodeName.toLowerCase(),
  621. element = $( "<" + nodeName + ">", that.document[0] )
  622. .addClass(className || that.currentItem[0].className+" ui-sortable-placeholder")
  623. .removeClass("ui-sortable-helper");
  624. if ( nodeName === "tbody" ) {
  625. that._createTrPlaceholder(
  626. that.currentItem.find( "tr" ).eq( 0 ),
  627. $( "<tr>", that.document[ 0 ] ).appendTo( element )
  628. );
  629. } else if ( nodeName === "tr" ) {
  630. that._createTrPlaceholder( that.currentItem, element );
  631. } else if ( nodeName === "img" ) {
  632. element.attr( "src", that.currentItem.attr( "src" ) );
  633. }
  634. if ( !className ) {
  635. element.css( "visibility", "hidden" );
  636. }
  637. return element;
  638. },
  639. update: function(container, p) {
  640. // 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that
  641. // 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified
  642. if(className && !o.forcePlaceholderSize) {
  643. return;
  644. }
  645. //If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item
  646. if(!p.height()) { p.height(that.currentItem.innerHeight() - parseInt(that.currentItem.css("paddingTop")||0, 10) - parseInt(that.currentItem.css("paddingBottom")||0, 10)); }
  647. if(!p.width()) { p.width(that.currentItem.innerWidth() - parseInt(that.currentItem.css("paddingLeft")||0, 10) - parseInt(that.currentItem.css("paddingRight")||0, 10)); }
  648. }
  649. };
  650. }
  651. //Create the placeholder
  652. that.placeholder = $(o.placeholder.element.call(that.element, that.currentItem));
  653. //Append it after the actual current item
  654. that.currentItem.after(that.placeholder);
  655. //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)
  656. o.placeholder.update(that, that.placeholder);
  657. },
  658. _createTrPlaceholder: function( sourceTr, targetTr ) {
  659. var that = this;
  660. sourceTr.children().each(function() {
  661. $( "<td>&#160;</td>", that.document[ 0 ] )
  662. .attr( "colspan", $( this ).attr( "colspan" ) || 1 )
  663. .appendTo( targetTr );
  664. });
  665. },
  666. _contactContainers: function(event) {
  667. var i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, cur, nearBottom, floating, axis,
  668. innermostContainer = null,
  669. innermostIndex = null;
  670. // get innermost container that intersects with item
  671. for (i = this.containers.length - 1; i >= 0; i--) {
  672. // never consider a container that's located within the item itself
  673. if($.contains(this.currentItem[0], this.containers[i].element[0])) {
  674. continue;
  675. }
  676. if(this._intersectsWith(this.containers[i].containerCache)) {
  677. // if we've already found a container and it's more "inner" than this, then continue
  678. if(innermostContainer && $.contains(this.containers[i].element[0], innermostContainer.element[0])) {
  679. continue;
  680. }
  681. innermostContainer = this.containers[i];
  682. innermostIndex = i;
  683. } else {
  684. // container doesn't intersect. trigger "out" event if necessary
  685. if(this.containers[i].containerCache.over) {
  686. this.containers[i]._trigger("out", event, this._uiHash(this));
  687. this.containers[i].containerCache.over = 0;
  688. }
  689. }
  690. }
  691. // if no intersecting containers found, return
  692. if(!innermostContainer) {
  693. return;
  694. }
  695. // move the item into the container if it's not there already
  696. if(this.containers.length === 1) {
  697. if (!this.containers[innermostIndex].containerCache.over) {
  698. this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
  699. this.containers[innermostIndex].containerCache.over = 1;
  700. }
  701. } else {
  702. //When entering a new container, we will find the item with the least distance and append our item near it
  703. dist = 10000;
  704. itemWithLeastDistance = null;
  705. floating = innermostContainer.floating || this._isFloating(this.currentItem);
  706. posProperty = floating ? "left" : "top";
  707. sizeProperty = floating ? "width" : "height";
  708. axis = floating ? "clientX" : "clientY";
  709. for (j = this.items.length - 1; j >= 0; j--) {
  710. if(!$.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) {
  711. continue;
  712. }
  713. if(this.items[j].item[0] === this.currentItem[0]) {
  714. continue;
  715. }
  716. cur = this.items[j].item.offset()[posProperty];
  717. nearBottom = false;
  718. if ( event[ axis ] - cur > this.items[ j ][ sizeProperty ] / 2 ) {
  719. nearBottom = true;
  720. }
  721. if ( Math.abs( event[ axis ] - cur ) < dist ) {
  722. dist = Math.abs( event[ axis ] - cur );
  723. itemWithLeastDistance = this.items[ j ];
  724. this.direction = nearBottom ? "up": "down";
  725. }
  726. }
  727. //Check if dropOnEmpty is enabled
  728. if(!itemWithLeastDistance && !this.options.dropOnEmpty) {
  729. return;
  730. }
  731. if(this.currentContainer === this.containers[innermostIndex]) {
  732. if ( !this.currentContainer.containerCache.over ) {
  733. this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash() );
  734. this.currentContainer.containerCache.over = 1;
  735. }
  736. return;
  737. }
  738. itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true);
  739. this._trigger("change", event, this._uiHash());
  740. this.containers[innermostIndex]._trigger("change", event, this._uiHash(this));
  741. this.currentContainer = this.containers[innermostIndex];
  742. //Update the placeholder
  743. this.options.placeholder.update(this.currentContainer, this.placeholder);
  744. this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
  745. this.containers[innermostIndex].containerCache.over = 1;
  746. }
  747. },
  748. _createHelper: function(event) {
  749. var o = this.options,
  750. helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper === "clone" ? this.currentItem.clone() : this.currentItem);
  751. //Add the helper to the DOM if that didn't happen already
  752. if(!helper.parents("body").length) {
  753. $(o.appendTo !== "parent" ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]);
  754. }
  755. if(helper[0] === this.currentItem[0]) {
  756. this._storedCSS = { width: this.currentItem[0].style.width, height: this.currentItem[0].style.height, position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left") };
  757. }
  758. if(!helper[0].style.width || o.forceHelperSize) {
  759. helper.width(this.currentItem.width());
  760. }
  761. if(!helper[0].style.height || o.forceHelperSize) {
  762. helper.height(this.currentItem.height());
  763. }
  764. return helper;
  765. },
  766. _adjustOffsetFromHelper: function(obj) {
  767. if (typeof obj === "string") {
  768. obj = obj.split(" ");
  769. }
  770. if ($.isArray(obj)) {
  771. obj = {left: +obj[0], top: +obj[1] || 0};
  772. }
  773. if ("left" in obj) {
  774. this.offset.click.left = obj.left + this.margins.left;
  775. }
  776. if ("right" in obj) {
  777. this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
  778. }
  779. if ("top" in obj) {
  780. this.offset.click.top = obj.top + this.margins.top;
  781. }
  782. if ("bottom" in obj) {
  783. this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
  784. }
  785. },
  786. _getParentOffset: function() {
  787. //Get the offsetParent and cache its position
  788. this.offsetParent = this.helper.offsetParent();
  789. var po = this.offsetParent.offset();
  790. // This is a special case where we need to modify a offset calculated on start, since the following happened:
  791. // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
  792. // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
  793. // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
  794. if(this.cssPosition === "absolute" && this.scrollParent[0] !== this.document[0] && $.contains(this.scrollParent[0], this.offsetParent[0])) {
  795. po.left += this.scrollParent.scrollLeft();
  796. po.top += this.scrollParent.scrollTop();
  797. }
  798. // This needs to be actually done for all browsers, since pageX/pageY includes this information
  799. // with an ugly IE fix
  800. if( this.offsetParent[0] === this.document[0].body || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) {
  801. po = { top: 0, left: 0 };
  802. }
  803. return {
  804. top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
  805. left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
  806. };
  807. },
  808. _getRelativeOffset: function() {
  809. if(this.cssPosition === "relative") {
  810. var p = this.currentItem.position();
  811. return {
  812. top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
  813. left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
  814. };
  815. } else {
  816. return { top: 0, left: 0 };
  817. }
  818. },
  819. _cacheMargins: function() {
  820. this.margins = {
  821. left: (parseInt(this.currentItem.css("marginLeft"),10) || 0),
  822. top: (parseInt(this.currentItem.css("marginTop"),10) || 0)
  823. };
  824. },
  825. _cacheHelperProportions: function() {
  826. this.helperProportions = {
  827. width: this.helper.outerWidth(),
  828. height: this.helper.outerHeight()
  829. };
  830. },
  831. _setContainment: function() {
  832. var ce, co, over,
  833. o = this.options;
  834. if(o.containment === "parent") {
  835. o.containment = this.helper[0].parentNode;
  836. }
  837. if(o.containment === "document" || o.containment === "window") {
  838. this.containment = [
  839. 0 - this.offset.relative.left - this.offset.parent.left,
  840. 0 - this.offset.relative.top - this.offset.parent.top,
  841. o.containment === "document" ? this.document.width() : this.window.width() - this.helperProportions.width - this.margins.left,
  842. (o.containment === "document" ? this.document.width() : this.window.height() || this.document[0].body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
  843. ];
  844. }
  845. if(!(/^(document|window|parent)$/).test(o.containment)) {
  846. ce = $(o.containment)[0];
  847. co = $(o.containment).offset();
  848. over = ($(ce).css("overflow") !== "hidden");
  849. this.containment = [
  850. co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left,
  851. co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top,
  852. co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left,
  853. co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top
  854. ];
  855. }
  856. },
  857. _convertPositionTo: function(d, pos) {
  858. if(!pos) {
  859. pos = this.position;
  860. }
  861. var mod = d === "absolute" ? 1 : -1,
  862. scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== this.document[0] && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent,
  863. scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
  864. return {
  865. top: (
  866. pos.top + // The absolute mouse position
  867. this.offset.relative.top * mod + // Only for relative positioned nodes: Relative offset from element to offset parent
  868. this.offset.parent.top * mod - // The offsetParent's offset without borders (offset + border)
  869. ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
  870. ),
  871. left: (
  872. pos.left + // The absolute mouse position
  873. this.offset.relative.left * mod + // Only for relative positioned nodes: Relative offset from element to offset parent
  874. this.offset.parent.left * mod - // The offsetParent's offset without borders (offset + border)
  875. ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
  876. )
  877. };
  878. },
  879. _generatePosition: function(event) {
  880. var top, left,
  881. o = this.options,
  882. pageX = event.pageX,
  883. pageY = event.pageY,
  884. scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== this.document[0] && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
  885. // This is another very weird special case that only happens for relative elements:
  886. // 1. If the css position is relative
  887. // 2. and the scroll parent is the document or similar to the offset parent
  888. // we have to refresh the relative offset during the scroll so there are no jumps
  889. if(this.cssPosition === "relative" && !(this.scrollParent[0] !== this.document[0] && this.scrollParent[0] !== this.offsetParent[0])) {
  890. this.offset.relative = this._getRelativeOffset();
  891. }
  892. /*
  893. * - Position constraining -
  894. * Constrain the position to a mix of grid, containment.
  895. */
  896. if(this.originalPosition) { //If we are not dragging yet, we won't check for options
  897. if(this.containment) {
  898. if(event.pageX - this.offset.click.left < this.containment[0]) {
  899. pageX = this.containment[0] + this.offset.click.left;
  900. }
  901. if(event.pageY - this.offset.click.top < this.containment[1]) {
  902. pageY = this.containment[1] + this.offset.click.top;
  903. }
  904. if(event.pageX - this.offset.click.left > this.containment[2]) {
  905. pageX = this.containment[2] + this.offset.click.left;
  906. }
  907. if(event.pageY - this.offset.click.top > this.containment[3]) {
  908. pageY = this.containment[3] + this.offset.click.top;
  909. }
  910. }
  911. if(o.grid) {
  912. top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1];
  913. pageY = this.containment ? ( (top - this.offset.click.top >= this.containment[1] && top - this.offset.click.top <= this.containment[3]) ? top : ((top - this.offset.click.top >= this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
  914. left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0];
  915. pageX = this.containment ? ( (left - this.offset.click.left >= this.containment[0] && left - this.offset.click.left <= this.containment[2]) ? left : ((left - this.offset.click.left >= this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
  916. }
  917. }
  918. return {
  919. top: (
  920. pageY - // The absolute mouse position
  921. this.offset.click.top - // Click offset (relative to the element)
  922. this.offset.relative.top - // Only for relative positioned nodes: Relative offset from element to offset parent
  923. this.offset.parent.top + // The offsetParent's offset without borders (offset + border)
  924. ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
  925. ),
  926. left: (
  927. pageX - // The absolute mouse position
  928. this.offset.click.left - // Click offset (relative to the element)
  929. this.offset.relative.left - // Only for relative positioned nodes: Relative offset from element to offset parent
  930. this.offset.parent.left + // The offsetParent's offset without borders (offset + border)
  931. ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
  932. )
  933. };
  934. },
  935. _rearrange: function(event, i, a, hardRefresh) {
  936. a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction === "down" ? i.item[0] : i.item[0].nextSibling));
  937. //Various things done here to improve the performance:
  938. // 1. we create a setTimeout, that calls refreshPositions
  939. // 2. on the instance, we have a counter variable, that get's higher after every append
  940. // 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same
  941. // 4. this lets only the last addition to the timeout stack through
  942. this.counter = this.counter ? ++this.counter : 1;
  943. var counter = this.counter;
  944. this._delay(function() {
  945. if(counter === this.counter) {
  946. this.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove
  947. }
  948. });
  949. },
  950. _clear: function(event, noPropagation) {
  951. this.reverting = false;
  952. // We delay all events that have to be triggered to after the point where the placeholder has been removed and
  953. // everything else normalized again
  954. var i,
  955. delayedTriggers = [];
  956. // We first have to update the dom position of the actual currentItem
  957. // Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088)
  958. if(!this._noFinalSort && this.currentItem.parent().length) {
  959. this.placeholder.before(this.currentItem);
  960. }
  961. this._noFinalSort = null;
  962. if(this.helper[0] === this.currentItem[0]) {
  963. for(i in this._storedCSS) {
  964. if(this._storedCSS[i] === "auto" || this._storedCSS[i] === "static") {
  965. this._storedCSS[i] = "";
  966. }
  967. }
  968. this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
  969. } else {
  970. this.currentItem.show();
  971. }
  972. if(this.fromOutside && !noPropagation) {
  973. delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); });
  974. }
  975. if((this.fromOutside || this.domPosition.prev !== this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent !== this.currentItem.parent()[0]) && !noPropagation) {
  976. delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed
  977. }
  978. // Check if the items Container has Changed and trigger appropriate
  979. // events.
  980. if (this !== this.currentContainer) {
  981. if(!noPropagation) {
  982. delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); });
  983. delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); }; }).call(this, this.currentContainer));
  984. delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this)); }; }).call(this, this.currentContainer));
  985. }
  986. }
  987. //Post events to containers
  988. function delayEvent( type, instance, container ) {
  989. return function( event ) {
  990. container._trigger( type, event, instance._uiHash( instance ) );
  991. };
  992. }
  993. for (i = this.containers.length - 1; i >= 0; i--){
  994. if (!noPropagation) {
  995. delayedTriggers.push( delayEvent( "deactivate", this, this.containers[ i ] ) );
  996. }
  997. if(this.containers[i].containerCache.over) {
  998. delayedTriggers.push( delayEvent( "out", this, this.containers[ i ] ) );
  999. this.containers[i].containerCache.over = 0;
  1000. }
  1001. }
  1002. //Do what was originally in plugins
  1003. if ( this.storedCursor ) {
  1004. this.document.find( "body" ).css( "cursor", this.storedCursor );
  1005. this.storedStylesheet.remove();
  1006. }
  1007. if(this._storedOpacity) {
  1008. this.helper.css("opacity", this._storedOpacity);
  1009. }
  1010. if(this._storedZIndex) {
  1011. this.helper.css("zIndex", this._storedZIndex === "auto" ? "" : this._storedZIndex);
  1012. }
  1013. this.dragging = false;
  1014. if(!noPropagation) {
  1015. this._trigger("beforeStop", event, this._uiHash());
  1016. }
  1017. //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
  1018. this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
  1019. if ( !this.cancelHelperRemoval ) {
  1020. if ( this.helper[ 0 ] !== this.currentItem[ 0 ] ) {
  1021. this.helper.remove();
  1022. }
  1023. this.helper = null;
  1024. }
  1025. if(!noPropagation) {
  1026. for (i=0; i < delayedTriggers.length; i++) {
  1027. delayedTriggers[i].call(this, event);
  1028. } //Trigger all delayed events
  1029. this._trigger("stop", event, this._uiHash());
  1030. }
  1031. this.fromOutside = false;
  1032. return !this.cancelHelperRemoval;
  1033. },
  1034. _trigger: function() {
  1035. if ($.Widget.prototype._trigger.apply(this, arguments) === false) {
  1036. this.cancel();
  1037. }
  1038. },
  1039. _uiHash: function(_inst) {
  1040. var inst = _inst || this;
  1041. return {
  1042. helper: inst.helper,
  1043. placeholder: inst.placeholder || $([]),
  1044. position: inst.position,
  1045. originalPosition: inst.originalPosition,
  1046. offset: inst.positionAbs,
  1047. item: inst.currentItem,
  1048. sender: _inst ? _inst.element : null
  1049. };
  1050. }
  1051. });
  1052. }));