jquery.ui.sortable.js 45 KB

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