You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1336 lines
30 KiB

11 years ago
/**!
* Sortable
* @author RubaXa <trash@rubaxa.org>
* @license MIT
*/
10 years ago
(function (factory) {
11 years ago
"use strict";
10 years ago
if (typeof define === "function" && define.amd) {
11 years ago
define(factory);
11 years ago
}
10 years ago
else if (typeof module != "undefined" && typeof module.exports != "undefined") {
module.exports = factory();
}
10 years ago
else if (typeof Package !== "undefined") {
Sortable = factory(); // export for Meteor.js
}
11 years ago
else {
10 years ago
/* jshint sub:true */
11 years ago
window["Sortable"] = factory();
}
10 years ago
})(function () {
11 years ago
"use strict";
if (typeof window == "undefined" || typeof window.document == "undefined") {
return function() {
throw new Error( "Sortable.js requires a window with a document" );
}
}
11 years ago
10 years ago
var dragEl,
parentEl,
10 years ago
ghostEl,
cloneEl,
rootEl,
nextEl,
11 years ago
scrollEl,
scrollParentEl,
10 years ago
lastEl,
lastCSS,
lastParentCSS,
11 years ago
10 years ago
oldIndex,
newIndex,
10 years ago
activeGroup,
putSortable,
autoScroll = {},
11 years ago
10 years ago
tapEvt,
touchEvt,
11 years ago
moved,
10 years ago
/** @const */
RSPACE = /\s+/g,
10 years ago
expando = 'Sortable' + (new Date).getTime(),
11 years ago
10 years ago
win = window,
document = win.document,
parseInt = win.parseInt,
10 years ago
9 years ago
$ = win.jQuery || win.Zepto,
Polymer = win.Polymer,
supportDraggable = !!('draggable' in document.createElement('div')),
supportCssPointerEvents = (function (el) {
el = document.createElement('x');
el.style.cssText = 'pointer-events:auto';
return el.style.pointerEvents === 'auto';
})(),
10 years ago
_silent = false,
abs = Math.abs,
min = Math.min,
10 years ago
slice = [].slice,
11 years ago
touchDragOverListeners = [],
_autoScroll = _throttle(function (/**Event*/evt, /**Object*/options, /**HTMLElement*/rootEl) {
// Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=505521
if (rootEl && options.scroll) {
var el,
rect,
sens = options.scrollSensitivity,
speed = options.scrollSpeed,
x = evt.clientX,
y = evt.clientY,
winWidth = window.innerWidth,
winHeight = window.innerHeight,
vx,
vy
;
// Delect scrollEl
if (scrollParentEl !== rootEl) {
scrollEl = options.scroll;
scrollParentEl = rootEl;
if (scrollEl === true) {
scrollEl = rootEl;
do {
if ((scrollEl.offsetWidth < scrollEl.scrollWidth) ||
(scrollEl.offsetHeight < scrollEl.scrollHeight)
) {
break;
}
/* jshint boss:true */
} while (scrollEl = scrollEl.parentNode);
}
}
if (scrollEl) {
el = scrollEl;
rect = scrollEl.getBoundingClientRect();
vx = (abs(rect.right - x) <= sens) - (abs(rect.left - x) <= sens);
vy = (abs(rect.bottom - y) <= sens) - (abs(rect.top - y) <= sens);
}
if (!(vx || vy)) {
vx = (winWidth - x <= sens) - (x <= sens);
vy = (winHeight - y <= sens) - (y <= sens);
/* jshint expr:true */
(vx || vy) && (el = win);
}
if (autoScroll.vx !== vx || autoScroll.vy !== vy || autoScroll.el !== el) {
autoScroll.el = el;
autoScroll.vx = vx;
autoScroll.vy = vy;
clearInterval(autoScroll.pid);
if (el) {
autoScroll.pid = setInterval(function () {
if (el === win) {
win.scrollTo(win.pageXOffset + vx * speed, win.pageYOffset + vy * speed);
} else {
vy && (el.scrollTop += vy * speed);
vx && (el.scrollLeft += vx * speed);
}
}, 24);
}
}
}
}, 30),
_prepareGroup = function (options) {
function toFn(value, pull) {
if (value === void 0) {
value = group.name;
}
if (typeof value === 'function') {
return value;
} else {
return function (to, from) {
var fromGroup = from.options.group.name;
return pull
? value
: value && (value.join
? value.indexOf(fromGroup) > -1
: (fromGroup == value)
);
};
}
}
var group = {};
var originalGroup = options.group;
if (!originalGroup || typeof originalGroup != 'object') {
originalGroup = {name: originalGroup};
}
group.name = originalGroup.name;
group.checkPull = toFn(originalGroup.pull, true);
group.checkPut = toFn(originalGroup.put);
options.group = group;
}
11 years ago
;
11 years ago
/**
* @class Sortable
* @param {HTMLElement} el
* @param {Object} [options]
11 years ago
*/
10 years ago
function Sortable(el, options) {
if (!(el && el.nodeType && el.nodeType === 1)) {
throw 'Sortable: `el` must be HTMLElement, and not ' + {}.toString.call(el);
}
11 years ago
this.el = el; // root element
this.options = options = _extend({}, options);
11 years ago
// Export instance
el[expando] = this;
// Default options
var defaults = {
group: Math.random(),
sort: true,
disabled: false,
store: null,
handle: null,
scroll: true,
scrollSensitivity: 30,
scrollSpeed: 10,
draggable: /[uo]l/i.test(el.nodeName) ? 'li' : '>*',
ghostClass: 'sortable-ghost',
chosenClass: 'sortable-chosen',
ignore: 'a, img',
filter: null,
animation: 0,
setData: function (dataTransfer, dragEl) {
dataTransfer.setData('Text', dragEl.textContent);
},
dropBubble: false,
dragoverBubble: false,
dataIdAttr: 'data-id',
delay: 0,
forceFallback: false,
fallbackClass: 'sortable-fallback',
fallbackOnBody: false,
fallbackTolerance: 0
};
// Set default options
for (var name in defaults) {
!(name in options) && (options[name] = defaults[name]);
}
11 years ago
_prepareGroup(options);
11 years ago
11 years ago
// Bind all private methods
10 years ago
for (var fn in this) {
if (fn.charAt(0) === '_' && typeof this[fn] === 'function') {
this[fn] = this[fn].bind(this);
11 years ago
}
}
// Setup drag mode
this.nativeDraggable = options.forceFallback ? false : supportDraggable;
11 years ago
// Bind events
_on(el, 'mousedown', this._onTapStart);
_on(el, 'touchstart', this._onTapStart);
if (this.nativeDraggable) {
_on(el, 'dragover', this);
_on(el, 'dragenter', this);
}
11 years ago
touchDragOverListeners.push(this._onDragOver);
// Restore sorting
options.store && this.sort(options.store.get(this));
11 years ago
}
Sortable.prototype = /** @lends Sortable.prototype */ {
11 years ago
constructor: Sortable,
_onTapStart: function (/** Event|TouchEvent */evt) {
var _this = this,
el = this.el,
options = this.options,
type = evt.type,
touch = evt.touches && evt.touches[0],
target = (touch || evt).target,
originalTarget = target,
filter = options.filter,
startIndex;
// Don't trigger start event when an element is been dragged, otherwise the evt.oldindex always wrong when set option.group.
9 years ago
if (dragEl) {
return;
}
if (type === 'mousedown' && evt.button !== 0 || options.disabled) {
return; // only left button or enabled
}
11 years ago
target = _closest(target, options.draggable, el);
if (!target) {
return;
}
if (options.handle && !_closest(originalTarget, options.handle, el)) {
return;
}
// Get the index of the dragged element within its parent
startIndex = _index(target, options.draggable);
// Check filter
if (typeof filter === 'function') {
if (filter.call(this, evt, target, this)) {
_dispatchEvent(_this, originalTarget, 'filter', target, el, startIndex);
evt.preventDefault();
return; // cancel dnd
}
}
else if (filter) {
filter = filter.split(',').some(function (criteria) {
criteria = _closest(originalTarget, criteria.trim(), el);
if (criteria) {
_dispatchEvent(_this, criteria, 'filter', target, el, startIndex);
return true;
}
});
if (filter) {
evt.preventDefault();
return; // cancel dnd
}
}
// Prepare `dragstart`
this._prepareDragStart(evt, touch, target, startIndex);
},
_prepareDragStart: function (/** Event */evt, /** Touch */touch, /** HTMLElement */target, /** Number */startIndex) {
var _this = this,
el = _this.el,
options = _this.options,
ownerDocument = el.ownerDocument,
dragStartFn;
if (target && !dragEl && (target.parentNode === el)) {
tapEvt = evt;
rootEl = el;
dragEl = target;
parentEl = dragEl.parentNode;
nextEl = dragEl.nextSibling;
activeGroup = options.group;
oldIndex = startIndex;
this._lastX = (touch || evt).clientX;
this._lastY = (touch || evt).clientY;
dragStartFn = function () {
// Delayed drag has been triggered
// we can re-enable the events: touchmove/mousemove
_this._disableDelayedDrag();
// Make the element draggable
dragEl.draggable = _this.nativeDraggable;
// Chosen item
_toggleClass(dragEl, _this.options.chosenClass, true);
// Bind the events: dragstart/dragend
_this._triggerDragStart(touch);
// Drag start event
_dispatchEvent(_this, rootEl, 'choose', dragEl, rootEl, oldIndex);
};
// Disable "draggable"
options.ignore.split(',').forEach(function (criteria) {
_find(dragEl, criteria.trim(), _disableDraggable);
});
_on(ownerDocument, 'mouseup', _this._onDrop);
_on(ownerDocument, 'touchend', _this._onDrop);
_on(ownerDocument, 'touchcancel', _this._onDrop);
if (options.delay) {
// If the user moves the pointer or let go the click or touch
// before the delay has been reached:
// disable the delayed drag
_on(ownerDocument, 'mouseup', _this._disableDelayedDrag);
_on(ownerDocument, 'touchend', _this._disableDelayedDrag);
_on(ownerDocument, 'touchcancel', _this._disableDelayedDrag);
_on(ownerDocument, 'mousemove', _this._disableDelayedDrag);
_on(ownerDocument, 'touchmove', _this._disableDelayedDrag);
_this._dragStartTimer = setTimeout(dragStartFn, options.delay);
} else {
dragStartFn();
}
}
},
11 years ago
_disableDelayedDrag: function () {
var ownerDocument = this.el.ownerDocument;
clearTimeout(this._dragStartTimer);
_off(ownerDocument, 'mouseup', this._disableDelayedDrag);
_off(ownerDocument, 'touchend', this._disableDelayedDrag);
_off(ownerDocument, 'touchcancel', this._disableDelayedDrag);
_off(ownerDocument, 'mousemove', this._disableDelayedDrag);
_off(ownerDocument, 'touchmove', this._disableDelayedDrag);
},
_triggerDragStart: function (/** Touch */touch) {
if (touch) {
// Touch device support
tapEvt = {
target: dragEl,
clientX: touch.clientX,
clientY: touch.clientY
};
this._onDragStart(tapEvt, 'touch');
}
else if (!this.nativeDraggable) {
this._onDragStart(tapEvt, true);
}
else {
_on(dragEl, 'dragend', this);
_on(rootEl, 'dragstart', this._onDragStart);
}
try {
if (document.selection) {
document.selection.empty();
} else {
window.getSelection().removeAllRanges();
}
} catch (err) {
}
},
_dragStarted: function () {
if (rootEl && dragEl) {
// Apply effect
_toggleClass(dragEl, this.options.ghostClass, true);
Sortable.active = this;
// Drag start event
_dispatchEvent(this, rootEl, 'start', dragEl, rootEl, oldIndex);
}
11 years ago
},
10 years ago
_emulateDragOver: function () {
if (touchEvt) {
if (this._lastX === touchEvt.clientX && this._lastY === touchEvt.clientY) {
return;
}
this._lastX = touchEvt.clientX;
this._lastY = touchEvt.clientY;
if (!supportCssPointerEvents) {
_css(ghostEl, 'display', 'none');
}
11 years ago
10 years ago
var target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY),
parent = target,
groupName = ' ' + this.options.group.name + '',
10 years ago
i = touchDragOverListeners.length;
11 years ago
if (parent) {
do {
if (parent[expando]) {
while (i--) {
touchDragOverListeners[i]({
clientX: touchEvt.clientX,
clientY: touchEvt.clientY,
target: target,
rootEl: parent
});
}
break;
}
target = parent; // store last element
11 years ago
}
/* jshint boss:true */
while (parent = parent.parentNode);
11 years ago
}
if (!supportCssPointerEvents) {
_css(ghostEl, 'display', '');
}
11 years ago
}
},
10 years ago
_onTouchMove: function (/**TouchEvent*/evt) {
if (tapEvt) {
var options = this.options,
fallbackTolerance = options.fallbackTolerance,
touch = evt.touches ? evt.touches[0] : evt,
dx = touch.clientX - tapEvt.clientX,
dy = touch.clientY - tapEvt.clientY,
translate3d = evt.touches ? 'translate3d(' + dx + 'px,' + dy + 'px,0)' : 'translate(' + dx + 'px,' + dy + 'px)';
// only set the status to dragging, when we are actually dragging
9 years ago
if (!Sortable.active) {
if (fallbackTolerance &&
min(abs(touch.clientX - this._lastX), abs(touch.clientY - this._lastY)) < fallbackTolerance
) {
return;
}
this._dragStarted();
}
9 years ago
// as well as creating the ghost element on the document body
this._appendGhost();
9 years ago
moved = true;
11 years ago
touchEvt = touch;
10 years ago
_css(ghostEl, 'webkitTransform', translate3d);
_css(ghostEl, 'mozTransform', translate3d);
_css(ghostEl, 'msTransform', translate3d);
_css(ghostEl, 'transform', translate3d);
11 years ago
evt.preventDefault();
11 years ago
}
},
9 years ago
_appendGhost: function () {
if (!ghostEl) {
10 years ago
var rect = dragEl.getBoundingClientRect(),
css = _css(dragEl),
9 years ago
options = this.options,
10 years ago
ghostRect;
11 years ago
ghostEl = dragEl.cloneNode(true);
11 years ago
9 years ago
_toggleClass(ghostEl, options.ghostClass, false);
_toggleClass(ghostEl, options.fallbackClass, true);
11 years ago
_css(ghostEl, 'top', rect.top - parseInt(css.marginTop, 10));
_css(ghostEl, 'left', rect.left - parseInt(css.marginLeft, 10));
11 years ago
_css(ghostEl, 'width', rect.width);
_css(ghostEl, 'height', rect.height);
11 years ago
_css(ghostEl, 'opacity', '0.8');
_css(ghostEl, 'position', 'fixed');
_css(ghostEl, 'zIndex', '100000');
_css(ghostEl, 'pointerEvents', 'none');
11 years ago
9 years ago
options.fallbackOnBody && document.body.appendChild(ghostEl) || rootEl.appendChild(ghostEl);
11 years ago
// Fixing dimensions.
ghostRect = ghostEl.getBoundingClientRect();
10 years ago
_css(ghostEl, 'width', rect.width * 2 - ghostRect.width);
_css(ghostEl, 'height', rect.height * 2 - ghostRect.height);
}
},
_onDragStart: function (/**Event*/evt, /**boolean*/useFallback) {
var dataTransfer = evt.dataTransfer,
options = this.options;
this._offUpEvents();
if (activeGroup.checkPull(this, this, dragEl, evt) == 'clone') {
cloneEl = _clone(dragEl);
_css(cloneEl, 'display', 'none');
rootEl.insertBefore(cloneEl, dragEl);
_dispatchEvent(this, rootEl, 'clone', dragEl);
}
if (useFallback) {
10 years ago
if (useFallback === 'touch') {
// Bind touch events
_on(document, 'touchmove', this._onTouchMove);
_on(document, 'touchend', this._onDrop);
_on(document, 'touchcancel', this._onDrop);
} else {
// Old brwoser
_on(document, 'mousemove', this._onTouchMove);
_on(document, 'mouseup', this._onDrop);
}
11 years ago
this._loopId = setInterval(this._emulateDragOver, 50);
11 years ago
}
else {
if (dataTransfer) {
dataTransfer.effectAllowed = 'move';
options.setData && options.setData.call(this, dataTransfer, dragEl);
}
11 years ago
_on(document, 'drop', this);
setTimeout(this._dragStarted, 0);
11 years ago
}
},
10 years ago
_onDragOver: function (/**Event*/evt) {
var el = this.el,
target,
dragRect,
revert,
options = this.options,
group = options.group,
activeSortable = Sortable.active,
isOwner = (activeGroup === group),
canSort = options.sort;
if (evt.preventDefault !== void 0) {
evt.preventDefault();
!options.dragoverBubble && evt.stopPropagation();
}
moved = true;
10 years ago
if (activeGroup && !options.disabled &&
(isOwner
? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list
: (
putSortable === this ||
activeGroup.checkPull(this, activeSortable, dragEl, evt) && group.checkPut(this, activeSortable, dragEl, evt)
)
) &&
(evt.rootEl === void 0 || evt.rootEl === this.el) // touch fallback
10 years ago
) {
10 years ago
// Smart auto-scrolling
_autoScroll(evt, options, this.el);
if (_silent) {
return;
}
target = _closest(evt.target, options.draggable, el);
dragRect = dragEl.getBoundingClientRect();
putSortable = this;
if (revert) {
_cloneHide(true);
parentEl = rootEl; // actualization
if (cloneEl || nextEl) {
rootEl.insertBefore(dragEl, cloneEl || nextEl);
}
else if (!canSort) {
rootEl.appendChild(dragEl);
}
return;
}
11 years ago
10 years ago
if ((el.children.length === 0) || (el.children[0] === ghostEl) ||
(el === evt.target) && (target = _ghostIsLast(el, evt))
10 years ago
) {
if (target) {
if (target.animated) {
return;
}
targetRect = target.getBoundingClientRect();
}
11 years ago
_cloneHide(isOwner);
if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect) !== false) {
if (!dragEl.contains(el)) {
el.appendChild(dragEl);
parentEl = el; // actualization
}
this._animate(dragRect, dragEl);
target && this._animate(targetRect, target);
}
}
10 years ago
else if (target && !target.animated && target !== dragEl && (target.parentNode[expando] !== void 0)) {
if (lastEl !== target) {
lastEl = target;
lastCSS = _css(target);
lastParentCSS = _css(target.parentNode);
11 years ago
}
10 years ago
var targetRect = target.getBoundingClientRect(),
width = targetRect.right - targetRect.left,
height = targetRect.bottom - targetRect.top,
floating = /left|right|inline/.test(lastCSS.cssFloat + lastCSS.display)
|| (lastParentCSS.display == 'flex' && lastParentCSS['flex-direction'].indexOf('row') === 0),
10 years ago
isWide = (target.offsetWidth > dragEl.offsetWidth),
isLong = (target.offsetHeight > dragEl.offsetHeight),
halfway = (floating ? (evt.clientX - targetRect.left) / width : (evt.clientY - targetRect.top) / height) > 0.5,
nextSibling = target.nextElementSibling,
moveVector = _onMove(rootEl, el, dragEl, dragRect, target, targetRect),
10 years ago
after
;
if (moveVector !== false) {
_silent = true;
setTimeout(_unsilent, 30);
_cloneHide(isOwner);
if (moveVector === 1 || moveVector === -1) {
after = (moveVector === 1);
}
else if (floating) {
var elTop = dragEl.offsetTop,
tgTop = target.offsetTop;
9 years ago
if (elTop === tgTop) {
after = (target.previousElementSibling === dragEl) && !isWide || halfway && isWide;
}
else if (target.previousElementSibling === dragEl || dragEl.previousElementSibling === target) {
after = (evt.clientY - targetRect.top) / height > 0.5;
} else {
after = tgTop > elTop;
}
} else {
after = (nextSibling !== dragEl) && !isLong || halfway && isLong;
}
if (!dragEl.contains(el)) {
if (after && !nextSibling) {
el.appendChild(dragEl);
} else {
target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
}
}
parentEl = dragEl.parentNode; // actualization
this._animate(dragRect, dragEl);
this._animate(targetRect, target);
}
11 years ago
}
}
},
_animate: function (prevRect, target) {
var ms = this.options.animation;
if (ms) {
var currentRect = target.getBoundingClientRect();
_css(target, 'transition', 'none');
_css(target, 'transform', 'translate3d('
+ (prevRect.left - currentRect.left) + 'px,'
+ (prevRect.top - currentRect.top) + 'px,0)'
);
target.offsetWidth; // repaint
_css(target, 'transition', 'all ' + ms + 'ms');
_css(target, 'transform', 'translate3d(0,0,0)');
10 years ago
clearTimeout(target.animated);
target.animated = setTimeout(function () {
_css(target, 'transition', '');
_css(target, 'transform', '');
target.animated = false;
}, ms);
}
},
_offUpEvents: function () {
var ownerDocument = this.el.ownerDocument;
_off(document, 'touchmove', this._onTouchMove);
_off(ownerDocument, 'mouseup', this._onDrop);
_off(ownerDocument, 'touchend', this._onDrop);
_off(ownerDocument, 'touchcancel', this._onDrop);
},
11 years ago
10 years ago
_onDrop: function (/**Event*/evt) {
var el = this.el,
options = this.options;
11 years ago
clearInterval(this._loopId);
clearInterval(autoScroll.pid);
clearTimeout(this._dragStartTimer);
11 years ago
// Unbind events
_off(document, 'mousemove', this._onTouchMove);
if (this.nativeDraggable) {
_off(document, 'drop', this);
_off(el, 'dragstart', this._onDragStart);
}
11 years ago
this._offUpEvents();
11 years ago
10 years ago
if (evt) {
9 years ago
if (moved) {
evt.preventDefault();
!options.dropBubble && evt.stopPropagation();
}
11 years ago
ghostEl && ghostEl.parentNode.removeChild(ghostEl);
11 years ago
10 years ago
if (dragEl) {
if (this.nativeDraggable) {
_off(dragEl, 'dragend', this);
}
_disableDraggable(dragEl);
// Remove class's
11 years ago
_toggleClass(dragEl, this.options.ghostClass, false);
_toggleClass(dragEl, this.options.chosenClass, false);
11 years ago
if (rootEl !== parentEl) {
newIndex = _index(dragEl, options.draggable);
if (newIndex >= 0) {
10 years ago
// Add event
_dispatchEvent(null, parentEl, 'add', dragEl, rootEl, oldIndex, newIndex);
// Remove event
_dispatchEvent(this, rootEl, 'remove', dragEl, rootEl, oldIndex, newIndex);
// drag from one list and drop into another
_dispatchEvent(null, parentEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
_dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
}
11 years ago
}
else {
// Remove clone
cloneEl && cloneEl.parentNode.removeChild(cloneEl);
10 years ago
if (dragEl.nextSibling !== nextEl) {
// Get the index of the dragged element within its parent
newIndex = _index(dragEl, options.draggable);
if (newIndex >= 0) {
// drag & drop within the same list
_dispatchEvent(this, rootEl, 'update', dragEl, rootEl, oldIndex, newIndex);
_dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
}
}
11 years ago
}
if (Sortable.active) {
9 years ago
if (newIndex === null || newIndex === -1) {
newIndex = oldIndex;
}
_dispatchEvent(this, rootEl, 'end', dragEl, rootEl, oldIndex, newIndex);
// Save sorting
this.save();
}
11 years ago
}
}
this._nulling();
},
11 years ago
_nulling: function () {
rootEl =
dragEl =
parentEl =
ghostEl =
nextEl =
cloneEl =
scrollEl =
scrollParentEl =
11 years ago
tapEvt =
touchEvt =
moved =
newIndex =
11 years ago
lastEl =
lastCSS =
11 years ago
putSortable =
activeGroup =
Sortable.active = null;
},
11 years ago
handleEvent: function (/**Event*/evt) {
var type = evt.type;
if (type === 'dragover' || type === 'dragenter') {
if (dragEl) {
this._onDragOver(evt);
_globalDragOver(evt);
}
}
else if (type === 'drop' || type === 'dragend') {
this._onDrop(evt);
}
},
/**
* Serializes the item into an array of string.
* @returns {String[]}
*/
toArray: function () {
var order = [],
el,
children = this.el.children,
i = 0,
n = children.length,
options = this.options;
for (; i < n; i++) {
el = children[i];
if (_closest(el, options.draggable, this.el)) {
order.push(el.getAttribute(options.dataIdAttr) || _generateId(el));
}
}
return order;
},
/**
* Sorts the elements according to the array.
* @param {String[]} order order of the items
*/
sort: function (order) {
var items = {}, rootEl = this.el;
this.toArray().forEach(function (id, i) {
var el = rootEl.children[i];
if (_closest(el, this.options.draggable, rootEl)) {
items[id] = el;
}
}, this);
order.forEach(function (id) {
if (items[id]) {
rootEl.removeChild(items[id]);
rootEl.appendChild(items[id]);
}
});
},
/**
* Save the current sorting
*/
save: function () {
var store = this.options.store;
store && store.set(this);
},
/**
* 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.
* @param {HTMLElement} el
* @param {String} [selector] default: `options.draggable`
* @returns {HTMLElement|null}
*/
closest: function (el, selector) {
return _closest(el, selector || this.options.draggable, this.el);
},
/**
* Set/get option
* @param {string} name
* @param {*} [value]
* @returns {*}
*/
option: function (name, value) {
var options = this.options;
if (value === void 0) {
return options[name];
} else {
options[name] = value;
if (name === 'group') {
_prepareGroup(options);
}
}
},
/**
* Destroy
*/
destroy: function () {
var el = this.el;
11 years ago
el[expando] = null;
11 years ago
_off(el, 'mousedown', this._onTapStart);
_off(el, 'touchstart', this._onTapStart);
if (this.nativeDraggable) {
_off(el, 'dragover', this);
_off(el, 'dragenter', this);
}
11 years ago
// Remove draggable attributes
10 years ago
Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function (el) {
el.removeAttribute('draggable');
});
11 years ago
touchDragOverListeners.splice(touchDragOverListeners.indexOf(this._onDragOver), 1);
this._onDrop();
this.el = el = null;
11 years ago
}
};
function _cloneHide(state) {
if (cloneEl && (cloneEl.state !== state)) {
_css(cloneEl, 'display', state ? 'none' : '');
!state && cloneEl.state && rootEl.insertBefore(cloneEl, dragEl);
cloneEl.state = state;
}
}
function _closest(/**HTMLElement*/el, /**String*/selector, /**HTMLElement*/ctx) {
if (el) {
11 years ago
ctx = ctx || document;
do {
if ((selector === '>*' && el.parentNode === ctx) || _matches(el, selector)) {
10 years ago
return el;
11 years ago
}
}
10 years ago
while (el !== ctx && (el = el.parentNode));
11 years ago
}
10 years ago
return null;
11 years ago
}
function _globalDragOver(/**Event*/evt) {
if (evt.dataTransfer) {
evt.dataTransfer.dropEffect = 'move';
}
11 years ago
evt.preventDefault();
}
10 years ago
function _on(el, event, fn) {
11 years ago
el.addEventListener(event, fn, false);
}
10 years ago
function _off(el, event, fn) {
11 years ago
el.removeEventListener(event, fn, false);
}
10 years ago
function _toggleClass(el, name, state) {
if (el) {
if (el.classList) {
11 years ago
el.classList[state ? 'add' : 'remove'](name);
}
else {
var className = (' ' + el.className + ' ').replace(RSPACE, ' ').replace(' ' + name + ' ', ' ');
el.className = (className + (state ? ' ' + name : '')).replace(RSPACE, ' ');
11 years ago
}
}
}
10 years ago
function _css(el, prop, val) {
var style = el && el.style;
10 years ago
if (style) {
if (val === void 0) {
if (document.defaultView && document.defaultView.getComputedStyle) {
11 years ago
val = document.defaultView.getComputedStyle(el, '');
}
10 years ago
else if (el.currentStyle) {
val = el.currentStyle;
11 years ago
}
return prop === void 0 ? val : val[prop];
}
else {
if (!(prop in style)) {
prop = '-webkit-' + prop;
}
style[prop] = val + (typeof val === 'string' ? '' : 'px');
11 years ago
}
}
}
10 years ago
function _find(ctx, tagName, iterator) {
if (ctx) {
11 years ago
var list = ctx.getElementsByTagName(tagName), i = 0, n = list.length;
10 years ago
if (iterator) {
for (; i < n; i++) {
11 years ago
iterator(list[i], i);
}
}
10 years ago
return list;
11 years ago
}
10 years ago
return [];
11 years ago
}
function _dispatchEvent(sortable, rootEl, name, targetEl, fromEl, startIndex, newIndex) {
sortable = (sortable || rootEl[expando]);
var evt = document.createEvent('Event'),
options = sortable.options,
onName = 'on' + name.charAt(0).toUpperCase() + name.substr(1);
evt.initEvent(name, true, true);
evt.to = rootEl;
evt.from = fromEl || rootEl;
evt.item = targetEl || rootEl;
evt.clone = cloneEl;
evt.oldIndex = startIndex;
evt.newIndex = newIndex;
rootEl.dispatchEvent(evt);
if (options[onName]) {
options[onName].call(sortable, evt);
}
}
function _onMove(fromEl, toEl, dragEl, dragRect, targetEl, targetRect) {
var evt,
sortable = fromEl[expando],
onMoveFn = sortable.options.onMove,
retVal;
evt = document.createEvent('Event');
evt.initEvent('move', true, true);
evt.to = toEl;
evt.from = fromEl;
evt.dragged = dragEl;
evt.draggedRect = dragRect;
evt.related = targetEl || toEl;
evt.relatedRect = targetRect || toEl.getBoundingClientRect();
fromEl.dispatchEvent(evt);
if (onMoveFn) {
retVal = onMoveFn.call(sortable, evt);
}
return retVal;
}
10 years ago
function _disableDraggable(el) {
el.draggable = false;
11 years ago
}
10 years ago
function _unsilent() {
_silent = false;
}
/** @returns {HTMLElement|false} */
function _ghostIsLast(el, evt) {
var lastEl = el.lastElementChild,
rect = lastEl.getBoundingClientRect();
// 5 — min delta
return (
(abs(evt.clientY - (rect.top + rect.height)) > 5) ||
(abs(evt.clientX - (rect.right + rect.width)) > 5)
) && lastEl;
}
/**
* Generate id
* @param {HTMLElement} el
* @returns {String}
* @private
*/
function _generateId(el) {
var str = el.tagName + el.className + el.src + el.href + el.textContent,
i = str.length,
10 years ago
sum = 0;
while (i--) {
sum += str.charCodeAt(i);
}
return sum.toString(36);
}
/**
* Returns the index of an element within its parent for a selected set of
* elements
* @param {HTMLElement} el
* @param {selector} selector
* @return {number}
*/
function _index(el, selector) {
var index = 0;
if (!el || !el.parentNode) {
return -1;
}
while (el && (el = el.previousElementSibling)) {
if ((el.nodeName.toUpperCase() !== 'TEMPLATE') && (selector === '>*' || _matches(el, selector))) {
index++;
}
}
return index;
}
11 years ago
function _matches(/**HTMLElement*/el, /**String*/selector) {
if (el) {
selector = selector.split('.');
var tag = selector.shift().toUpperCase(),
re = new RegExp('\\s(' + selector.join('|') + ')(?=\\s)', 'g');
return (
(tag === '' || el.nodeName.toUpperCase() == tag) &&
(!selector.length || ((' ' + el.className + ' ').match(re) || []).length == selector.length)
);
}
return false;
}
function _throttle(callback, ms) {
var args, _this;
return function () {
if (args === void 0) {
args = arguments;
_this = this;
setTimeout(function () {
if (args.length === 1) {
callback.call(_this, args[0]);
} else {
callback.apply(_this, args);
}
args = void 0;
}, ms);
}
};
}
function _extend(dst, src) {
if (dst && src) {
for (var key in src) {
if (src.hasOwnProperty(key)) {
dst[key] = src[key];
}
}
}
return dst;
}
function _clone(el) {
return $
? $(el).clone(true)[0]
: (Polymer && Polymer.dom
? Polymer.dom(el).cloneNode(true)
: el.cloneNode(true)
);
}
11 years ago
// Export utils
Sortable.utils = {
on: _on,
off: _off,
css: _css,
find: _find,
is: function (el, selector) {
return !!_closest(el, selector, el);
},
extend: _extend,
throttle: _throttle,
11 years ago
closest: _closest,
toggleClass: _toggleClass,
clone: _clone,
index: _index
11 years ago
};
/**
* Create sortable instance
* @param {HTMLElement} el
* @param {Object} [options]
*/
Sortable.create = function (el, options) {
10 years ago
return new Sortable(el, options);
};
11 years ago
// Export
Sortable.version = '1.4.2';
return Sortable;
11 years ago
});