Sortable.js 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271
  1. /** @license
  2. ========================================================================
  3. Sortable
  4. @author RubaXa <trash@rubaxa.org>
  5. @license MIT
  6. */
  7. (function (factory) {
  8. "use strict";
  9. if (typeof define === "function" && define.amd) {
  10. define(factory);
  11. }
  12. else if (typeof module != "undefined" && typeof module.exports != "undefined") {
  13. module.exports = factory();
  14. }
  15. else if (typeof Package !== "undefined") {
  16. Sortable = factory(); // export for Meteor.js
  17. }
  18. else {
  19. /* jshint sub:true */
  20. window["Sortable"] = factory();
  21. }
  22. })(function () {
  23. "use strict";
  24. if (typeof window == "undefined" || typeof window.document == "undefined") {
  25. return function() {
  26. throw new Error( "Sortable.js requires a window with a document" );
  27. }
  28. }
  29. var dragEl,
  30. parentEl,
  31. ghostEl,
  32. cloneEl,
  33. rootEl,
  34. nextEl,
  35. scrollEl,
  36. scrollParentEl,
  37. lastEl,
  38. lastCSS,
  39. lastParentCSS,
  40. oldIndex,
  41. newIndex,
  42. activeGroup,
  43. autoScroll = {},
  44. tapEvt,
  45. touchEvt,
  46. moved,
  47. /** @const */
  48. RSPACE = /\s+/g,
  49. expando = 'Sortable' + (new Date).getTime(),
  50. win = window,
  51. document = win.document,
  52. parseInt = win.parseInt,
  53. supportDraggable = !!('draggable' in document.createElement('div')),
  54. supportCssPointerEvents = (function (el) {
  55. el = document.createElement('x');
  56. el.style.cssText = 'pointer-events:auto';
  57. return el.style.pointerEvents === 'auto';
  58. })(),
  59. _silent = false,
  60. abs = Math.abs,
  61. slice = [].slice,
  62. touchDragOverListeners = [],
  63. _autoScroll = _throttle(function (/**Event*/evt, /**Object*/options, /**HTMLElement*/rootEl) {
  64. // Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=505521
  65. if (rootEl && options.scroll) {
  66. var el,
  67. rect,
  68. sens = options.scrollSensitivity,
  69. speed = options.scrollSpeed,
  70. x = evt.clientX,
  71. y = evt.clientY,
  72. winWidth = window.innerWidth,
  73. winHeight = window.innerHeight,
  74. vx,
  75. vy
  76. ;
  77. // Delect scrollEl
  78. if (scrollParentEl !== rootEl) {
  79. scrollEl = options.scroll;
  80. scrollParentEl = rootEl;
  81. if (scrollEl === true) {
  82. scrollEl = rootEl;
  83. do {
  84. if ((scrollEl.offsetWidth < scrollEl.scrollWidth) ||
  85. (scrollEl.offsetHeight < scrollEl.scrollHeight)
  86. ) {
  87. break;
  88. }
  89. /* jshint boss:true */
  90. } while (scrollEl = scrollEl.parentNode);
  91. }
  92. }
  93. if (scrollEl) {
  94. el = scrollEl;
  95. rect = scrollEl.getBoundingClientRect();
  96. vx = (abs(rect.right - x) <= sens) - (abs(rect.left - x) <= sens);
  97. vy = (abs(rect.bottom - y) <= sens) - (abs(rect.top - y) <= sens);
  98. }
  99. if (!(vx || vy)) {
  100. vx = (winWidth - x <= sens) - (x <= sens);
  101. vy = (winHeight - y <= sens) - (y <= sens);
  102. /* jshint expr:true */
  103. (vx || vy) && (el = win);
  104. }
  105. if (autoScroll.vx !== vx || autoScroll.vy !== vy || autoScroll.el !== el) {
  106. autoScroll.el = el;
  107. autoScroll.vx = vx;
  108. autoScroll.vy = vy;
  109. clearInterval(autoScroll.pid);
  110. if (el) {
  111. autoScroll.pid = setInterval(function () {
  112. if (el === win) {
  113. win.scrollTo(win.pageXOffset + vx * speed, win.pageYOffset + vy * speed);
  114. } else {
  115. vy && (el.scrollTop += vy * speed);
  116. vx && (el.scrollLeft += vx * speed);
  117. }
  118. }, 24);
  119. }
  120. }
  121. }
  122. }, 30),
  123. _prepareGroup = function (options) {
  124. var group = options.group;
  125. if (!group || typeof group != 'object') {
  126. group = options.group = {name: group};
  127. }
  128. ['pull', 'put'].forEach(function (key) {
  129. if (!(key in group)) {
  130. group[key] = true;
  131. }
  132. });
  133. options.groups = ' ' + group.name + (group.put.join ? ' ' + group.put.join(' ') : '') + ' ';
  134. }
  135. ;
  136. /**
  137. * @class Sortable
  138. * @param {HTMLElement} el
  139. * @param {Object} [options]
  140. */
  141. function Sortable(el, options) {
  142. if (!(el && el.nodeType && el.nodeType === 1)) {
  143. throw 'Sortable: `el` must be HTMLElement, and not ' + {}.toString.call(el);
  144. }
  145. this.el = el; // root element
  146. this.options = options = _extend({}, options);
  147. // Export instance
  148. el[expando] = this;
  149. // Default options
  150. var defaults = {
  151. group: Math.random(),
  152. sort: true,
  153. disabled: false,
  154. store: null,
  155. handle: null,
  156. scroll: true,
  157. scrollSensitivity: 30,
  158. scrollSpeed: 10,
  159. draggable: /[uo]l/i.test(el.nodeName) ? 'li' : '>*',
  160. ghostClass: 'sortable-ghost',
  161. chosenClass: 'sortable-chosen',
  162. ignore: 'a, img',
  163. filter: null,
  164. animation: 0,
  165. setData: function (dataTransfer, dragEl) {
  166. dataTransfer.setData('Text', dragEl.textContent);
  167. },
  168. dropBubble: false,
  169. dragoverBubble: false,
  170. dataIdAttr: 'data-id',
  171. delay: 0,
  172. forceFallback: false,
  173. fallbackClass: 'sortable-fallback',
  174. fallbackOnBody: false
  175. };
  176. // Set default options
  177. for (var name in defaults) {
  178. !(name in options) && (options[name] = defaults[name]);
  179. }
  180. _prepareGroup(options);
  181. // Bind all private methods
  182. for (var fn in this) {
  183. if (fn.charAt(0) === '_') {
  184. this[fn] = this[fn].bind(this);
  185. }
  186. }
  187. // Setup drag mode
  188. this.nativeDraggable = options.forceFallback ? false : supportDraggable;
  189. // Bind events
  190. _on(el, 'mousedown', this._onTapStart);
  191. _on(el, 'touchstart', this._onTapStart);
  192. if (this.nativeDraggable) {
  193. _on(el, 'dragover', this);
  194. _on(el, 'dragenter', this);
  195. }
  196. touchDragOverListeners.push(this._onDragOver);
  197. // Restore sorting
  198. options.store && this.sort(options.store.get(this));
  199. }
  200. Sortable.prototype = /** @lends Sortable.prototype */ {
  201. constructor: Sortable,
  202. _onTapStart: function (/** Event|TouchEvent */evt) {
  203. var _this = this,
  204. el = this.el,
  205. options = this.options,
  206. type = evt.type,
  207. touch = evt.touches && evt.touches[0],
  208. target = (touch || evt).target,
  209. originalTarget = target,
  210. filter = options.filter;
  211. if (type === 'mousedown' && evt.button !== 0 || options.disabled) {
  212. return; // only left button or enabled
  213. }
  214. target = _closest(target, options.draggable, el);
  215. if (!target) {
  216. return;
  217. }
  218. // get the index of the dragged element within its parent
  219. oldIndex = _index(target, options.draggable);
  220. // Check filter
  221. if (typeof filter === 'function') {
  222. if (filter.call(this, evt, target, this)) {
  223. _dispatchEvent(_this, originalTarget, 'filter', target, el, oldIndex);
  224. evt.preventDefault();
  225. return; // cancel dnd
  226. }
  227. }
  228. else if (filter) {
  229. filter = filter.split(',').some(function (criteria) {
  230. criteria = _closest(originalTarget, criteria.trim(), el);
  231. if (criteria) {
  232. _dispatchEvent(_this, criteria, 'filter', target, el, oldIndex);
  233. return true;
  234. }
  235. });
  236. if (filter) {
  237. //evt.preventDefault();
  238. return; // cancel dnd
  239. }
  240. }
  241. if (options.handle && !_closest(originalTarget, options.handle, el)) {
  242. return;
  243. }
  244. // Prepare `dragstart`
  245. this._prepareDragStart(evt, touch, target);
  246. },
  247. _prepareDragStart: function (/** Event */evt, /** Touch */touch, /** HTMLElement */target) {
  248. var _this = this,
  249. el = _this.el,
  250. options = _this.options,
  251. ownerDocument = el.ownerDocument,
  252. dragStartFn;
  253. if (target && !dragEl && (target.parentNode === el)) {
  254. tapEvt = evt;
  255. rootEl = el;
  256. dragEl = target;
  257. parentEl = dragEl.parentNode;
  258. nextEl = dragEl.nextSibling;
  259. activeGroup = options.group;
  260. dragStartFn = function () {
  261. // Delayed drag has been triggered
  262. // we can re-enable the events: touchmove/mousemove
  263. _this._disableDelayedDrag();
  264. // Make the element draggable
  265. dragEl.draggable = true;
  266. // Chosen item
  267. _toggleClass(dragEl, _this.options.chosenClass, true);
  268. // Bind the events: dragstart/dragend
  269. _this._triggerDragStart(touch);
  270. };
  271. // Disable "draggable"
  272. options.ignore.split(',').forEach(function (criteria) {
  273. _find(dragEl, criteria.trim(), _disableDraggable);
  274. });
  275. _on(ownerDocument, 'mouseup', _this._onDrop);
  276. _on(ownerDocument, 'touchend', _this._onDrop);
  277. _on(ownerDocument, 'touchcancel', _this._onDrop);
  278. if (options.delay) {
  279. // If the user moves the pointer or let go the click or touch
  280. // before the delay has been reached:
  281. // disable the delayed drag
  282. _on(ownerDocument, 'mouseup', _this._disableDelayedDrag);
  283. _on(ownerDocument, 'touchend', _this._disableDelayedDrag);
  284. _on(ownerDocument, 'touchcancel', _this._disableDelayedDrag);
  285. _on(ownerDocument, 'mousemove', _this._disableDelayedDrag);
  286. _on(ownerDocument, 'touchmove', _this._disableDelayedDrag);
  287. _this._dragStartTimer = setTimeout(dragStartFn, options.delay);
  288. } else {
  289. dragStartFn();
  290. }
  291. }
  292. },
  293. _disableDelayedDrag: function () {
  294. var ownerDocument = this.el.ownerDocument;
  295. clearTimeout(this._dragStartTimer);
  296. _off(ownerDocument, 'mouseup', this._disableDelayedDrag);
  297. _off(ownerDocument, 'touchend', this._disableDelayedDrag);
  298. _off(ownerDocument, 'touchcancel', this._disableDelayedDrag);
  299. _off(ownerDocument, 'mousemove', this._disableDelayedDrag);
  300. _off(ownerDocument, 'touchmove', this._disableDelayedDrag);
  301. },
  302. _triggerDragStart: function (/** Touch */touch) {
  303. if (touch) {
  304. // Touch device support
  305. tapEvt = {
  306. target: dragEl,
  307. clientX: touch.clientX,
  308. clientY: touch.clientY
  309. };
  310. this._onDragStart(tapEvt, 'touch');
  311. }
  312. else if (!this.nativeDraggable) {
  313. this._onDragStart(tapEvt, true);
  314. }
  315. else {
  316. _on(dragEl, 'dragend', this);
  317. _on(rootEl, 'dragstart', this._onDragStart);
  318. }
  319. try {
  320. if (document.selection) {
  321. document.selection.empty();
  322. } else {
  323. window.getSelection().removeAllRanges();
  324. }
  325. } catch (err) {
  326. }
  327. },
  328. _dragStarted: function () {
  329. if (rootEl && dragEl) {
  330. // Apply effect
  331. _toggleClass(dragEl, this.options.ghostClass, true);
  332. Sortable.active = this;
  333. // Drag start event
  334. _dispatchEvent(this, rootEl, 'start', dragEl, rootEl, oldIndex);
  335. }
  336. },
  337. _emulateDragOver: function () {
  338. if (touchEvt) {
  339. if (this._lastX === touchEvt.clientX && this._lastY === touchEvt.clientY) {
  340. return;
  341. }
  342. this._lastX = touchEvt.clientX;
  343. this._lastY = touchEvt.clientY;
  344. if (!supportCssPointerEvents) {
  345. _css(ghostEl, 'display', 'none');
  346. }
  347. var target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY),
  348. parent = target,
  349. groupName = ' ' + this.options.group.name + '',
  350. i = touchDragOverListeners.length;
  351. if (parent) {
  352. do {
  353. if (parent[expando] && parent[expando].options.groups.indexOf(groupName) > -1) {
  354. while (i--) {
  355. touchDragOverListeners[i]({
  356. clientX: touchEvt.clientX,
  357. clientY: touchEvt.clientY,
  358. target: target,
  359. rootEl: parent
  360. });
  361. }
  362. break;
  363. }
  364. target = parent; // store last element
  365. }
  366. /* jshint boss:true */
  367. while (parent = parent.parentNode);
  368. }
  369. if (!supportCssPointerEvents) {
  370. _css(ghostEl, 'display', '');
  371. }
  372. }
  373. },
  374. _onTouchMove: function (/**TouchEvent*/evt) {
  375. if (tapEvt) {
  376. // only set the status to dragging, when we are actually dragging
  377. if (!Sortable.active) {
  378. this._dragStarted();
  379. }
  380. // as well as creating the ghost element on the document body
  381. this._appendGhost();
  382. var touch = evt.touches ? evt.touches[0] : evt,
  383. dx = touch.clientX - tapEvt.clientX,
  384. dy = touch.clientY - tapEvt.clientY,
  385. translate3d = evt.touches ? 'translate3d(' + dx + 'px,' + dy + 'px,0)' : 'translate(' + dx + 'px,' + dy + 'px)';
  386. moved = true;
  387. touchEvt = touch;
  388. _css(ghostEl, 'webkitTransform', translate3d);
  389. _css(ghostEl, 'mozTransform', translate3d);
  390. _css(ghostEl, 'msTransform', translate3d);
  391. _css(ghostEl, 'transform', translate3d);
  392. evt.preventDefault();
  393. }
  394. },
  395. _appendGhost: function () {
  396. if (!ghostEl) {
  397. var rect = dragEl.getBoundingClientRect(),
  398. css = _css(dragEl),
  399. options = this.options,
  400. ghostRect;
  401. ghostEl = dragEl.cloneNode(true);
  402. _toggleClass(ghostEl, options.ghostClass, false);
  403. _toggleClass(ghostEl, options.fallbackClass, true);
  404. _css(ghostEl, 'top', rect.top - parseInt(css.marginTop, 10));
  405. _css(ghostEl, 'left', rect.left - parseInt(css.marginLeft, 10));
  406. _css(ghostEl, 'width', rect.width);
  407. _css(ghostEl, 'height', rect.height);
  408. _css(ghostEl, 'opacity', '0.8');
  409. _css(ghostEl, 'position', 'fixed');
  410. _css(ghostEl, 'zIndex', '100000');
  411. _css(ghostEl, 'pointerEvents', 'none');
  412. options.fallbackOnBody && document.body.appendChild(ghostEl) || rootEl.appendChild(ghostEl);
  413. // Fixing dimensions.
  414. ghostRect = ghostEl.getBoundingClientRect();
  415. _css(ghostEl, 'width', rect.width * 2 - ghostRect.width);
  416. _css(ghostEl, 'height', rect.height * 2 - ghostRect.height);
  417. }
  418. },
  419. _onDragStart: function (/**Event*/evt, /**boolean*/useFallback) {
  420. var dataTransfer = evt.dataTransfer,
  421. options = this.options;
  422. this._offUpEvents();
  423. if (activeGroup.pull == 'clone') {
  424. cloneEl = dragEl.cloneNode(true);
  425. _css(cloneEl, 'display', 'none');
  426. rootEl.insertBefore(cloneEl, dragEl);
  427. }
  428. if (useFallback) {
  429. if (useFallback === 'touch') {
  430. // Bind touch events
  431. _on(document, 'touchmove', this._onTouchMove);
  432. _on(document, 'touchend', this._onDrop);
  433. _on(document, 'touchcancel', this._onDrop);
  434. } else {
  435. // Old brwoser
  436. _on(document, 'mousemove', this._onTouchMove);
  437. _on(document, 'mouseup', this._onDrop);
  438. }
  439. this._loopId = setInterval(this._emulateDragOver, 50);
  440. }
  441. else {
  442. if (dataTransfer) {
  443. dataTransfer.effectAllowed = 'move';
  444. options.setData && options.setData.call(this, dataTransfer, dragEl);
  445. }
  446. _on(document, 'drop', this);
  447. setTimeout(this._dragStarted, 0);
  448. }
  449. },
  450. _onDragOver: function (/**Event*/evt) {
  451. var el = this.el,
  452. target,
  453. dragRect,
  454. revert,
  455. options = this.options,
  456. group = options.group,
  457. groupPut = group.put,
  458. isOwner = (activeGroup === group),
  459. canSort = options.sort;
  460. if (evt.preventDefault !== void 0) {
  461. evt.preventDefault();
  462. !options.dragoverBubble && evt.stopPropagation();
  463. }
  464. moved = true;
  465. if (activeGroup && !options.disabled &&
  466. (isOwner
  467. ? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list
  468. : activeGroup.pull && groupPut && (
  469. (activeGroup.name === group.name) || // by Name
  470. (groupPut.indexOf && ~groupPut.indexOf(activeGroup.name)) // by Array
  471. )
  472. ) &&
  473. (evt.rootEl === void 0 || evt.rootEl === this.el) // touch fallback
  474. ) {
  475. // Smart auto-scrolling
  476. _autoScroll(evt, options, this.el);
  477. if (_silent) {
  478. return;
  479. }
  480. target = _closest(evt.target, options.draggable, el);
  481. dragRect = dragEl.getBoundingClientRect();
  482. if (revert) {
  483. _cloneHide(true);
  484. if (cloneEl || nextEl) {
  485. rootEl.insertBefore(dragEl, cloneEl || nextEl);
  486. }
  487. else if (!canSort) {
  488. rootEl.appendChild(dragEl);
  489. }
  490. return;
  491. }
  492. if ((el.children.length === 0) || (el.children[0] === ghostEl) ||
  493. (el === evt.target) && (target = _ghostIsLast(el, evt))
  494. ) {
  495. if (target) {
  496. if (target.animated) {
  497. return;
  498. }
  499. targetRect = target.getBoundingClientRect();
  500. }
  501. _cloneHide(isOwner);
  502. if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect) !== false) {
  503. if (!dragEl.contains(el)) {
  504. el.appendChild(dragEl);
  505. parentEl = el; // actualization
  506. }
  507. this._animate(dragRect, dragEl);
  508. target && this._animate(targetRect, target);
  509. }
  510. }
  511. else if (target && !target.animated && target !== dragEl && (target.parentNode[expando] !== void 0)) {
  512. if (lastEl !== target) {
  513. lastEl = target;
  514. lastCSS = _css(target);
  515. lastParentCSS = _css(target.parentNode);
  516. }
  517. var targetRect = target.getBoundingClientRect(),
  518. width = targetRect.right - targetRect.left,
  519. height = targetRect.bottom - targetRect.top,
  520. floating = /left|right|inline/.test(lastCSS.cssFloat + lastCSS.display)
  521. || (lastParentCSS.display == 'flex' && lastParentCSS['flex-direction'].indexOf('row') === 0),
  522. isWide = (target.offsetWidth > dragEl.offsetWidth),
  523. isLong = (target.offsetHeight > dragEl.offsetHeight),
  524. halfway = (floating ? (evt.clientX - targetRect.left) / width : (evt.clientY - targetRect.top) / height) > 0.5,
  525. nextSibling = target.nextElementSibling,
  526. moveVector = _onMove(rootEl, el, dragEl, dragRect, target, targetRect),
  527. after
  528. ;
  529. if (moveVector !== false) {
  530. _silent = true;
  531. setTimeout(_unsilent, 30);
  532. _cloneHide(isOwner);
  533. if (moveVector === 1 || moveVector === -1) {
  534. after = (moveVector === 1);
  535. }
  536. else if (floating) {
  537. var elTop = dragEl.offsetTop,
  538. tgTop = target.offsetTop;
  539. if (elTop === tgTop) {
  540. after = (target.previousElementSibling === dragEl) && !isWide || halfway && isWide;
  541. } else {
  542. after = tgTop > elTop;
  543. }
  544. } else {
  545. after = (nextSibling !== dragEl) && !isLong || halfway && isLong;
  546. }
  547. if (!dragEl.contains(el)) {
  548. if (after && !nextSibling) {
  549. el.appendChild(dragEl);
  550. } else {
  551. target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
  552. }
  553. }
  554. parentEl = dragEl.parentNode; // actualization
  555. this._animate(dragRect, dragEl);
  556. this._animate(targetRect, target);
  557. }
  558. }
  559. }
  560. },
  561. _animate: function (prevRect, target) {
  562. var ms = this.options.animation;
  563. if (ms) {
  564. var currentRect = target.getBoundingClientRect();
  565. _css(target, 'transition', 'none');
  566. _css(target, 'transform', 'translate3d('
  567. + (prevRect.left - currentRect.left) + 'px,'
  568. + (prevRect.top - currentRect.top) + 'px,0)'
  569. );
  570. target.offsetWidth; // repaint
  571. _css(target, 'transition', 'all ' + ms + 'ms');
  572. _css(target, 'transform', 'translate3d(0,0,0)');
  573. clearTimeout(target.animated);
  574. target.animated = setTimeout(function () {
  575. _css(target, 'transition', '');
  576. _css(target, 'transform', '');
  577. target.animated = false;
  578. }, ms);
  579. }
  580. },
  581. _offUpEvents: function () {
  582. var ownerDocument = this.el.ownerDocument;
  583. _off(document, 'touchmove', this._onTouchMove);
  584. _off(ownerDocument, 'mouseup', this._onDrop);
  585. _off(ownerDocument, 'touchend', this._onDrop);
  586. _off(ownerDocument, 'touchcancel', this._onDrop);
  587. },
  588. _onDrop: function (/**Event*/evt) {
  589. var el = this.el,
  590. options = this.options;
  591. clearInterval(this._loopId);
  592. clearInterval(autoScroll.pid);
  593. clearTimeout(this._dragStartTimer);
  594. // Unbind events
  595. _off(document, 'mousemove', this._onTouchMove);
  596. if (this.nativeDraggable) {
  597. _off(document, 'drop', this);
  598. _off(el, 'dragstart', this._onDragStart);
  599. }
  600. this._offUpEvents();
  601. if (evt) {
  602. if (moved) {
  603. evt.preventDefault();
  604. !options.dropBubble && evt.stopPropagation();
  605. }
  606. ghostEl && ghostEl.parentNode.removeChild(ghostEl);
  607. if (dragEl) {
  608. if (this.nativeDraggable) {
  609. _off(dragEl, 'dragend', this);
  610. }
  611. _disableDraggable(dragEl);
  612. // Remove class's
  613. _toggleClass(dragEl, this.options.ghostClass, false);
  614. _toggleClass(dragEl, this.options.chosenClass, false);
  615. if (rootEl !== parentEl) {
  616. newIndex = _index(dragEl, options.draggable);
  617. if (newIndex >= 0) {
  618. // drag from one list and drop into another
  619. _dispatchEvent(null, parentEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
  620. _dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
  621. // Add event
  622. _dispatchEvent(null, parentEl, 'add', dragEl, rootEl, oldIndex, newIndex);
  623. // Remove event
  624. _dispatchEvent(this, rootEl, 'remove', dragEl, rootEl, oldIndex, newIndex);
  625. }
  626. }
  627. else {
  628. // Remove clone
  629. cloneEl && cloneEl.parentNode.removeChild(cloneEl);
  630. if (dragEl.nextSibling !== nextEl) {
  631. // Get the index of the dragged element within its parent
  632. newIndex = _index(dragEl, options.draggable);
  633. if (newIndex >= 0) {
  634. // drag & drop within the same list
  635. _dispatchEvent(this, rootEl, 'update', dragEl, rootEl, oldIndex, newIndex);
  636. _dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
  637. }
  638. }
  639. }
  640. if (Sortable.active) {
  641. if (newIndex === null || newIndex === -1) {
  642. newIndex = oldIndex;
  643. }
  644. _dispatchEvent(this, rootEl, 'end', dragEl, rootEl, oldIndex, newIndex);
  645. // Save sorting
  646. this.save();
  647. }
  648. }
  649. }
  650. this._nulling();
  651. },
  652. _nulling: function() {
  653. // Nulling
  654. rootEl =
  655. dragEl =
  656. parentEl =
  657. ghostEl =
  658. nextEl =
  659. cloneEl =
  660. scrollEl =
  661. scrollParentEl =
  662. tapEvt =
  663. touchEvt =
  664. moved =
  665. newIndex =
  666. lastEl =
  667. lastCSS =
  668. activeGroup =
  669. Sortable.active = null;
  670. },
  671. handleEvent: function (/**Event*/evt) {
  672. var type = evt.type;
  673. if (type === 'dragover' || type === 'dragenter') {
  674. if (dragEl) {
  675. this._onDragOver(evt);
  676. _globalDragOver(evt);
  677. }
  678. }
  679. else if (type === 'drop' || type === 'dragend') {
  680. this._onDrop(evt);
  681. }
  682. },
  683. /**
  684. * Serializes the item into an array of string.
  685. * @returns {String[]}
  686. */
  687. toArray: function () {
  688. var order = [],
  689. el,
  690. children = this.el.children,
  691. i = 0,
  692. n = children.length,
  693. options = this.options;
  694. for (; i < n; i++) {
  695. el = children[i];
  696. if (_closest(el, options.draggable, this.el)) {
  697. order.push(el.getAttribute(options.dataIdAttr) || _generateId(el));
  698. }
  699. }
  700. return order;
  701. },
  702. /**
  703. * Sorts the elements according to the array.
  704. * @param {String[]} order order of the items
  705. */
  706. sort: function (order) {
  707. var items = {}, rootEl = this.el;
  708. this.toArray().forEach(function (id, i) {
  709. var el = rootEl.children[i];
  710. if (_closest(el, this.options.draggable, rootEl)) {
  711. items[id] = el;
  712. }
  713. }, this);
  714. order.forEach(function (id) {
  715. if (items[id]) {
  716. rootEl.removeChild(items[id]);
  717. rootEl.appendChild(items[id]);
  718. }
  719. });
  720. },
  721. /**
  722. * Save the current sorting
  723. */
  724. save: function () {
  725. var store = this.options.store;
  726. store && store.set(this);
  727. },
  728. /**
  729. * For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree.
  730. * @param {HTMLElement} el
  731. * @param {String} [selector] default: `options.draggable`
  732. * @returns {HTMLElement|null}
  733. */
  734. closest: function (el, selector) {
  735. return _closest(el, selector || this.options.draggable, this.el);
  736. },
  737. /**
  738. * Set/get option
  739. * @param {string} name
  740. * @param {*} [value]
  741. * @returns {*}
  742. */
  743. option: function (name, value) {
  744. var options = this.options;
  745. if (value === void 0) {
  746. return options[name];
  747. } else {
  748. options[name] = value;
  749. if (name === 'group') {
  750. _prepareGroup(options);
  751. }
  752. }
  753. },
  754. /**
  755. * Destroy
  756. */
  757. destroy: function () {
  758. var el = this.el;
  759. el[expando] = null;
  760. _off(el, 'mousedown', this._onTapStart);
  761. _off(el, 'touchstart', this._onTapStart);
  762. if (this.nativeDraggable) {
  763. _off(el, 'dragover', this);
  764. _off(el, 'dragenter', this);
  765. }
  766. // Remove draggable attributes
  767. Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function (el) {
  768. el.removeAttribute('draggable');
  769. });
  770. touchDragOverListeners.splice(touchDragOverListeners.indexOf(this._onDragOver), 1);
  771. this._onDrop();
  772. this.el = el = null;
  773. }
  774. };
  775. function _cloneHide(state) {
  776. if (cloneEl && (cloneEl.state !== state)) {
  777. _css(cloneEl, 'display', state ? 'none' : '');
  778. !state && cloneEl.state && rootEl.insertBefore(cloneEl, dragEl);
  779. cloneEl.state = state;
  780. }
  781. }
  782. function _closest(/**HTMLElement*/el, /**String*/selector, /**HTMLElement*/ctx) {
  783. if (el) {
  784. ctx = ctx || document;
  785. do {
  786. if (
  787. (selector === '>*' && el.parentNode === ctx)
  788. || _matches(el, selector)
  789. ) {
  790. return el;
  791. }
  792. }
  793. while (el !== ctx && (el = el.parentNode));
  794. }
  795. return null;
  796. }
  797. function _globalDragOver(/**Event*/evt) {
  798. if (evt.dataTransfer) {
  799. evt.dataTransfer.dropEffect = 'move';
  800. }
  801. evt.preventDefault();
  802. }
  803. function _on(el, event, fn) {
  804. el.addEventListener(event, fn, false);
  805. }
  806. function _off(el, event, fn) {
  807. el.removeEventListener(event, fn, false);
  808. }
  809. function _toggleClass(el, name, state) {
  810. if (el) {
  811. if (el.classList) {
  812. el.classList[state ? 'add' : 'remove'](name);
  813. }
  814. else {
  815. var className = (' ' + el.className + ' ').replace(RSPACE, ' ').replace(' ' + name + ' ', ' ');
  816. el.className = (className + (state ? ' ' + name : '')).replace(RSPACE, ' ');
  817. }
  818. }
  819. }
  820. function _css(el, prop, val) {
  821. var style = el && el.style;
  822. if (style) {
  823. if (val === void 0) {
  824. if (document.defaultView && document.defaultView.getComputedStyle) {
  825. val = document.defaultView.getComputedStyle(el, '');
  826. }
  827. else if (el.currentStyle) {
  828. val = el.currentStyle;
  829. }
  830. return prop === void 0 ? val : val[prop];
  831. }
  832. else {
  833. if (!(prop in style)) {
  834. prop = '-webkit-' + prop;
  835. }
  836. style[prop] = val + (typeof val === 'string' ? '' : 'px');
  837. }
  838. }
  839. }
  840. function _find(ctx, tagName, iterator) {
  841. if (ctx) {
  842. var list = ctx.getElementsByTagName(tagName), i = 0, n = list.length;
  843. if (iterator) {
  844. for (; i < n; i++) {
  845. iterator(list[i], i);
  846. }
  847. }
  848. return list;
  849. }
  850. return [];
  851. }
  852. function _dispatchEvent(sortable, rootEl, name, targetEl, fromEl, startIndex, newIndex) {
  853. var evt = document.createEvent('Event'),
  854. options = (sortable || rootEl[expando]).options,
  855. onName = 'on' + name.charAt(0).toUpperCase() + name.substr(1);
  856. evt.initEvent(name, true, true);
  857. evt.to = rootEl;
  858. evt.from = fromEl || rootEl;
  859. evt.item = targetEl || rootEl;
  860. evt.clone = cloneEl;
  861. evt.oldIndex = startIndex;
  862. evt.newIndex = newIndex;
  863. rootEl.dispatchEvent(evt);
  864. if (options[onName]) {
  865. options[onName].call(sortable, evt);
  866. }
  867. }
  868. function _onMove(fromEl, toEl, dragEl, dragRect, targetEl, targetRect) {
  869. var evt,
  870. sortable = fromEl[expando],
  871. onMoveFn = sortable.options.onMove,
  872. retVal;
  873. evt = document.createEvent('Event');
  874. evt.initEvent('move', true, true);
  875. evt.to = toEl;
  876. evt.from = fromEl;
  877. evt.dragged = dragEl;
  878. evt.draggedRect = dragRect;
  879. evt.related = targetEl || toEl;
  880. evt.relatedRect = targetRect || toEl.getBoundingClientRect();
  881. fromEl.dispatchEvent(evt);
  882. if (onMoveFn) {
  883. retVal = onMoveFn.call(sortable, evt);
  884. }
  885. return retVal;
  886. }
  887. function _disableDraggable(el) {
  888. el.draggable = false;
  889. }
  890. function _unsilent() {
  891. _silent = false;
  892. }
  893. /** @returns {HTMLElement|false} */
  894. function _ghostIsLast(el, evt) {
  895. var lastEl = el.lastElementChild,
  896. rect = lastEl.getBoundingClientRect();
  897. return ((evt.clientY - (rect.top + rect.height) > 5) || (evt.clientX - (rect.right + rect.width) > 5)) && lastEl; // min delta
  898. }
  899. /**
  900. * Generate id
  901. * @param {HTMLElement} el
  902. * @returns {String}
  903. * @private
  904. */
  905. function _generateId(el) {
  906. var str = el.tagName + el.className + el.src + el.href + el.textContent,
  907. i = str.length,
  908. sum = 0;
  909. while (i--) {
  910. sum += str.charCodeAt(i);
  911. }
  912. return sum.toString(36);
  913. }
  914. /**
  915. * Returns the index of an element within its parent for a selected set of
  916. * elements
  917. * @param {HTMLElement} el
  918. * @param {selector} selector
  919. * @return {number}
  920. */
  921. function _index(el, selector) {
  922. var index = 0;
  923. if (!el || !el.parentNode) {
  924. return -1;
  925. }
  926. while (el && (el = el.previousElementSibling)) {
  927. if (el.nodeName.toUpperCase() !== 'TEMPLATE'
  928. && _matches(el, selector)) {
  929. index++;
  930. }
  931. }
  932. return index;
  933. }
  934. function _matches(/**HTMLElement*/el, /**String*/selector) {
  935. if (el) {
  936. selector = selector.split('.');
  937. var tag = selector.shift().toUpperCase(),
  938. re = new RegExp('\\s(' + selector.join('|') + ')(?=\\s)', 'g');
  939. return (
  940. (tag === '' || el.nodeName.toUpperCase() == tag) &&
  941. (!selector.length || ((' ' + el.className + ' ').match(re) || []).length == selector.length)
  942. );
  943. }
  944. return false;
  945. }
  946. function _throttle(callback, ms) {
  947. var args, _this;
  948. return function () {
  949. if (args === void 0) {
  950. args = arguments;
  951. _this = this;
  952. setTimeout(function () {
  953. if (args.length === 1) {
  954. callback.call(_this, args[0]);
  955. } else {
  956. callback.apply(_this, args);
  957. }
  958. args = void 0;
  959. }, ms);
  960. }
  961. };
  962. }
  963. function _extend(dst, src) {
  964. if (dst && src) {
  965. for (var key in src) {
  966. if (src.hasOwnProperty(key)) {
  967. dst[key] = src[key];
  968. }
  969. }
  970. }
  971. return dst;
  972. }
  973. // Export utils
  974. Sortable.utils = {
  975. on: _on,
  976. off: _off,
  977. css: _css,
  978. find: _find,
  979. is: function (el, selector) {
  980. return !!_closest(el, selector, el);
  981. },
  982. extend: _extend,
  983. throttle: _throttle,
  984. closest: _closest,
  985. toggleClass: _toggleClass,
  986. index: _index
  987. };
  988. /**
  989. * Create sortable instance
  990. * @param {HTMLElement} el
  991. * @param {Object} [options]
  992. */
  993. Sortable.create = function (el, options) {
  994. return new Sortable(el, options);
  995. };
  996. // Export
  997. Sortable.version = '1.4.2';
  998. return Sortable;
  999. });