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