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 + + + + + + + + + + + + + +
+
This is Sortable
+
It works with Bootstrap...
+
...out of the box.
+
It has support for touch devices.
+
Just drag some elements around.
+
+ + + + +