Нет описания

sortable.js 45KB

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