PageRenderTime 47ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/files/sortable/1.0.1/Sortable.js

https://gitlab.com/Mirros/jsdelivr
JavaScript | 1012 lines | 695 code | 229 blank | 88 comment | 173 complexity | b54dc1d1f551067a078126d219558ada MD5 | raw file
  1. /**!
  2. * Sortable
  3. * @author RubaXa <trash@rubaxa.org>
  4. * @license MIT
  5. */
  6. (function (factory) {
  7. "use strict";
  8. if (typeof define === "function" && define.amd) {
  9. define(factory);
  10. }
  11. else if (typeof module != "undefined" && typeof module.exports != "undefined") {
  12. module.exports = factory();
  13. }
  14. else if (typeof Package !== "undefined") {
  15. Sortable = factory(); // export for Meteor.js
  16. }
  17. else {
  18. /* jshint sub:true */
  19. window["Sortable"] = factory();
  20. }
  21. })(function () {
  22. "use strict";
  23. var dragEl,
  24. ghostEl,
  25. cloneEl,
  26. rootEl,
  27. scrollEl,
  28. nextEl,
  29. lastEl,
  30. lastCSS,
  31. oldIndex,
  32. newIndex,
  33. activeGroup,
  34. autoScroll = {},
  35. tapEvt,
  36. touchEvt,
  37. expando = 'Sortable' + (new Date).getTime(),
  38. win = window,
  39. document = win.document,
  40. parseInt = win.parseInt,
  41. supportIEdnd = !!document.createElement('div').dragDrop,
  42. _silent = false,
  43. _dispatchEvent = function (rootEl, name, targetEl, fromEl, startIndex, newIndex) {
  44. var evt = document.createEvent('Event');
  45. evt.initEvent(name, true, true);
  46. evt.item = targetEl || rootEl;
  47. evt.from = fromEl || rootEl;
  48. evt.clone = cloneEl;
  49. evt.oldIndex = startIndex;
  50. evt.newIndex = newIndex;
  51. rootEl.dispatchEvent(evt);
  52. },
  53. _customEvents = 'onAdd onUpdate onRemove onStart onEnd onFilter onSort'.split(' '),
  54. noop = function () {},
  55. abs = Math.abs,
  56. slice = [].slice,
  57. touchDragOverListeners = []
  58. ;
  59. /**
  60. * @class Sortable
  61. * @param {HTMLElement} el
  62. * @param {Object} [options]
  63. */
  64. function Sortable(el, options) {
  65. this.el = el; // root element
  66. this.options = options = (options || {});
  67. // Default options
  68. var defaults = {
  69. group: Math.random(),
  70. sort: true,
  71. disabled: false,
  72. store: null,
  73. handle: null,
  74. scroll: true,
  75. scrollSensitivity: 30,
  76. scrollSpeed: 10,
  77. draggable: /[uo]l/i.test(el.nodeName) ? 'li' : '>*',
  78. ghostClass: 'sortable-ghost',
  79. ignore: 'a, img',
  80. filter: null,
  81. animation: 0,
  82. setData: function (dataTransfer, dragEl) {
  83. dataTransfer.setData('Text', dragEl.textContent);
  84. },
  85. dropBubble: false,
  86. dragoverBubble: false
  87. };
  88. // Set default options
  89. for (var name in defaults) {
  90. !(name in options) && (options[name] = defaults[name]);
  91. }
  92. var group = options.group;
  93. if (!group || typeof group != 'object') {
  94. group = options.group = { name: group };
  95. }
  96. ['pull', 'put'].forEach(function (key) {
  97. if (!(key in group)) {
  98. group[key] = true;
  99. }
  100. });
  101. // Define events
  102. _customEvents.forEach(function (name) {
  103. options[name] = _bind(this, options[name] || noop);
  104. _on(el, name.substr(2).toLowerCase(), options[name]);
  105. }, this);
  106. // Export group name
  107. el[expando] = group.name + ' ' + (group.put.join ? group.put.join(' ') : '');
  108. // Bind all private methods
  109. for (var fn in this) {
  110. if (fn.charAt(0) === '_') {
  111. this[fn] = _bind(this, this[fn]);
  112. }
  113. }
  114. // Bind events
  115. _on(el, 'mousedown', this._onTapStart);
  116. _on(el, 'touchstart', this._onTapStart);
  117. supportIEdnd && _on(el, 'selectstart', this._onTapStart);
  118. _on(el, 'dragover', this._onDragOver);
  119. _on(el, 'dragenter', this._onDragOver);
  120. touchDragOverListeners.push(this._onDragOver);
  121. // Restore sorting
  122. options.store && this.sort(options.store.get(this));
  123. }
  124. Sortable.prototype = /** @lends Sortable.prototype */ {
  125. constructor: Sortable,
  126. _dragStarted: function () {
  127. // Apply effect
  128. _toggleClass(dragEl, this.options.ghostClass, true);
  129. Sortable.active = this;
  130. // Drag start event
  131. _dispatchEvent(rootEl, 'start', dragEl, rootEl, oldIndex);
  132. },
  133. _onTapStart: function (/**Event|TouchEvent*/evt) {
  134. var type = evt.type,
  135. touch = evt.touches && evt.touches[0],
  136. target = (touch || evt).target,
  137. originalTarget = target,
  138. options = this.options,
  139. el = this.el,
  140. filter = options.filter;
  141. if (type === 'mousedown' && evt.button !== 0 || options.disabled) {
  142. return; // only left button or enabled
  143. }
  144. if (options.handle) {
  145. target = _closest(target, options.handle, el);
  146. }
  147. target = _closest(target, options.draggable, el);
  148. // get the index of the dragged element within its parent
  149. oldIndex = _index(target);
  150. // Check filter
  151. if (typeof filter === 'function') {
  152. if (filter.call(this, evt, target, this)) {
  153. _dispatchEvent(originalTarget, 'filter', target, el, oldIndex);
  154. evt.preventDefault();
  155. return; // cancel dnd
  156. }
  157. }
  158. else if (filter) {
  159. filter = filter.split(',').some(function (criteria) {
  160. criteria = _closest(originalTarget, criteria.trim(), el);
  161. if (criteria) {
  162. _dispatchEvent(criteria, 'filter', target, el, oldIndex);
  163. return true;
  164. }
  165. });
  166. if (filter) {
  167. evt.preventDefault();
  168. return; // cancel dnd
  169. }
  170. }
  171. // Prepare `dragstart`
  172. if (target && !dragEl && (target.parentNode === el)) {
  173. // IE 9 Support
  174. (type === 'selectstart') && target.dragDrop();
  175. tapEvt = evt;
  176. rootEl = this.el;
  177. dragEl = target;
  178. nextEl = dragEl.nextSibling;
  179. activeGroup = this.options.group;
  180. dragEl.draggable = true;
  181. // Disable "draggable"
  182. options.ignore.split(',').forEach(function (criteria) {
  183. _find(target, criteria.trim(), _disableDraggable);
  184. });
  185. if (touch) {
  186. // Touch device support
  187. tapEvt = {
  188. target: target,
  189. clientX: touch.clientX,
  190. clientY: touch.clientY
  191. };
  192. this._onDragStart(tapEvt, true);
  193. evt.preventDefault();
  194. }
  195. _on(document, 'mouseup', this._onDrop);
  196. _on(document, 'touchend', this._onDrop);
  197. _on(document, 'touchcancel', this._onDrop);
  198. _on(dragEl, 'dragend', this);
  199. _on(rootEl, 'dragstart', this._onDragStart);
  200. _on(document, 'dragover', this);
  201. try {
  202. if (document.selection) {
  203. document.selection.empty();
  204. } else {
  205. window.getSelection().removeAllRanges();
  206. }
  207. } catch (err) {
  208. }
  209. }
  210. },
  211. _emulateDragOver: function () {
  212. if (touchEvt) {
  213. _css(ghostEl, 'display', 'none');
  214. var target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY),
  215. parent = target,
  216. groupName = this.options.group.name,
  217. i = touchDragOverListeners.length;
  218. if (parent) {
  219. do {
  220. if ((' ' + parent[expando] + ' ').indexOf(groupName) > -1) {
  221. while (i--) {
  222. touchDragOverListeners[i]({
  223. clientX: touchEvt.clientX,
  224. clientY: touchEvt.clientY,
  225. target: target,
  226. rootEl: parent
  227. });
  228. }
  229. break;
  230. }
  231. target = parent; // store last element
  232. }
  233. /* jshint boss:true */
  234. while (parent = parent.parentNode);
  235. }
  236. _css(ghostEl, 'display', '');
  237. }
  238. },
  239. _onTouchMove: function (/**TouchEvent*/evt) {
  240. if (tapEvt) {
  241. var touch = evt.touches[0],
  242. dx = touch.clientX - tapEvt.clientX,
  243. dy = touch.clientY - tapEvt.clientY,
  244. translate3d = 'translate3d(' + dx + 'px,' + dy + 'px,0)';
  245. touchEvt = touch;
  246. _css(ghostEl, 'webkitTransform', translate3d);
  247. _css(ghostEl, 'mozTransform', translate3d);
  248. _css(ghostEl, 'msTransform', translate3d);
  249. _css(ghostEl, 'transform', translate3d);
  250. this._onDrag(touch);
  251. evt.preventDefault();
  252. }
  253. },
  254. _onDragStart: function (/**Event*/evt, /**boolean*/isTouch) {
  255. var dataTransfer = evt.dataTransfer,
  256. options = this.options;
  257. this._offUpEvents();
  258. if (activeGroup.pull == 'clone') {
  259. cloneEl = dragEl.cloneNode(true);
  260. _css(cloneEl, 'display', 'none');
  261. rootEl.insertBefore(cloneEl, dragEl);
  262. }
  263. if (isTouch) {
  264. var rect = dragEl.getBoundingClientRect(),
  265. css = _css(dragEl),
  266. ghostRect;
  267. ghostEl = dragEl.cloneNode(true);
  268. _css(ghostEl, 'top', rect.top - parseInt(css.marginTop, 10));
  269. _css(ghostEl, 'left', rect.left - parseInt(css.marginLeft, 10));
  270. _css(ghostEl, 'width', rect.width);
  271. _css(ghostEl, 'height', rect.height);
  272. _css(ghostEl, 'opacity', '0.8');
  273. _css(ghostEl, 'position', 'fixed');
  274. _css(ghostEl, 'zIndex', '100000');
  275. rootEl.appendChild(ghostEl);
  276. // Fixing dimensions.
  277. ghostRect = ghostEl.getBoundingClientRect();
  278. _css(ghostEl, 'width', rect.width * 2 - ghostRect.width);
  279. _css(ghostEl, 'height', rect.height * 2 - ghostRect.height);
  280. // Bind touch events
  281. _on(document, 'touchmove', this._onTouchMove);
  282. _on(document, 'touchend', this._onDrop);
  283. _on(document, 'touchcancel', this._onDrop);
  284. this._loopId = setInterval(this._emulateDragOver, 150);
  285. }
  286. else {
  287. if (dataTransfer) {
  288. dataTransfer.effectAllowed = 'move';
  289. options.setData && options.setData.call(this, dataTransfer, dragEl);
  290. }
  291. _on(document, 'drop', this);
  292. }
  293. scrollEl = options.scroll;
  294. if (scrollEl === true) {
  295. scrollEl = rootEl;
  296. do {
  297. if ((scrollEl.offsetWidth < scrollEl.scrollWidth) ||
  298. (scrollEl.offsetHeight < scrollEl.scrollHeight)
  299. ) {
  300. break;
  301. }
  302. /* jshint boss:true */
  303. } while (scrollEl = scrollEl.parentNode);
  304. }
  305. setTimeout(this._dragStarted, 0);
  306. },
  307. _onDrag: _throttle(function (/**Event*/evt) {
  308. // Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=505521
  309. if (rootEl && this.options.scroll) {
  310. var el,
  311. rect,
  312. options = this.options,
  313. sens = options.scrollSensitivity,
  314. speed = options.scrollSpeed,
  315. x = evt.clientX,
  316. y = evt.clientY,
  317. winWidth = window.innerWidth,
  318. winHeight = window.innerHeight,
  319. vx = (winWidth - x <= sens) - (x <= sens),
  320. vy = (winHeight - y <= sens) - (y <= sens)
  321. ;
  322. if (vx || vy) {
  323. el = win;
  324. }
  325. else if (scrollEl) {
  326. el = scrollEl;
  327. rect = scrollEl.getBoundingClientRect();
  328. vx = (abs(rect.right - x) <= sens) - (abs(rect.left - x) <= sens);
  329. vy = (abs(rect.bottom - y) <= sens) - (abs(rect.top - y) <= sens);
  330. }
  331. if (autoScroll.vx !== vx || autoScroll.vy !== vy || autoScroll.el !== el) {
  332. autoScroll.el = el;
  333. autoScroll.vx = vx;
  334. autoScroll.vy = vy;
  335. clearInterval(autoScroll.pid);
  336. if (el) {
  337. autoScroll.pid = setInterval(function () {
  338. if (el === win) {
  339. win.scrollTo(win.scrollX + vx * speed, win.scrollY + vy * speed);
  340. } else {
  341. vy && (el.scrollTop += vy * speed);
  342. vx && (el.scrollLeft += vx * speed);
  343. }
  344. }, 24);
  345. }
  346. }
  347. }
  348. }, 30),
  349. _onDragOver: function (/**Event*/evt) {
  350. var el = this.el,
  351. target,
  352. dragRect,
  353. revert,
  354. options = this.options,
  355. group = options.group,
  356. groupPut = group.put,
  357. isOwner = (activeGroup === group),
  358. canSort = options.sort;
  359. if (evt.preventDefault !== void 0) {
  360. evt.preventDefault();
  361. !options.dragoverBubble && evt.stopPropagation();
  362. }
  363. if (!_silent && activeGroup &&
  364. (isOwner
  365. ? canSort || (revert = !rootEl.contains(dragEl))
  366. : activeGroup.pull && groupPut && (
  367. (activeGroup.name === group.name) || // by Name
  368. (groupPut.indexOf && ~groupPut.indexOf(activeGroup.name)) // by Array
  369. )
  370. ) &&
  371. (evt.rootEl === void 0 || evt.rootEl === this.el)
  372. ) {
  373. target = _closest(evt.target, options.draggable, el);
  374. dragRect = dragEl.getBoundingClientRect();
  375. if (revert) {
  376. _cloneHide(true);
  377. if (cloneEl || nextEl) {
  378. rootEl.insertBefore(dragEl, cloneEl || nextEl);
  379. }
  380. else if (!canSort) {
  381. rootEl.appendChild(dragEl);
  382. }
  383. return;
  384. }
  385. if ((el.children.length === 0) || (el.children[0] === ghostEl) ||
  386. (el === evt.target) && (target = _ghostInBottom(el, evt))
  387. ) {
  388. if (target) {
  389. if (target.animated) {
  390. return;
  391. }
  392. targetRect = target.getBoundingClientRect();
  393. }
  394. _cloneHide(isOwner);
  395. el.appendChild(dragEl);
  396. this._animate(dragRect, dragEl);
  397. target && this._animate(targetRect, target);
  398. }
  399. else if (target && !target.animated && target !== dragEl && (target.parentNode[expando] !== void 0)) {
  400. if (lastEl !== target) {
  401. lastEl = target;
  402. lastCSS = _css(target);
  403. }
  404. var targetRect = target.getBoundingClientRect(),
  405. width = targetRect.right - targetRect.left,
  406. height = targetRect.bottom - targetRect.top,
  407. floating = /left|right|inline/.test(lastCSS.cssFloat + lastCSS.display),
  408. isWide = (target.offsetWidth > dragEl.offsetWidth),
  409. isLong = (target.offsetHeight > dragEl.offsetHeight),
  410. halfway = (floating ? (evt.clientX - targetRect.left) / width : (evt.clientY - targetRect.top) / height) > 0.5,
  411. nextSibling = target.nextElementSibling,
  412. after
  413. ;
  414. _silent = true;
  415. setTimeout(_unsilent, 30);
  416. _cloneHide(isOwner);
  417. if (floating) {
  418. after = (target.previousElementSibling === dragEl) && !isWide || halfway && isWide;
  419. } else {
  420. after = (nextSibling !== dragEl) && !isLong || halfway && isLong;
  421. }
  422. if (after && !nextSibling) {
  423. el.appendChild(dragEl);
  424. } else {
  425. target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
  426. }
  427. this._animate(dragRect, dragEl);
  428. this._animate(targetRect, target);
  429. }
  430. }
  431. },
  432. _animate: function (prevRect, target) {
  433. var ms = this.options.animation;
  434. if (ms) {
  435. var currentRect = target.getBoundingClientRect();
  436. _css(target, 'transition', 'none');
  437. _css(target, 'transform', 'translate3d('
  438. + (prevRect.left - currentRect.left) + 'px,'
  439. + (prevRect.top - currentRect.top) + 'px,0)'
  440. );
  441. target.offsetWidth; // repaint
  442. _css(target, 'transition', 'all ' + ms + 'ms');
  443. _css(target, 'transform', 'translate3d(0,0,0)');
  444. clearTimeout(target.animated);
  445. target.animated = setTimeout(function () {
  446. _css(target, 'transition', '');
  447. target.animated = false;
  448. }, ms);
  449. }
  450. },
  451. _offUpEvents: function () {
  452. _off(document, 'mouseup', this._onDrop);
  453. _off(document, 'touchmove', this._onTouchMove);
  454. _off(document, 'touchend', this._onDrop);
  455. _off(document, 'touchcancel', this._onDrop);
  456. },
  457. _onDrop: function (/**Event*/evt) {
  458. var el = this.el,
  459. options = this.options;
  460. clearInterval(this._loopId);
  461. clearInterval(autoScroll.pid);
  462. // Unbind events
  463. _off(document, 'drop', this);
  464. _off(document, 'dragover', this);
  465. _off(el, 'dragstart', this._onDragStart);
  466. this._offUpEvents();
  467. if (evt) {
  468. evt.preventDefault();
  469. !options.dropBubble && evt.stopPropagation();
  470. ghostEl && ghostEl.parentNode.removeChild(ghostEl);
  471. if (dragEl) {
  472. _off(dragEl, 'dragend', this);
  473. _disableDraggable(dragEl);
  474. _toggleClass(dragEl, this.options.ghostClass, false);
  475. if (rootEl !== dragEl.parentNode) {
  476. newIndex = _index(dragEl);
  477. // drag from one list and drop into another
  478. _dispatchEvent(dragEl.parentNode, 'sort', dragEl, rootEl, oldIndex, newIndex);
  479. _dispatchEvent(rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
  480. // Add event
  481. _dispatchEvent(dragEl, 'add', dragEl, rootEl, oldIndex, newIndex);
  482. // Remove event
  483. _dispatchEvent(rootEl, 'remove', dragEl, rootEl, oldIndex, newIndex);
  484. }
  485. else {
  486. // Remove clone
  487. cloneEl && cloneEl.parentNode.removeChild(cloneEl);
  488. if (dragEl.nextSibling !== nextEl) {
  489. // Get the index of the dragged element within its parent
  490. newIndex = _index(dragEl);
  491. // drag & drop within the same list
  492. _dispatchEvent(rootEl, 'update', dragEl, rootEl, oldIndex, newIndex);
  493. _dispatchEvent(rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
  494. }
  495. }
  496. // Drag end event
  497. Sortable.active && _dispatchEvent(rootEl, 'end', dragEl, rootEl, oldIndex, newIndex);
  498. }
  499. // Set NULL
  500. rootEl =
  501. dragEl =
  502. ghostEl =
  503. nextEl =
  504. cloneEl =
  505. tapEvt =
  506. touchEvt =
  507. lastEl =
  508. lastCSS =
  509. activeGroup =
  510. Sortable.active = null;
  511. // Save sorting
  512. this.save();
  513. }
  514. },
  515. handleEvent: function (/**Event*/evt) {
  516. var type = evt.type;
  517. if (type === 'dragover') {
  518. this._onDrag(evt);
  519. _globalDragOver(evt);
  520. }
  521. else if (type === 'drop' || type === 'dragend') {
  522. this._onDrop(evt);
  523. }
  524. },
  525. /**
  526. * Serializes the item into an array of string.
  527. * @returns {String[]}
  528. */
  529. toArray: function () {
  530. var order = [],
  531. el,
  532. children = this.el.children,
  533. i = 0,
  534. n = children.length;
  535. for (; i < n; i++) {
  536. el = children[i];
  537. if (_closest(el, this.options.draggable, this.el)) {
  538. order.push(el.getAttribute('data-id') || _generateId(el));
  539. }
  540. }
  541. return order;
  542. },
  543. /**
  544. * Sorts the elements according to the array.
  545. * @param {String[]} order order of the items
  546. */
  547. sort: function (order) {
  548. var items = {}, rootEl = this.el;
  549. this.toArray().forEach(function (id, i) {
  550. var el = rootEl.children[i];
  551. if (_closest(el, this.options.draggable, rootEl)) {
  552. items[id] = el;
  553. }
  554. }, this);
  555. order.forEach(function (id) {
  556. if (items[id]) {
  557. rootEl.removeChild(items[id]);
  558. rootEl.appendChild(items[id]);
  559. }
  560. });
  561. },
  562. /**
  563. * Save the current sorting
  564. */
  565. save: function () {
  566. var store = this.options.store;
  567. store && store.set(this);
  568. },
  569. /**
  570. * 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.
  571. * @param {HTMLElement} el
  572. * @param {String} [selector] default: `options.draggable`
  573. * @returns {HTMLElement|null}
  574. */
  575. closest: function (el, selector) {
  576. return _closest(el, selector || this.options.draggable, this.el);
  577. },
  578. /**
  579. * Set/get option
  580. * @param {string} name
  581. * @param {*} [value]
  582. * @returns {*}
  583. */
  584. option: function (name, value) {
  585. var options = this.options;
  586. if (value === void 0) {
  587. return options[name];
  588. } else {
  589. options[name] = value;
  590. }
  591. },
  592. /**
  593. * Destroy
  594. */
  595. destroy: function () {
  596. var el = this.el, options = this.options;
  597. _customEvents.forEach(function (name) {
  598. _off(el, name.substr(2).toLowerCase(), options[name]);
  599. });
  600. _off(el, 'mousedown', this._onTapStart);
  601. _off(el, 'touchstart', this._onTapStart);
  602. _off(el, 'selectstart', this._onTapStart);
  603. _off(el, 'dragover', this._onDragOver);
  604. _off(el, 'dragenter', this._onDragOver);
  605. //remove draggable attributes
  606. Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function (el) {
  607. el.removeAttribute('draggable');
  608. });
  609. touchDragOverListeners.splice(touchDragOverListeners.indexOf(this._onDragOver), 1);
  610. this._onDrop();
  611. this.el = null;
  612. }
  613. };
  614. function _cloneHide(state) {
  615. if (cloneEl && (cloneEl.state !== state)) {
  616. _css(cloneEl, 'display', state ? 'none' : '');
  617. !state && cloneEl.state && rootEl.insertBefore(cloneEl, dragEl);
  618. cloneEl.state = state;
  619. }
  620. }
  621. function _bind(ctx, fn) {
  622. var args = slice.call(arguments, 2);
  623. return fn.bind ? fn.bind.apply(fn, [ctx].concat(args)) : function () {
  624. return fn.apply(ctx, args.concat(slice.call(arguments)));
  625. };
  626. }
  627. function _closest(/**HTMLElement*/el, /**String*/selector, /**HTMLElement*/ctx) {
  628. if (el) {
  629. ctx = ctx || document;
  630. selector = selector.split('.');
  631. var tag = selector.shift().toUpperCase(),
  632. re = new RegExp('\\s(' + selector.join('|') + ')\\s', 'g');
  633. do {
  634. if (
  635. (tag === '>*' && el.parentNode === ctx) || (
  636. (tag === '' || el.nodeName.toUpperCase() == tag) &&
  637. (!selector.length || ((' ' + el.className + ' ').match(re) || []).length == selector.length)
  638. )
  639. ) {
  640. return el;
  641. }
  642. }
  643. while (el !== ctx && (el = el.parentNode));
  644. }
  645. return null;
  646. }
  647. function _globalDragOver(/**Event*/evt) {
  648. evt.dataTransfer.dropEffect = 'move';
  649. evt.preventDefault();
  650. }
  651. function _on(el, event, fn) {
  652. el.addEventListener(event, fn, false);
  653. }
  654. function _off(el, event, fn) {
  655. el.removeEventListener(event, fn, false);
  656. }
  657. function _toggleClass(el, name, state) {
  658. if (el) {
  659. if (el.classList) {
  660. el.classList[state ? 'add' : 'remove'](name);
  661. }
  662. else {
  663. var className = (' ' + el.className + ' ').replace(/\s+/g, ' ').replace(' ' + name + ' ', '');
  664. el.className = className + (state ? ' ' + name : '');
  665. }
  666. }
  667. }
  668. function _css(el, prop, val) {
  669. var style = el && el.style;
  670. if (style) {
  671. if (val === void 0) {
  672. if (document.defaultView && document.defaultView.getComputedStyle) {
  673. val = document.defaultView.getComputedStyle(el, '');
  674. }
  675. else if (el.currentStyle) {
  676. val = el.currentStyle;
  677. }
  678. return prop === void 0 ? val : val[prop];
  679. }
  680. else {
  681. if (!(prop in style)) {
  682. prop = '-webkit-' + prop;
  683. }
  684. style[prop] = val + (typeof val === 'string' ? '' : 'px');
  685. }
  686. }
  687. }
  688. function _find(ctx, tagName, iterator) {
  689. if (ctx) {
  690. var list = ctx.getElementsByTagName(tagName), i = 0, n = list.length;
  691. if (iterator) {
  692. for (; i < n; i++) {
  693. iterator(list[i], i);
  694. }
  695. }
  696. return list;
  697. }
  698. return [];
  699. }
  700. function _disableDraggable(el) {
  701. el.draggable = false;
  702. }
  703. function _unsilent() {
  704. _silent = false;
  705. }
  706. /** @returns {HTMLElement|false} */
  707. function _ghostInBottom(el, evt) {
  708. var lastEl = el.lastElementChild, rect = lastEl.getBoundingClientRect();
  709. return (evt.clientY - (rect.top + rect.height) > 5) && lastEl; // min delta
  710. }
  711. /**
  712. * Generate id
  713. * @param {HTMLElement} el
  714. * @returns {String}
  715. * @private
  716. */
  717. function _generateId(el) {
  718. var str = el.tagName + el.className + el.src + el.href + el.textContent,
  719. i = str.length,
  720. sum = 0;
  721. while (i--) {
  722. sum += str.charCodeAt(i);
  723. }
  724. return sum.toString(36);
  725. }
  726. /**
  727. * Returns the index of an element within its parent
  728. * @param el
  729. * @returns {number}
  730. * @private
  731. */
  732. function _index(/**HTMLElement*/el) {
  733. var index = 0;
  734. while (el && (el = el.previousElementSibling) && (el.nodeName.toUpperCase() !== 'TEMPLATE')) {
  735. index++;
  736. }
  737. return index;
  738. }
  739. function _throttle(callback, ms) {
  740. var args, _this;
  741. return function () {
  742. if (args === void 0) {
  743. args = arguments;
  744. _this = this;
  745. setTimeout(function () {
  746. if (args.length === 1) {
  747. callback.call(_this, args[0]);
  748. } else {
  749. callback.apply(_this, args);
  750. }
  751. args = void 0;
  752. }, ms);
  753. }
  754. };
  755. }
  756. // Export utils
  757. Sortable.utils = {
  758. on: _on,
  759. off: _off,
  760. css: _css,
  761. find: _find,
  762. bind: _bind,
  763. is: function (el, selector) {
  764. return !!_closest(el, selector, el);
  765. },
  766. throttle: _throttle,
  767. closest: _closest,
  768. toggleClass: _toggleClass,
  769. dispatchEvent: _dispatchEvent,
  770. index: _index
  771. };
  772. Sortable.version = '1.0.1';
  773. /**
  774. * Create sortable instance
  775. * @param {HTMLElement} el
  776. * @param {Object} [options]
  777. */
  778. Sortable.create = function (el, options) {
  779. return new Sortable(el, options);
  780. };
  781. // Export
  782. return Sortable;
  783. });