diff --git a/Gruntfile.js b/Gruntfile.js
index 26e1656..5d67184 100755
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -35,9 +35,7 @@ module.exports = function (grunt) {
}
},
jquery: {
- files: {
- 'jquery.fn.sortable.min.js': 'jquery.fn.sortable.js'
- }
+ files: {}
}
},
@@ -54,22 +52,37 @@ module.exports = function (grunt) {
});
- grunt.registerTask('jquery', function (arg) {
+ grunt.registerTask('jquery', function (exportName, uglify) {
+ if (exportName == 'min') {
+ exportName = null;
+ uglify = 'min';
+ }
+
+ if (!exportName) {
+ exportName = 'sortable';
+ }
+
var fs = require('fs'),
- filename = 'jquery.fn.sortable.js';
+ filename = 'jquery.fn.' + exportName + '.js';
grunt.log.oklns(filename);
fs.writeFileSync(
filename,
(fs.readFileSync('jquery.binding.js') + '')
+ .replace('$.fn.sortable', '$.fn.' + exportName)
.replace('/* CODE */',
(fs.readFileSync('Sortable.js') + '')
.replace(/^[\s\S]*?function[\s\S]*?(var[\s\S]+)\/\/\s+Export[\s\S]+/, '$1')
)
);
- if (arg === 'min') {
+ if (uglify) {
+ var opts = {};
+
+ opts['jquery.fn.' + exportName + '.min.js'] = filename;
+ grunt.config.set('uglify.jquery.files', opts);
+
grunt.task.run('uglify:jquery');
}
});
diff --git a/README.md b/README.md
index 44a1f94..a935ba4 100644
--- a/README.md
+++ b/README.md
@@ -62,6 +62,7 @@ var sortable = new Sortable(el, {
filter: ".ignore-elements", // Selectors that do not lead to dragging (String or Function)
draggable: ".item", // Specifies which items inside the element should be sortable
ghostClass: "sortable-ghost", // Class name for the drop placeholder
+ dataIdAttr: 'data-id',
scroll: true, // or HTMLElement
scrollSensitivity: 30, // px, how near the mouse must be to an edge to start scrolling.
@@ -108,6 +109,16 @@ var sortable = new Sortable(el, {
// Attempt to drag a filtered element
onFilter: function (/**Event*/evt) {
var itemEl = evt.item; // HTMLElement receiving the `mousedown|tapstart` event.
+ },
+
+ // Event when you move an item in the list or between lists
+ onMove: function (/**Event*/evt) {
+ // Example: http://jsbin.com/tuyafe/1/edit?js,output
+ evt.dragged; // dragged HTMLElement
+ evt.draggedRect; // TextRectangle {left, top, right и bottom}
+ evt.related; // HTMLElement on which have guided
+ evt.relatedRect; // TextRectangle
+ // retrun false; — for cancel
}
});
```
@@ -386,6 +397,32 @@ React.render(
---
+
+### Support KnockoutJS
+Include [knockout-sortable.js](knockout-sortable.js)
+
+```html
+
+
+
+
+
+
+
+```
+
+Using this bindingHandler sorts the observableArray when the user sorts the HTMLElements.
+
+The sortable/draggable bindingHandlers supports the same syntax as Knockouts built in [template](http://knockoutjs.com/documentation/template-binding.html) binding except for the `data` option, meaning that you could supply the name of a template or specify a separate templateEngine. The difference between the sortable and draggable handlers is that the draggable has the sortable `group` option set to `{pull:'clone',put: false}` and the `sort` option set to false by default (overridable).
+
+Other attributes are:
+* options: an object that contains settings for the underlaying sortable, ie `group`,`handle`, events etc.
+* collection: if your `foreach` array is a computed then you would supply the underlaying observableArray that you would like to sort here.
+
+
+---
+
+
### Method
@@ -399,7 +436,7 @@ For each element in the set, get the first element that matches the selector by
##### toArray():`String[]`
-Serializes the sortable's item `data-id`'s into an array of string.
+Serializes the sortable's item `data-id`'s (`dataIdAttr` option) into an array of string.
##### sort(order:`String[]`)
@@ -536,11 +573,11 @@ Link to the active instance.
```html
-
+
-
+
@@ -576,6 +613,7 @@ Now you can use `jquery.fn.sortable.js`:
$("#list").sortable("{method-name}", "foo", "bar"); // call an instance method with parameters
```
+And `grunt jquery:mySortableFunc` → `jquery.fn.mySortableFunc.js`
---
diff --git a/Sortable.js b/Sortable.js
index 2be6bae..6de4e05 100644
--- a/Sortable.js
+++ b/Sortable.js
@@ -45,6 +45,9 @@
tapEvt,
touchEvt,
+ /** @const */
+ RSPACE = /\s+/g,
+
expando = 'Sortable' + (new Date).getTime(),
win = window,
@@ -53,28 +56,8 @@
supportDraggable = !!('draggable' in document.createElement('div')),
-
_silent = false,
- _dispatchEvent = function (rootEl, name, targetEl, fromEl, startIndex, newIndex) {
- var evt = document.createEvent('Event');
-
- evt.initEvent(name, true, true);
-
- evt.item = targetEl || rootEl;
- evt.from = fromEl || rootEl;
- evt.clone = cloneEl;
-
- evt.oldIndex = startIndex;
- evt.newIndex = newIndex;
-
- rootEl.dispatchEvent(evt);
- },
-
- _customEvents = 'onAdd onUpdate onRemove onStart onEnd onFilter onSort'.split(' '),
-
- noop = function () {},
-
abs = Math.abs,
slice = [].slice,
@@ -144,7 +127,7 @@
if (el) {
autoScroll.pid = setInterval(function () {
if (el === win) {
- win.scrollTo(win.scrollX + vx * speed, win.scrollY + vy * speed);
+ win.scrollTo(win.pageXOffset + vx * speed, win.pageYOffset + vy * speed);
} else {
vy && (el.scrollTop += vy * speed);
vx && (el.scrollLeft += vx * speed);
@@ -165,7 +148,11 @@
*/
function Sortable(el, options) {
this.el = el; // root element
- this.options = options = (options || {});
+ this.options = options = _extend({}, options);
+
+
+ // Export instance
+ el[expando] = this;
// Default options
@@ -187,7 +174,9 @@
dataTransfer.setData('Text', dragEl.textContent);
},
dropBubble: false,
- dragoverBubble: false
+ dragoverBubble: false,
+ dataIdAttr: 'data-id',
+ delay: 0
};
@@ -211,16 +200,7 @@
});
- // Define events
- _customEvents.forEach(function (name) {
- options[name] = _bind(this, options[name] || noop);
- _on(el, name.substr(2).toLowerCase(), options[name]);
- }, this);
-
-
- // Export options
options.groups = ' ' + group.name + (group.put.join ? ' ' + group.put.join(' ') : '') + ' ';
- el[expando] = options;
// Bind all private methods
@@ -248,29 +228,17 @@
Sortable.prototype = /** @lends Sortable.prototype */ {
constructor: Sortable,
-
- _dragStarted: function () {
- if (rootEl && dragEl) {
- // Apply effect
- _toggleClass(dragEl, this.options.ghostClass, true);
-
- Sortable.active = this;
-
- // Drag start event
- _dispatchEvent(rootEl, 'start', dragEl, rootEl, oldIndex);
- }
- },
-
-
- _onTapStart: function (/**Event|TouchEvent*/evt) {
- var type = evt.type,
+ _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,
- options = this.options,
- el = this.el,
filter = options.filter;
+
if (type === 'mousedown' && evt.button !== 0 || options.disabled) {
return; // only left button or enabled
}
@@ -287,7 +255,7 @@
// Check filter
if (typeof filter === 'function') {
if (filter.call(this, evt, target, this)) {
- _dispatchEvent(originalTarget, 'filter', target, el, oldIndex);
+ _dispatchEvent(_this, originalTarget, 'filter', target, el, oldIndex);
evt.preventDefault();
return; // cancel dnd
}
@@ -297,7 +265,7 @@
criteria = _closest(originalTarget, criteria.trim(), el);
if (criteria) {
- _dispatchEvent(criteria, 'filter', target, el, oldIndex);
+ _dispatchEvent(_this, criteria, 'filter', target, el, oldIndex);
return true;
}
});
@@ -315,52 +283,105 @@
// Prepare `dragstart`
+ this._prepareDragStart(evt, touch, target);
+ },
+
+ _prepareDragStart: function (/** Event */evt, /** Touch */touch, /** HTMLElement */target) {
+ var _this = this,
+ el = _this.el,
+ options = _this.options,
+ ownerDocument = el.ownerDocument,
+ dragStartFn;
+
if (target && !dragEl && (target.parentNode === el)) {
tapEvt = evt;
- rootEl = this.el;
+ rootEl = el;
dragEl = target;
nextEl = dragEl.nextSibling;
- activeGroup = this.options.group;
+ activeGroup = options.group;
- dragEl.draggable = true;
+ dragStartFn = function () {
+ // Delayed drag has been triggered
+ // we can re-enable the events: touchmove/mousemove
+ _this._disableDelayedDrag();
- // Disable "draggable"
- options.ignore.split(',').forEach(function (criteria) {
- _find(target, criteria.trim(), _disableDraggable);
- });
+ // Make the element draggable
+ dragEl.draggable = true;
- if (touch) {
- // Touch device support
- tapEvt = {
- target: target,
- clientX: touch.clientX,
- clientY: touch.clientY
- };
+ // Disable "draggable"
+ options.ignore.split(',').forEach(function (criteria) {
+ _find(dragEl, criteria.trim(), _disableDraggable);
+ });
- this._onDragStart(tapEvt, 'touch');
- evt.preventDefault();
+ // Bind the events: dragstart/dragend
+ _this._triggerDragStart(touch);
+ };
+
+ _on(ownerDocument, 'mouseup', _this._onDrop);
+ _on(ownerDocument, 'touchend', _this._onDrop);
+ _on(ownerDocument, 'touchcancel', _this._onDrop);
+
+ if (options.delay) {
+ // If the user moves the pointer before the delay has been reached:
+ // disable the delayed drag
+ _on(ownerDocument, 'mousemove', _this._disableDelayedDrag);
+ _on(ownerDocument, 'touchmove', _this._disableDelayedDrag);
+
+ _this._dragStartTimer = setTimeout(dragStartFn, options.delay);
+ } else {
+ dragStartFn();
}
+ }
+ },
- _on(document, 'mouseup', this._onDrop);
- _on(document, 'touchend', this._onDrop);
- _on(document, 'touchcancel', this._onDrop);
+ _disableDelayedDrag: function () {
+ var ownerDocument = this.el.ownerDocument;
+ clearTimeout(this._dragStartTimer);
+
+ _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 (!supportDraggable) {
+ this._onDragStart(tapEvt, true);
+ }
+ else {
_on(dragEl, 'dragend', this);
_on(rootEl, 'dragstart', this._onDragStart);
+ }
- if (!supportDraggable) {
- this._onDragStart(tapEvt, true);
+ try {
+ if (document.selection) {
+ document.selection.empty();
+ } else {
+ window.getSelection().removeAllRanges();
}
+ } catch (err) {
+ }
+ },
- 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);
}
},
@@ -375,7 +396,7 @@
if (parent) {
do {
- if (parent[expando] && parent[expando].groups.indexOf(groupName) > -1) {
+ if (parent[expando] && parent[expando].options.groups.indexOf(groupName) > -1) {
while (i--) {
touchDragOverListeners[i]({
clientX: touchEvt.clientX,
@@ -488,10 +509,6 @@
isOwner = (activeGroup === group),
canSort = options.sort;
- if (!dragEl) {
- return;
- }
-
if (evt.preventDefault !== void 0) {
evt.preventDefault();
!options.dragoverBubble && evt.stopPropagation();
@@ -499,13 +516,13 @@
if (activeGroup && !options.disabled &&
(isOwner
- ? canSort || (revert = !rootEl.contains(dragEl))
+ ? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list
: activeGroup.pull && groupPut && (
(activeGroup.name === group.name) || // by Name
(groupPut.indexOf && ~groupPut.indexOf(activeGroup.name)) // by Array
)
) &&
- (evt.rootEl === void 0 || evt.rootEl === this.el)
+ (evt.rootEl === void 0 || evt.rootEl === this.el) // touch fallback
) {
// Smart auto-scrolling
_autoScroll(evt, options, this.el);
@@ -544,9 +561,11 @@
_cloneHide(isOwner);
- el.appendChild(dragEl);
- this._animate(dragRect, dragEl);
- target && this._animate(targetRect, target);
+ if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect) !== false) {
+ el.appendChild(dragEl);
+ this._animate(dragRect, dragEl);
+ target && this._animate(targetRect, target);
+ }
}
else if (target && !target.animated && target !== dragEl && (target.parentNode[expando] !== void 0)) {
if (lastEl !== target) {
@@ -563,28 +582,34 @@
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),
after
;
- _silent = true;
- setTimeout(_unsilent, 30);
+ if (moveVector !== false) {
+ _silent = true;
+ setTimeout(_unsilent, 30);
- _cloneHide(isOwner);
+ _cloneHide(isOwner);
- if (floating) {
- after = (target.previousElementSibling === dragEl) && !isWide || halfway && isWide;
- } else {
- after = (nextSibling !== dragEl) && !isLong || halfway && isLong;
- }
+ if (moveVector === 1 || moveVector === -1) {
+ after = (moveVector === 1);
+ }
+ else if (floating) {
+ after = (target.previousElementSibling === dragEl) && !isWide || halfway && isWide;
+ } else {
+ after = (nextSibling !== dragEl) && !isLong || halfway && isLong;
+ }
- if (after && !nextSibling) {
- el.appendChild(dragEl);
- } else {
- target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
- }
+ if (after && !nextSibling) {
+ el.appendChild(dragEl);
+ } else {
+ target.parentNode.insertBefore(dragEl, after ? nextSibling : target);
+ }
- this._animate(dragRect, dragEl);
- this._animate(targetRect, target);
+ this._animate(dragRect, dragEl);
+ this._animate(targetRect, target);
+ }
}
}
},
@@ -616,10 +641,12 @@
},
_offUpEvents: function () {
- _off(document, 'mouseup', this._onDrop);
+ var ownerDocument = this.el.ownerDocument;
+
_off(document, 'touchmove', this._onTouchMove);
- _off(document, 'touchend', this._onDrop);
- _off(document, 'touchcancel', this._onDrop);
+ _off(ownerDocument, 'mouseup', this._onDrop);
+ _off(ownerDocument, 'touchend', this._onDrop);
+ _off(ownerDocument, 'touchcancel', this._onDrop);
},
_onDrop: function (/**Event*/evt) {
@@ -629,6 +656,8 @@
clearInterval(this._loopId);
clearInterval(autoScroll.pid);
+ clearTimeout(this.dragStartTimer);
+
// Unbind events
_off(document, 'drop', this);
_off(document, 'mousemove', this._onTouchMove);
@@ -652,14 +681,14 @@
newIndex = _index(dragEl);
// drag from one list and drop into another
- _dispatchEvent(dragEl.parentNode, 'sort', dragEl, rootEl, oldIndex, newIndex);
- _dispatchEvent(rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
+ _dispatchEvent(null, dragEl.parentNode, 'sort', dragEl, rootEl, oldIndex, newIndex);
+ _dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
// Add event
- _dispatchEvent(dragEl, 'add', dragEl, rootEl, oldIndex, newIndex);
+ _dispatchEvent(null, dragEl.parentNode, 'add', dragEl, rootEl, oldIndex, newIndex);
// Remove event
- _dispatchEvent(rootEl, 'remove', dragEl, rootEl, oldIndex, newIndex);
+ _dispatchEvent(this, rootEl, 'remove', dragEl, rootEl, oldIndex, newIndex);
}
else {
// Remove clone
@@ -670,13 +699,18 @@
newIndex = _index(dragEl);
// drag & drop within the same list
- _dispatchEvent(rootEl, 'update', dragEl, rootEl, oldIndex, newIndex);
- _dispatchEvent(rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
+ _dispatchEvent(this, rootEl, 'update', dragEl, rootEl, oldIndex, newIndex);
+ _dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
}
}
- // Drag end event
- Sortable.active && _dispatchEvent(rootEl, 'end', dragEl, rootEl, oldIndex, newIndex);
+ if (Sortable.active) {
+ // Drag end event
+ _dispatchEvent(this, rootEl, 'end', dragEl, rootEl, oldIndex, newIndex);
+
+ // Save sorting
+ this.save();
+ }
}
// Nulling
@@ -697,9 +731,6 @@
activeGroup =
Sortable.active = null;
-
- // Save sorting
- this.save();
}
},
@@ -708,8 +739,10 @@
var type = evt.type;
if (type === 'dragover' || type === 'dragenter') {
- this._onDragOver(evt);
- _globalDragOver(evt);
+ if (dragEl) {
+ this._onDragOver(evt);
+ _globalDragOver(evt);
+ }
}
else if (type === 'drop' || type === 'dragend') {
this._onDrop(evt);
@@ -726,12 +759,13 @@
el,
children = this.el.children,
i = 0,
- n = children.length;
+ n = children.length,
+ options = this.options;
for (; i < n; i++) {
el = children[i];
- if (_closest(el, this.options.draggable, this.el)) {
- order.push(el.getAttribute('data-id') || _generateId(el));
+ if (_closest(el, options.draggable, this.el)) {
+ order.push(el.getAttribute(options.dataIdAttr) || _generateId(el));
}
}
@@ -804,11 +838,9 @@
* Destroy
*/
destroy: function () {
- var el = this.el, options = this.options;
+ var el = this.el;
- _customEvents.forEach(function (name) {
- _off(el, name.substr(2).toLowerCase(), options[name]);
- });
+ el[expando] = null;
_off(el, 'mousedown', this._onTapStart);
_off(el, 'touchstart', this._onTapStart);
@@ -816,7 +848,7 @@
_off(el, 'dragover', this);
_off(el, 'dragenter', this);
- //remove draggable attributes
+ // Remove draggable attributes
Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function (el) {
el.removeAttribute('draggable');
});
@@ -825,7 +857,7 @@
this._onDrop();
- this.el = null;
+ this.el = el = null;
}
};
@@ -894,8 +926,8 @@
el.classList[state ? 'add' : 'remove'](name);
}
else {
- var className = (' ' + el.className + ' ').replace(/\s+/g, ' ').replace(' ' + name + ' ', '');
- el.className = className + (state ? ' ' + name : '');
+ var className = (' ' + el.className + ' ').replace(RSPACE, ' ').replace(' ' + name + ' ', ' ');
+ el.className = (className + (state ? ' ' + name : '')).replace(RSPACE, ' ');
}
}
}
@@ -943,6 +975,54 @@
}
+
+ function _dispatchEvent(sortable, rootEl, name, targetEl, fromEl, startIndex, newIndex) {
+ var evt = document.createEvent('Event'),
+ options = (sortable || rootEl[expando]).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;
+
+ if (options[onName]) {
+ options[onName].call(sortable, evt);
+ }
+
+ rootEl.dispatchEvent(evt);
+ }
+
+
+ function _onMove(fromEl, toEl, dragEl, dragRect, targetEl, targetRect) {
+ var evt,
+ sortable = fromEl[expando],
+ onMoveFn = sortable.options.onMove,
+ retVal;
+
+ if (onMoveFn) {
+ 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();
+
+ retVal = onMoveFn.call(sortable, evt);
+ }
+
+ return retVal;
+ }
+
+
function _disableDraggable(el) {
el.draggable = false;
}
@@ -1015,6 +1095,18 @@
};
}
+ function _extend(dst, src) {
+ if (dst && src) {
+ for (var key in src) {
+ if (src.hasOwnProperty(key)) {
+ dst[key] = src[key];
+ }
+ }
+ }
+
+ return dst;
+ }
+
// Export utils
Sortable.utils = {
@@ -1026,15 +1118,15 @@
is: function (el, selector) {
return !!_closest(el, selector, el);
},
+ extend: _extend,
throttle: _throttle,
closest: _closest,
toggleClass: _toggleClass,
- dispatchEvent: _dispatchEvent,
index: _index
};
- Sortable.version = '1.1.1';
+ Sortable.version = '1.2.0';
/**
diff --git a/Sortable.min.js b/Sortable.min.js
index 909ab3e..6b3c942 100644
--- a/Sortable.min.js
+++ b/Sortable.min.js
@@ -1,2 +1,2 @@
-/*! Sortable 1.1.1 - MIT | git://github.com/rubaxa/Sortable.git */
-!function(a){"use strict";"function"==typeof define&&define.amd?define(a):"undefined"!=typeof module&&"undefined"!=typeof module.exports?module.exports=a():"undefined"!=typeof Package?Sortable=a():window.Sortable=a()}(function(){"use strict";function a(a,b){this.el=a,this.options=b=b||{};var d={group:Math.random(),sort:!0,disabled:!1,store:null,handle:null,scroll:!0,scrollSensitivity:30,scrollSpeed:10,draggable:/[uo]l/i.test(a.nodeName)?"li":">*",ghostClass:"sortable-ghost",ignore:"a, img",filter:null,animation:0,setData:function(a,b){a.setData("Text",b.textContent)},dropBubble:!1,dragoverBubble:!1};for(var e in d)!(e in b)&&(b[e]=d[e]);var g=b.group;g&&"object"==typeof g||(g=b.group={name:g}),["pull","put"].forEach(function(a){a in g||(g[a]=!0)}),M.forEach(function(d){b[d]=c(this,b[d]||N),f(a,d.substr(2).toLowerCase(),b[d])},this),b.groups=" "+g.name+(g.put.join?" "+g.put.join(" "):"")+" ",a[F]=b;for(var h in this)"_"===h.charAt(0)&&(this[h]=c(this,this[h]));f(a,"mousedown",this._onTapStart),f(a,"touchstart",this._onTapStart),f(a,"dragover",this),f(a,"dragenter",this),Q.push(this._onDragOver),b.store&&this.sort(b.store.get(this))}function b(a){s&&s.state!==a&&(i(s,"display",a?"none":""),!a&&s.state&&t.insertBefore(s,q),s.state=a)}function c(a,b){var c=P.call(arguments,2);return b.bind?b.bind.apply(b,[a].concat(c)):function(){return b.apply(a,c.concat(P.call(arguments)))}}function d(a,b,c){if(a){c=c||H,b=b.split(".");var d=b.shift().toUpperCase(),e=new RegExp("\\s("+b.join("|")+")\\s","g");do if(">*"===d&&a.parentNode===c||(""===d||a.nodeName.toUpperCase()==d)&&(!b.length||((" "+a.className+" ").match(e)||[]).length==b.length))return a;while(a!==c&&(a=a.parentNode))}return null}function e(a){a.dataTransfer.dropEffect="move",a.preventDefault()}function f(a,b,c){a.addEventListener(b,c,!1)}function g(a,b,c){a.removeEventListener(b,c,!1)}function h(a,b,c){if(a)if(a.classList)a.classList[c?"add":"remove"](b);else{var d=(" "+a.className+" ").replace(/\s+/g," ").replace(" "+b+" ","");a.className=d+(c?" "+b:"")}}function i(a,b,c){var d=a&&a.style;if(d){if(void 0===c)return H.defaultView&&H.defaultView.getComputedStyle?c=H.defaultView.getComputedStyle(a,""):a.currentStyle&&(c=a.currentStyle),void 0===b?c:c[b];b in d||(b="-webkit-"+b),d[b]=c+("string"==typeof c?"":"px")}}function j(a,b,c){if(a){var d=a.getElementsByTagName(b),e=0,f=d.length;if(c)for(;f>e;e++)c(d[e],e);return d}return[]}function k(a){a.draggable=!1}function l(){K=!1}function m(a,b){var c=a.lastElementChild,d=c.getBoundingClientRect();return b.clientY-(d.top+d.height)>5&&c}function n(a){for(var b=a.tagName+a.className+a.src+a.href+a.textContent,c=b.length,d=0;c--;)d+=b.charCodeAt(c);return d.toString(36)}function o(a){for(var b=0;a&&(a=a.previousElementSibling);)"TEMPLATE"!==a.nodeName.toUpperCase()&&b++;return b}function p(a,b){var c,d;return function(){void 0===c&&(c=arguments,d=this,setTimeout(function(){1===c.length?a.call(d,c[0]):a.apply(d,c),c=void 0},b))}}var q,r,s,t,u,v,w,x,y,z,A,B,C,D,E={},F="Sortable"+(new Date).getTime(),G=window,H=G.document,I=G.parseInt,J=!!("draggable"in H.createElement("div")),K=!1,L=function(a,b,c,d,e,f){var g=H.createEvent("Event");g.initEvent(b,!0,!0),g.item=c||a,g.from=d||a,g.clone=s,g.oldIndex=e,g.newIndex=f,a.dispatchEvent(g)},M="onAdd onUpdate onRemove onStart onEnd onFilter onSort".split(" "),N=function(){},O=Math.abs,P=[].slice,Q=[],R=p(function(a,b,c){if(c&&b.scroll){var d,e,f,g,h=b.scrollSensitivity,i=b.scrollSpeed,j=a.clientX,k=a.clientY,l=window.innerWidth,m=window.innerHeight;if(w!==c&&(v=b.scroll,w=c,v===!0)){v=c;do if(v.offsetWidth
=l-j)-(h>=j),g=(h>=m-k)-(h>=k),(f||g)&&(d=G)),(E.vx!==f||E.vy!==g||E.el!==d)&&(E.el=d,E.vx=f,E.vy=g,clearInterval(E.pid),d&&(E.pid=setInterval(function(){d===G?G.scrollTo(G.scrollX+f*i,G.scrollY+g*i):(g&&(d.scrollTop+=g*i),f&&(d.scrollLeft+=f*i))},24)))}},30);return a.prototype={constructor:a,_dragStarted:function(){t&&q&&(h(q,this.options.ghostClass,!0),a.active=this,L(t,"start",q,t,z))},_onTapStart:function(a){var b=a.type,c=a.touches&&a.touches[0],e=(c||a).target,g=e,h=this.options,i=this.el,l=h.filter;if(!("mousedown"===b&&0!==a.button||h.disabled)&&(e=d(e,h.draggable,i))){if(z=o(e),"function"==typeof l){if(l.call(this,a,e,this))return L(g,"filter",e,i,z),void a.preventDefault()}else if(l&&(l=l.split(",").some(function(a){return a=d(g,a.trim(),i),a?(L(a,"filter",e,i,z),!0):void 0})))return void a.preventDefault();if((!h.handle||d(g,h.handle,i))&&e&&!q&&e.parentNode===i){C=a,t=this.el,q=e,u=q.nextSibling,B=this.options.group,q.draggable=!0,h.ignore.split(",").forEach(function(a){j(e,a.trim(),k)}),c&&(C={target:e,clientX:c.clientX,clientY:c.clientY},this._onDragStart(C,"touch"),a.preventDefault()),f(H,"mouseup",this._onDrop),f(H,"touchend",this._onDrop),f(H,"touchcancel",this._onDrop),f(q,"dragend",this),f(t,"dragstart",this._onDragStart),J||this._onDragStart(C,!0);try{H.selection?H.selection.empty():window.getSelection().removeAllRanges()}catch(m){}}}},_emulateDragOver:function(){if(D){i(r,"display","none");var a=H.elementFromPoint(D.clientX,D.clientY),b=a,c=" "+this.options.group.name,d=Q.length;if(b)do{if(b[F]&&b[F].groups.indexOf(c)>-1){for(;d--;)Q[d]({clientX:D.clientX,clientY:D.clientY,target:a,rootEl:b});break}a=b}while(b=b.parentNode);i(r,"display","")}},_onTouchMove:function(a){if(C){var b=a.touches?a.touches[0]:a,c=b.clientX-C.clientX,d=b.clientY-C.clientY,e=a.touches?"translate3d("+c+"px,"+d+"px,0)":"translate("+c+"px,"+d+"px)";D=b,i(r,"webkitTransform",e),i(r,"mozTransform",e),i(r,"msTransform",e),i(r,"transform",e),a.preventDefault()}},_onDragStart:function(a,b){var c=a.dataTransfer,d=this.options;if(this._offUpEvents(),"clone"==B.pull&&(s=q.cloneNode(!0),i(s,"display","none"),t.insertBefore(s,q)),b){var e,g=q.getBoundingClientRect(),h=i(q);r=q.cloneNode(!0),i(r,"top",g.top-I(h.marginTop,10)),i(r,"left",g.left-I(h.marginLeft,10)),i(r,"width",g.width),i(r,"height",g.height),i(r,"opacity","0.8"),i(r,"position","fixed"),i(r,"zIndex","100000"),t.appendChild(r),e=r.getBoundingClientRect(),i(r,"width",2*g.width-e.width),i(r,"height",2*g.height-e.height),"touch"===b?(f(H,"touchmove",this._onTouchMove),f(H,"touchend",this._onDrop),f(H,"touchcancel",this._onDrop)):(f(H,"mousemove",this._onTouchMove),f(H,"mouseup",this._onDrop)),this._loopId=setInterval(this._emulateDragOver,150)}else c&&(c.effectAllowed="move",d.setData&&d.setData.call(this,c,q)),f(H,"drop",this);setTimeout(this._dragStarted,0)},_onDragOver:function(a){var c,e,f,g=this.el,h=this.options,j=h.group,k=j.put,n=B===j,o=h.sort;if(q&&(void 0!==a.preventDefault&&(a.preventDefault(),!h.dragoverBubble&&a.stopPropagation()),B&&!h.disabled&&(n?o||(f=!t.contains(q)):B.pull&&k&&(B.name===j.name||k.indexOf&&~k.indexOf(B.name)))&&(void 0===a.rootEl||a.rootEl===this.el))){if(R(a,h,this.el),K)return;if(c=d(a.target,h.draggable,g),e=q.getBoundingClientRect(),f)return b(!0),void(s||u?t.insertBefore(q,s||u):o||t.appendChild(q));if(0===g.children.length||g.children[0]===r||g===a.target&&(c=m(g,a))){if(c){if(c.animated)return;v=c.getBoundingClientRect()}b(n),g.appendChild(q),this._animate(e,q),c&&this._animate(v,c)}else if(c&&!c.animated&&c!==q&&void 0!==c.parentNode[F]){x!==c&&(x=c,y=i(c));var p,v=c.getBoundingClientRect(),w=v.right-v.left,z=v.bottom-v.top,A=/left|right|inline/.test(y.cssFloat+y.display),C=c.offsetWidth>q.offsetWidth,D=c.offsetHeight>q.offsetHeight,E=(A?(a.clientX-v.left)/w:(a.clientY-v.top)/z)>.5,G=c.nextElementSibling;K=!0,setTimeout(l,30),b(n),p=A?c.previousElementSibling===q&&!C||E&&C:G!==q&&!D||E&&D,p&&!G?g.appendChild(q):c.parentNode.insertBefore(q,p?G:c),this._animate(e,q),this._animate(v,c)}}},_animate:function(a,b){var c=this.options.animation;if(c){var d=b.getBoundingClientRect();i(b,"transition","none"),i(b,"transform","translate3d("+(a.left-d.left)+"px,"+(a.top-d.top)+"px,0)"),b.offsetWidth,i(b,"transition","all "+c+"ms"),i(b,"transform","translate3d(0,0,0)"),clearTimeout(b.animated),b.animated=setTimeout(function(){i(b,"transition",""),i(b,"transform",""),b.animated=!1},c)}},_offUpEvents:function(){g(H,"mouseup",this._onDrop),g(H,"touchmove",this._onTouchMove),g(H,"touchend",this._onDrop),g(H,"touchcancel",this._onDrop)},_onDrop:function(b){var c=this.el,d=this.options;clearInterval(this._loopId),clearInterval(E.pid),g(H,"drop",this),g(H,"mousemove",this._onTouchMove),g(c,"dragstart",this._onDragStart),this._offUpEvents(),b&&(b.preventDefault(),!d.dropBubble&&b.stopPropagation(),r&&r.parentNode.removeChild(r),q&&(g(q,"dragend",this),k(q),h(q,this.options.ghostClass,!1),t!==q.parentNode?(A=o(q),L(q.parentNode,"sort",q,t,z,A),L(t,"sort",q,t,z,A),L(q,"add",q,t,z,A),L(t,"remove",q,t,z,A)):(s&&s.parentNode.removeChild(s),q.nextSibling!==u&&(A=o(q),L(t,"update",q,t,z,A),L(t,"sort",q,t,z,A))),a.active&&L(t,"end",q,t,z,A)),t=q=r=u=s=v=w=C=D=x=y=B=a.active=null,this.save())},handleEvent:function(a){var b=a.type;"dragover"===b||"dragenter"===b?(this._onDragOver(a),e(a)):("drop"===b||"dragend"===b)&&this._onDrop(a)},toArray:function(){for(var a,b=[],c=this.el.children,e=0,f=c.length;f>e;e++)a=c[e],d(a,this.options.draggable,this.el)&&b.push(a.getAttribute("data-id")||n(a));return b},sort:function(a){var b={},c=this.el;this.toArray().forEach(function(a,e){var f=c.children[e];d(f,this.options.draggable,c)&&(b[a]=f)},this),a.forEach(function(a){b[a]&&(c.removeChild(b[a]),c.appendChild(b[a]))})},save:function(){var a=this.options.store;a&&a.set(this)},closest:function(a,b){return d(a,b||this.options.draggable,this.el)},option:function(a,b){var c=this.options;return void 0===b?c[a]:void(c[a]=b)},destroy:function(){var a=this.el,b=this.options;M.forEach(function(c){g(a,c.substr(2).toLowerCase(),b[c])}),g(a,"mousedown",this._onTapStart),g(a,"touchstart",this._onTapStart),g(a,"dragover",this),g(a,"dragenter",this),Array.prototype.forEach.call(a.querySelectorAll("[draggable]"),function(a){a.removeAttribute("draggable")}),Q.splice(Q.indexOf(this._onDragOver),1),this._onDrop(),this.el=null}},a.utils={on:f,off:g,css:i,find:j,bind:c,is:function(a,b){return!!d(a,b,a)},throttle:p,closest:d,toggleClass:h,dispatchEvent:L,index:o},a.version="1.1.1",a.create=function(b,c){return new a(b,c)},a});
\ No newline at end of file
+/*! Sortable 1.2.0 - MIT | git://github.com/rubaxa/Sortable.git */
+!function(a){"use strict";"function"==typeof define&&define.amd?define(a):"undefined"!=typeof module&&"undefined"!=typeof module.exports?module.exports=a():"undefined"!=typeof Package?Sortable=a():window.Sortable=a()}(function(){"use strict";function a(a,b){this.el=a,this.options=b=q({},b),a[H]=this;var d={group:Math.random(),sort:!0,disabled:!1,store:null,handle:null,scroll:!0,scrollSensitivity:30,scrollSpeed:10,draggable:/[uo]l/i.test(a.nodeName)?"li":">*",ghostClass:"sortable-ghost",ignore:"a, img",filter:null,animation:0,setData:function(a,b){a.setData("Text",b.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0};for(var e in d)!(e in b)&&(b[e]=d[e]);var g=b.group;g&&"object"==typeof g||(g=b.group={name:g}),["pull","put"].forEach(function(a){a in g||(g[a]=!0)}),b.groups=" "+g.name+(g.put.join?" "+g.put.join(" "):"")+" ";for(var h in this)"_"===h.charAt(0)&&(this[h]=c(this,this[h]));f(a,"mousedown",this._onTapStart),f(a,"touchstart",this._onTapStart),f(a,"dragover",this),f(a,"dragenter",this),Q.push(this._onDragOver),b.store&&this.sort(b.store.get(this))}function b(a){t&&t.state!==a&&(i(t,"display",a?"none":""),!a&&t.state&&u.insertBefore(t,r),t.state=a)}function c(a,b){var c=P.call(arguments,2);return b.bind?b.bind.apply(b,[a].concat(c)):function(){return b.apply(a,c.concat(P.call(arguments)))}}function d(a,b,c){if(a){c=c||J,b=b.split(".");var d=b.shift().toUpperCase(),e=new RegExp("\\s("+b.join("|")+")\\s","g");do if(">*"===d&&a.parentNode===c||(""===d||a.nodeName.toUpperCase()==d)&&(!b.length||((" "+a.className+" ").match(e)||[]).length==b.length))return a;while(a!==c&&(a=a.parentNode))}return null}function e(a){a.dataTransfer.dropEffect="move",a.preventDefault()}function f(a,b,c){a.addEventListener(b,c,!1)}function g(a,b,c){a.removeEventListener(b,c,!1)}function h(a,b,c){if(a)if(a.classList)a.classList[c?"add":"remove"](b);else{var d=(" "+a.className+" ").replace(G," ").replace(" "+b+" "," ");a.className=(d+(c?" "+b:"")).replace(G," ")}}function i(a,b,c){var d=a&&a.style;if(d){if(void 0===c)return J.defaultView&&J.defaultView.getComputedStyle?c=J.defaultView.getComputedStyle(a,""):a.currentStyle&&(c=a.currentStyle),void 0===b?c:c[b];b in d||(b="-webkit-"+b),d[b]=c+("string"==typeof c?"":"px")}}function j(a,b,c){if(a){var d=a.getElementsByTagName(b),e=0,f=d.length;if(c)for(;f>e;e++)c(d[e],e);return d}return[]}function k(a){a.draggable=!1}function l(){M=!1}function m(a,b){var c=a.lastElementChild,d=c.getBoundingClientRect();return b.clientY-(d.top+d.height)>5&&c}function n(a){for(var b=a.tagName+a.className+a.src+a.href+a.textContent,c=b.length,d=0;c--;)d+=b.charCodeAt(c);return d.toString(36)}function o(a){for(var b=0;a&&(a=a.previousElementSibling);)"TEMPLATE"!==a.nodeName.toUpperCase()&&b++;return b}function p(a,b){var c,d;return function(){void 0===c&&(c=arguments,d=this,setTimeout(function(){1===c.length?a.call(d,c[0]):a.apply(d,c),c=void 0},b))}}function q(a,b){if(a&&b)for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a}var r,s,t,u,v,w,x,y,z,A,B,C,D,E,F={},G=/\s+/g,H="Sortable"+(new Date).getTime(),I=window,J=I.document,K=I.parseInt,L=!!("draggable"in J.createElement("div")),M=!1,N=function(a,b,c,d,e,f,g){var h=J.createEvent("Event"),i=(a||b[H]).options,j="on"+c.charAt(0).toUpperCase()+c.substr(1);h.initEvent(c,!0,!0),h.item=d||b,h.from=e||b,h.clone=t,h.oldIndex=f,h.newIndex=g,i[j]&&i[j].call(a,h),b.dispatchEvent(h)},O=Math.abs,P=[].slice,Q=[],R=p(function(a,b,c){if(c&&b.scroll){var d,e,f,g,h=b.scrollSensitivity,i=b.scrollSpeed,j=a.clientX,k=a.clientY,l=window.innerWidth,m=window.innerHeight;if(x!==c&&(w=b.scroll,x=c,w===!0)){w=c;do if(w.offsetWidth=l-j)-(h>=j),g=(h>=m-k)-(h>=k),(f||g)&&(d=I)),(F.vx!==f||F.vy!==g||F.el!==d)&&(F.el=d,F.vx=f,F.vy=g,clearInterval(F.pid),d&&(F.pid=setInterval(function(){d===I?I.scrollTo(I.pageXOffset+f*i,I.pageYOffset+g*i):(g&&(d.scrollTop+=g*i),f&&(d.scrollLeft+=f*i))},24)))}},30);return a.prototype={constructor:a,_onTapStart:function(a){var b=this,c=this.el,e=this.options,f=a.type,g=a.touches&&a.touches[0],h=(g||a).target,i=h,j=e.filter;if(!("mousedown"===f&&0!==a.button||e.disabled)&&(h=d(h,e.draggable,c))){if(A=o(h),"function"==typeof j){if(j.call(this,a,h,this))return N(b,i,"filter",h,c,A),void a.preventDefault()}else if(j&&(j=j.split(",").some(function(a){return a=d(i,a.trim(),c),a?(N(b,a,"filter",h,c,A),!0):void 0})))return void a.preventDefault();(!e.handle||d(i,e.handle,c))&&this._prepareDragStart(a,g,h)}},_prepareDragStart:function(a,b,c){var d,e=this,g=e.el,h=e.options,i=g.ownerDocument;c&&!r&&c.parentNode===g&&(D=a,u=g,r=c,v=r.nextSibling,C=h.group,d=function(){e._disableDelayedDrag(),r.draggable=!0,h.ignore.split(",").forEach(function(a){j(r,a.trim(),k)}),e._triggerDragStart(b)},f(i,"mouseup",e._onDrop),f(i,"touchend",e._onDrop),f(i,"touchcancel",e._onDrop),h.delay?(f(i,"mousemove",e._disableDelayedDrag),f(i,"touchmove",e._disableDelayedDrag),e._dragStartTimer=setTimeout(d,h.delay)):d())},_disableDelayedDrag:function(){var a=this.el.ownerDocument;clearTimeout(this._dragStartTimer),g(a,"mousemove",this._disableDelayedDrag),g(a,"touchmove",this._disableDelayedDrag)},_triggerDragStart:function(a){a?(D={target:r,clientX:a.clientX,clientY:a.clientY},this._onDragStart(D,"touch")):L?(f(r,"dragend",this),f(u,"dragstart",this._onDragStart)):this._onDragStart(D,!0);try{J.selection?J.selection.empty():window.getSelection().removeAllRanges()}catch(b){}},_dragStarted:function(){u&&r&&(h(r,this.options.ghostClass,!0),a.active=this,N(this,u,"start",r,u,A))},_emulateDragOver:function(){if(E){i(s,"display","none");var a=J.elementFromPoint(E.clientX,E.clientY),b=a,c=" "+this.options.group.name,d=Q.length;if(b)do{if(b[H]&&b[H].options.groups.indexOf(c)>-1){for(;d--;)Q[d]({clientX:E.clientX,clientY:E.clientY,target:a,rootEl:b});break}a=b}while(b=b.parentNode);i(s,"display","")}},_onTouchMove:function(a){if(D){var b=a.touches?a.touches[0]:a,c=b.clientX-D.clientX,d=b.clientY-D.clientY,e=a.touches?"translate3d("+c+"px,"+d+"px,0)":"translate("+c+"px,"+d+"px)";E=b,i(s,"webkitTransform",e),i(s,"mozTransform",e),i(s,"msTransform",e),i(s,"transform",e),a.preventDefault()}},_onDragStart:function(a,b){var c=a.dataTransfer,d=this.options;if(this._offUpEvents(),"clone"==C.pull&&(t=r.cloneNode(!0),i(t,"display","none"),u.insertBefore(t,r)),b){var e,g=r.getBoundingClientRect(),h=i(r);s=r.cloneNode(!0),i(s,"top",g.top-K(h.marginTop,10)),i(s,"left",g.left-K(h.marginLeft,10)),i(s,"width",g.width),i(s,"height",g.height),i(s,"opacity","0.8"),i(s,"position","fixed"),i(s,"zIndex","100000"),u.appendChild(s),e=s.getBoundingClientRect(),i(s,"width",2*g.width-e.width),i(s,"height",2*g.height-e.height),"touch"===b?(f(J,"touchmove",this._onTouchMove),f(J,"touchend",this._onDrop),f(J,"touchcancel",this._onDrop)):(f(J,"mousemove",this._onTouchMove),f(J,"mouseup",this._onDrop)),this._loopId=setInterval(this._emulateDragOver,150)}else c&&(c.effectAllowed="move",d.setData&&d.setData.call(this,c,r)),f(J,"drop",this);setTimeout(this._dragStarted,0)},_onDragOver:function(a){var c,e,f,g=this.el,h=this.options,j=h.group,k=j.put,n=C===j,o=h.sort;if(void 0!==a.preventDefault&&(a.preventDefault(),!h.dragoverBubble&&a.stopPropagation()),C&&!h.disabled&&(n?o||(f=!u.contains(r)):C.pull&&k&&(C.name===j.name||k.indexOf&&~k.indexOf(C.name)))&&(void 0===a.rootEl||a.rootEl===this.el)){if(R(a,h,this.el),M)return;if(c=d(a.target,h.draggable,g),e=r.getBoundingClientRect(),f)return b(!0),void(t||v?u.insertBefore(r,t||v):o||u.appendChild(r));if(0===g.children.length||g.children[0]===s||g===a.target&&(c=m(g,a))){if(c){if(c.animated)return;q=c.getBoundingClientRect()}b(n),g.appendChild(r),this._animate(e,r),c&&this._animate(q,c)}else if(c&&!c.animated&&c!==r&&void 0!==c.parentNode[H]){y!==c&&(y=c,z=i(c));var p,q=c.getBoundingClientRect(),w=q.right-q.left,x=q.bottom-q.top,A=/left|right|inline/.test(z.cssFloat+z.display),B=c.offsetWidth>r.offsetWidth,D=c.offsetHeight>r.offsetHeight,E=(A?(a.clientX-q.left)/w:(a.clientY-q.top)/x)>.5,F=c.nextElementSibling;M=!0,setTimeout(l,30),b(n),p=A?c.previousElementSibling===r&&!B||E&&B:F!==r&&!D||E&&D,p&&!F?g.appendChild(r):c.parentNode.insertBefore(r,p?F:c),this._animate(e,r),this._animate(q,c)}}},_animate:function(a,b){var c=this.options.animation;if(c){var d=b.getBoundingClientRect();i(b,"transition","none"),i(b,"transform","translate3d("+(a.left-d.left)+"px,"+(a.top-d.top)+"px,0)"),b.offsetWidth,i(b,"transition","all "+c+"ms"),i(b,"transform","translate3d(0,0,0)"),clearTimeout(b.animated),b.animated=setTimeout(function(){i(b,"transition",""),i(b,"transform",""),b.animated=!1},c)}},_offUpEvents:function(){var a=this.el.ownerDocument;g(J,"touchmove",this._onTouchMove),g(a,"mouseup",this._onDrop),g(a,"touchend",this._onDrop),g(a,"touchcancel",this._onDrop)},_onDrop:function(b){var c=this.el,d=this.options;clearInterval(this._loopId),clearInterval(F.pid),clearTimeout(this.dragStartTimer),g(J,"drop",this),g(J,"mousemove",this._onTouchMove),g(c,"dragstart",this._onDragStart),this._offUpEvents(),b&&(b.preventDefault(),!d.dropBubble&&b.stopPropagation(),s&&s.parentNode.removeChild(s),r&&(g(r,"dragend",this),k(r),h(r,this.options.ghostClass,!1),u!==r.parentNode?(B=o(r),N(null,r.parentNode,"sort",r,u,A,B),N(this,u,"sort",r,u,A,B),N(null,r.parentNode,"add",r,u,A,B),N(this,u,"remove",r,u,A,B)):(t&&t.parentNode.removeChild(t),r.nextSibling!==v&&(B=o(r),N(this,u,"update",r,u,A,B),N(this,u,"sort",r,u,A,B))),a.active&&N(this,u,"end",r,u,A,B)),u=r=s=v=t=w=x=D=E=y=z=C=a.active=null,this.save())},handleEvent:function(a){var b=a.type;"dragover"===b||"dragenter"===b?r&&(this._onDragOver(a),e(a)):("drop"===b||"dragend"===b)&&this._onDrop(a)},toArray:function(){for(var a,b=[],c=this.el.children,e=0,f=c.length,g=this.options;f>e;e++)a=c[e],d(a,g.draggable,this.el)&&b.push(a.getAttribute(g.dataIdAttr)||n(a));return b},sort:function(a){var b={},c=this.el;this.toArray().forEach(function(a,e){var f=c.children[e];d(f,this.options.draggable,c)&&(b[a]=f)},this),a.forEach(function(a){b[a]&&(c.removeChild(b[a]),c.appendChild(b[a]))})},save:function(){var a=this.options.store;a&&a.set(this)},closest:function(a,b){return d(a,b||this.options.draggable,this.el)},option:function(a,b){var c=this.options;return void 0===b?c[a]:void(c[a]=b)},destroy:function(){var a=this.el;a[H]=null,g(a,"mousedown",this._onTapStart),g(a,"touchstart",this._onTapStart),g(a,"dragover",this),g(a,"dragenter",this),Array.prototype.forEach.call(a.querySelectorAll("[draggable]"),function(a){a.removeAttribute("draggable")}),Q.splice(Q.indexOf(this._onDragOver),1),this._onDrop(),this.el=a=null}},a.utils={on:f,off:g,css:i,find:j,bind:c,is:function(a,b){return!!d(a,b,a)},extend:q,throttle:p,closest:d,toggleClass:h,index:o},a.version="1.2.0",a.create=function(b,c){return new a(b,c)},a});
\ No newline at end of file
diff --git a/bower.json b/bower.json
index c5e42aa..4ae98d7 100644
--- a/bower.json
+++ b/bower.json
@@ -1,7 +1,7 @@
{
"name": "Sortable",
"main": "Sortable.js",
- "version": "1.1.1",
+ "version": "1.2.0",
"homepage": "http://rubaxa.github.io/Sortable/",
"authors": [
"RubaXa "
diff --git a/component.json b/component.json
index de414f1..04a090a 100644
--- a/component.json
+++ b/component.json
@@ -1,7 +1,7 @@
{
"name": "Sortable",
"main": "Sortable.js",
- "version": "1.1.1",
+ "version": "1.2.0",
"homepage": "http://rubaxa.github.io/Sortable/",
"repo": "RubaXa/Sortable",
"authors": [
diff --git a/knockout-sortable.js b/knockout-sortable.js
new file mode 100644
index 0000000..6a7f701
--- /dev/null
+++ b/knockout-sortable.js
@@ -0,0 +1,161 @@
+(function () {
+ "use strict";
+
+ var init = function (element, valueAccessor, allBindings, viewModel, bindingContext, sortableOptions) {
+
+ var options = buildOptions(valueAccessor, sortableOptions);
+
+ //It's seems that we cannot update the eventhandlers after we've created the sortable, so define them in init instead of update
+ ['onStart', 'onEnd', 'onRemove', 'onAdd', 'onUpdate', 'onSort', 'onFilter'].forEach(function (e) {
+ if (options[e] || eventHandlers[e])
+ options[e] = function (eventType, parentVM, parentBindings, handler, e) {
+ var itemVM = ko.dataFor(e.item),
+ //All of the bindings on the parent element
+ bindings = ko.utils.peekObservable(parentBindings()),
+ //The binding options for the draggable/sortable binding of the parent element
+ bindingHandlerBinding = bindings.sortable || bindings.draggable,
+ //The collection that we should modify
+ collection = bindingHandlerBinding.collection || bindingHandlerBinding.foreach;
+ if (handler)
+ handler(e, itemVM, parentVM, collection, bindings);
+ if (eventHandlers[eventType])
+ eventHandlers[eventType](e, itemVM, parentVM, collection, bindings);
+ }.bind(undefined, e, viewModel, allBindings, options[e]);
+ });
+
+ viewModel._sortable = Sortable.create(element, options);
+
+ //Destroy the sortable if knockout disposes the element it's connected to
+ ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
+ viewModel._sortable.destroy();
+ });
+ return ko.bindingHandlers.template.init(element, valueAccessor);
+ },
+ update = function (element, valueAccessor, allBindings, viewModel, bindingContext, sortableOptions) {
+
+ //There seems to be some problems with updating the options of a sortable
+ //Tested to change eventhandlers and the group options without any luck
+
+ return ko.bindingHandlers.template.update(element, valueAccessor, allBindings, viewModel, bindingContext);
+ },
+ eventHandlers = (function (handlers) {
+
+ var moveOperations = [],
+ tryMoveOperation = function (e, itemVM, parentVM, collection, parentBindings) {
+ //A move operation is the combination of a add and remove event, this is to make sure that we have both the target and origin collections
+ var currentOperation = { event: e, itemVM: itemVM, parentVM: parentVM, collection: collection, parentBindings: parentBindings },
+ existingOperation = moveOperations.filter(function (op) {
+ return op.itemVM === currentOperation.itemVM;
+ })[0];
+
+ if (!existingOperation) {
+ moveOperations.push(currentOperation);
+ }
+ else {
+ //We're finishing the operation and already have a handle on the operation item meaning that it's safe to remove it
+ moveOperations.splice(moveOperations.indexOf(existingOperation), 1);
+
+ var removeOperation = currentOperation.event.type === 'remove' ? currentOperation : existingOperation,
+ addOperation = currentOperation.event.type === 'add' ? currentOperation : existingOperation;
+
+ moveItem(itemVM, removeOperation.collection, addOperation.collection, addOperation.event.clone, addOperation.event);
+ }
+ },
+ //Moves an item from the to (collection to from (collection), these can be references to the same collection which means it's a sort,
+ //clone indicates if we should move or copy the item into the new collection
+ moveItem = function (itemVM, from, to, clone, e) {
+ //Unwrapping this allows us to manipulate the actual array
+ var fromArray = from(),
+ //It's not certain that the items actual index is the same as the index reported by sortable due to filtering etc.
+ originalIndex = fromArray.indexOf(itemVM);
+
+ //Remove sortables "unbound" element
+ e.item.parentNode.removeChild(e.item);
+
+ //This splice is necessary for both clone and move/sort
+ //In sort/move since it shouldn't be at this index/in this array anymore
+ //In clone since we have to work around knockouts valuHasMutated when manipulating arrays and avoid a "unbound" item added by sortable
+ fromArray.splice(originalIndex, 1);
+ //Update the array, this will also remove sortables "unbound" clone
+ from.valueHasMutated();
+ if (clone && from !== to) {
+ //Readd the item
+ fromArray.splice(originalIndex, 0, itemVM);
+ //Force knockout to update
+ from.valueHasMutated();
+ }
+ //Insert the item on its new position
+ to().splice(e.newIndex, 0, itemVM);
+ //Make sure to tell knockout that we've modified the actual array.
+ to.valueHasMutated();
+ };
+
+ handlers.onRemove = tryMoveOperation;
+ handlers.onAdd = tryMoveOperation;
+ handlers.onUpdate = function (e, itemVM, parentVM, collection, parentBindings) {
+ //This will be performed as a sort since the to/from collections reference the same collection and clone is set to false
+ moveItem(itemVM, collection, collection, false, e);
+ };
+
+ return handlers;
+ })({}),
+ //bindingOptions are the options set in the "data-bind" attribute in the ui.
+ //options are custom options, for instance draggable/sortable specific options
+ buildOptions = function (bindingOptions, options) {
+ //deep clone/copy of properties from the "from" argument onto the "into" argument and returns the modified "into"
+ var merge = function (into, from) {
+ for (var prop in from) {
+ if (Object.prototype.toString.call(from[prop]) === '[object Object]') {
+ if (Object.prototype.toString.call(into[prop]) !== '[object Object]') {
+ into[prop] = {};
+ }
+ into[prop] = merge(into[prop], from[prop]);
+ }
+ else
+ into[prop] = from[prop];
+ }
+
+ return into;
+ },
+ //unwrap the supplied options
+ unwrappedOptions = ko.utils.peekObservable(bindingOptions()).options || {};
+
+ //Make sure that we don't modify the provided settings object
+ options = merge({}, options);
+
+ //group is handled differently since we should both allow to change a draggable to a sortable (and vice versa),
+ //but still be able to set a name on a draggable without it becoming a drop target.
+ if (unwrappedOptions.group && Object.prototype.toString.call(unwrappedOptions.group) !== '[object Object]') {
+ //group property is a name string declaration, convert to object.
+ unwrappedOptions.group = { name: unwrappedOptions.group };
+ }
+
+ return merge(options, unwrappedOptions);
+ };
+
+ ko.bindingHandlers.draggable = {
+ sortableOptions: {
+ group: { pull: 'clone', put: false },
+ sort: false
+ },
+ init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
+ return init(element, valueAccessor, allBindings, viewModel, bindingContext, ko.bindingHandlers.draggable.sortableOptions);
+ },
+ update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
+ return update(element, valueAccessor, allBindings, viewModel, bindingContext, ko.bindingHandlers.draggable.sortableOptions);
+ }
+ };
+
+ ko.bindingHandlers.sortable = {
+ sortableOptions: {
+ group: { pull: true, put: true }
+ },
+ init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
+ return init(element, valueAccessor, allBindings, viewModel, bindingContext, ko.bindingHandlers.sortable.sortableOptions);
+ },
+ update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
+ return update(element, valueAccessor, allBindings, viewModel, bindingContext, ko.bindingHandlers.sortable.sortableOptions);
+ }
+ };
+
+})();
\ No newline at end of file
diff --git a/ng-sortable.js b/ng-sortable.js
index a872561..d340a59 100644
--- a/ng-sortable.js
+++ b/ng-sortable.js
@@ -25,7 +25,7 @@
angular.module('ng-sortable', [])
- .constant('$version', '0.3.5')
+ .constant('version', '0.3.7')
.directive('ngSortable', ['$parse', function ($parse) {
var removed,
nextSibling;
@@ -78,7 +78,7 @@
/* jshint expr:true */
options[name] && options[name]({
- model: item,
+ model: item || source && source.item(evt.item),
models: source && source.items(),
oldIndex: evt.oldIndex,
newIndex: evt.newIndex
@@ -143,13 +143,13 @@
},
onUpdate: function (/**Event*/evt) {
_sync(evt);
- _emitEvent(evt, source && source.item(evt.item));
+ _emitEvent(evt);
},
onRemove: function (/**Event*/evt) {
_emitEvent(evt, removed);
},
onSort: function (/**Event*/evt) {
- _emitEvent(evt, source && source.item(evt.item));
+ _emitEvent(evt);
}
}));
@@ -160,11 +160,17 @@
});
if (ngSortable && !/{|}/.test(ngSortable)) { // todo: ugly
- angular.forEach(['sort', 'disabled', 'draggable', 'handle', 'animation'], function (name) {
+ angular.forEach([
+ 'sort', 'disabled', 'draggable', 'handle', 'animation',
+ 'onStart', 'onEnd', 'onAdd', 'onUpdate', 'onRemove', 'onSort'
+ ], function (name) {
scope.$watch(ngSortable + '.' + name, function (value) {
if (value !== void 0) {
options[name] = value;
- sortable.option(name, value);
+
+ if (!/^on[A-Z]/.test(name)) {
+ sortable.option(name, value);
+ }
}
});
});
diff --git a/package.json b/package.json
index 44367c2..6b40d2a 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "sortablejs",
"exportName": "Sortable",
- "version": "1.1.1",
+ "version": "1.2.0",
"devDependencies": {
"grunt": "*",
"grunt-version": "*",
diff --git a/st/iframe/frame.html b/st/iframe/frame.html
new file mode 100644
index 0000000..677eeef
--- /dev/null
+++ b/st/iframe/frame.html
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 14
+
+ Drag me by the handle
+
+
+ 2
+
+ You can also select text
+
+
+ 1
+
+ Best of both worlds!
+
+
+
+
+
diff --git a/st/iframe/index.html b/st/iframe/index.html
new file mode 100644
index 0000000..fcd0898
--- /dev/null
+++ b/st/iframe/index.html
@@ -0,0 +1,49 @@
+
+
+
+
+ IFrame playground
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
It works with Bootstrap...
+
...out of the box.
+
It has support for touch devices.
+
Just drag some elements around.
+
+
+
+
+
+