Browse Source

+ master

pull/798/merge
Lebedev Konstantin 8 years ago
parent
commit
42bb4e89d2
  1. 1
      Gruntfile.js
  2. 38
      README.md
  3. 76
      Sortable.js
  4. 4
      Sortable.min.js
  5. 2
      component.json
  6. 239
      knockout-sortable.js
  7. 6
      package.json

1
Gruntfile.js

@ -82,7 +82,6 @@ module.exports = function (grunt) {
grunt.loadNpmTasks('grunt-version'); grunt.loadNpmTasks('grunt-version');
grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-exec');
grunt.registerTask('tests', ['jshint']); grunt.registerTask('tests', ['jshint']);
grunt.registerTask('default', ['tests', 'version', 'uglify:dist']); grunt.registerTask('default', ['tests', 'version', 'uglify:dist']);

38
README.md

@ -22,6 +22,7 @@ Demo: http://rubaxa.github.io/Sortable/
* [Mixin](https://github.com/SortableJS/react-mixin-sortablejs) * [Mixin](https://github.com/SortableJS/react-mixin-sortablejs)
* [Knockout](https://github.com/SortableJS/knockout-sortablejs) * [Knockout](https://github.com/SortableJS/knockout-sortablejs)
* [Polymer](https://github.com/SortableJS/polymer-sortablejs) * [Polymer](https://github.com/SortableJS/polymer-sortablejs)
* [Vue](https://github.com/SortableJS/Vue.Draggable)
* Supports any CSS library, e.g. [Bootstrap](#bs) * Supports any CSS library, e.g. [Bootstrap](#bs)
* Simple API * Simple API
* [CDN](#cdn) * [CDN](#cdn)
@ -69,7 +70,7 @@ var el = document.getElementById('items');
var sortable = Sortable.create(el); var sortable = Sortable.create(el);
``` ```
You can use any element for the list and its elements, not just `ul`/`li`. Here is an [example with `div`s](http://jsbin.com/luxero/2/edit?html,js,output). You can use any element for the list and its elements, not just `ul`/`li`. Here is an [example with `div`s](http://jsbin.com/qumuwe/edit?html,js,output).
--- ---
@ -86,6 +87,7 @@ var sortable = new Sortable(el, {
animation: 150, // ms, animation speed moving items when sorting, `0` — without animation animation: 150, // ms, animation speed moving items when sorting, `0` — without animation
handle: ".my-handle", // Drag handle selector within list items handle: ".my-handle", // Drag handle selector within list items
filter: ".ignore-elements", // Selectors that do not lead to dragging (String or Function) filter: ".ignore-elements", // Selectors that do not lead to dragging (String or Function)
preventOnFilter: true, // Call `event.preventDefault()` when triggered `filter`
draggable: ".item", // Specifies which items inside the element should be draggable draggable: ".item", // Specifies which items inside the element should be draggable
ghostClass: "sortable-ghost", // Class name for the drop placeholder ghostClass: "sortable-ghost", // Class name for the drop placeholder
chosenClass: "sortable-chosen", // Class name for the chosen item chosenClass: "sortable-chosen", // Class name for the chosen item
@ -96,7 +98,7 @@ var sortable = new Sortable(el, {
fallbackClass: "sortable-fallback", // Class name for the cloned DOM Element when using forceFallback fallbackClass: "sortable-fallback", // Class name for the cloned DOM Element when using forceFallback
fallbackOnBody: false, // Appends the cloned DOM Element into the Document's Body fallbackOnBody: false, // Appends the cloned DOM Element into the Document's Body
fallbackTolerance: 0 // Specify in pixels how far the mouse should move before it's considered as a drag. fallbackTolerance: 0, // Specify in pixels how far the mouse should move before it's considered as a drag.
scroll: true, // or HTMLElement scroll: true, // or HTMLElement
scrollFn: function(offsetX, offsetY, originalEvent) { ... }, // if you have custom scrollbar scrollFn may be used for autoscrolling scrollFn: function(offsetX, offsetY, originalEvent) { ... }, // if you have custom scrollbar scrollFn may be used for autoscrolling
@ -112,11 +114,6 @@ var sortable = new Sortable(el, {
evt.oldIndex; // element index within parent evt.oldIndex; // element index within parent
}, },
// Element is unchosen
onUnchoose: function (/**Event*/evt) {
evt.oldIndex; // element index within parent
},
// Element dragging started // Element dragging started
onStart: function (/**Event*/evt) { onStart: function (/**Event*/evt) {
evt.oldIndex; // element index within parent evt.oldIndex; // element index within parent
@ -186,10 +183,13 @@ You can also define whether lists can give away, give and keep a copy (`clone`),
* name: `String` — group name * name: `String` — group name
* pull: `true|false|'clone'|function` — ability to move from the list. `clone` — copy the item, rather than move. * pull: `true|false|'clone'|function` — ability to move from the list. `clone` — copy the item, rather than move.
* put: `true|false|["foo", "bar"]|function` — whether elements can be added from other lists, or an array of group names from which elements can be taken. * put: `true|false|["foo", "bar"]|function` — whether elements can be added from other lists, or an array of group names from which elements can be taken.
* revertClone: `boolean` — revert cloned element to initial position after moving to a another list.
Demo: Demo:
- http://jsbin.com/naduvo/edit?js,output - http://jsbin.com/naduvo/edit?js,output
- http://jsbin.com/rusuvot/edit?js,output — use of complex logic in the `pull` and` put` - http://jsbin.com/rusuvot/edit?js,output — use of complex logic in the `pull` and` put`
- http://jsbin.com/magogub/edit?js,output — use `revertClone: true`
--- ---
@ -198,7 +198,7 @@ Demo:
#### `sort` option #### `sort` option
Sorting inside list. Sorting inside list.
Demo: http://jsbin.com/xizeh/2/edit?html,js,output Demo: http://jsbin.com/videzob/edit?html,js,output
--- ---
@ -207,7 +207,7 @@ Demo: http://jsbin.com/xizeh/2/edit?html,js,output
#### `delay` option #### `delay` option
Time in milliseconds to define when the sorting should start. Time in milliseconds to define when the sorting should start.
Demo: http://jsbin.com/xizeh/4/edit?html,js,output Demo: http://jsbin.com/xizeh/edit?html,js,output
--- ---
@ -216,7 +216,7 @@ Demo: http://jsbin.com/xizeh/4/edit?html,js,output
#### `disabled` options #### `disabled` options
Disables the sortable if set to `true`. Disables the sortable if set to `true`.
Demo: http://jsbin.com/xiloqu/1/edit?html,js,output Demo: http://jsbin.com/xiloqu/edit?html,js,output
```js ```js
var sortable = Sortable.create(list); var sortable = Sortable.create(list);
@ -237,7 +237,7 @@ To make list items draggable, Sortable disables text selection by the user.
That's not always desirable. To allow text selection, define a drag handler, That's not always desirable. To allow text selection, define a drag handler,
which is an area of every list element that allows it to be dragged around. which is an area of every list element that allows it to be dragged around.
Demo: http://jsbin.com/newize/1/edit?html,js,output Demo: http://jsbin.com/newize/edit?html,js,output
```js ```js
Sortable.create(el, { Sortable.create(el, {
@ -290,7 +290,7 @@ Sortable.create(list, {
#### `ghostClass` option #### `ghostClass` option
Class name for the drop placeholder (default `sortable-ghost`). Class name for the drop placeholder (default `sortable-ghost`).
Demo: http://jsbin.com/hunifu/1/edit?css,js,output Demo: http://jsbin.com/hunifu/4/edit?css,js,output
```css ```css
.ghost { .ghost {
@ -311,7 +311,7 @@ Sortable.create(list, {
#### `chosenClass` option #### `chosenClass` option
Class name for the chosen item (default `sortable-chosen`). Class name for the chosen item (default `sortable-chosen`).
Demo: http://jsbin.com/hunifu/edit?html,css,js,output Demo: http://jsbin.com/hunifu/3/edit?html,css,js,output
```css ```css
.chosen { .chosen {
@ -337,7 +337,7 @@ This gives us the possibility to test the behaviour for older Browsers even in n
On top of that, the Fallback always generates a copy of that DOM Element and appends the class `fallbackClass` defined in the options. This behaviour controls the look of this 'dragged' Element. On top of that, the Fallback always generates a copy of that DOM Element and appends the class `fallbackClass` defined in the options. This behaviour controls the look of this 'dragged' Element.
Demo: http://jsbin.com/pucurizace/edit?html,css,js,output Demo: http://jsbin.com/yacuqib/edit?html,css,js,output
--- ---
@ -360,8 +360,8 @@ Dragging only starts if you move the pointer past a certain tolerance, so that y
If set to `true`, the page (or sortable-area) scrolls when coming to an edge. If set to `true`, the page (or sortable-area) scrolls when coming to an edge.
Demo: Demo:
- `window`: http://jsbin.com/boqugumiqi/1/edit?html,js,output - `window`: http://jsbin.com/tutuzeh/edit?html,js,output
- `overflow: hidden`: http://jsbin.com/kohamakiwi/1/edit?html,js,output - `overflow: hidden`: http://jsbin.com/kolisu/edit?html,js,output
--- ---
@ -491,7 +491,7 @@ Sortable.create(el, {
<a name="bs"></a> <a name="bs"></a>
### Bootstrap ### Bootstrap
Demo: http://jsbin.com/luxero/2/edit?html,js,output Demo: http://jsbin.com/qumuwe/edit?html,js,output
```html ```html
<!-- Latest compiled and minified CSS --> <!-- Latest compiled and minified CSS -->
@ -562,11 +562,11 @@ Link to the active instance.
```html ```html
<!-- CDNJS :: Sortable (https://cdnjs.com/) --> <!-- CDNJS :: Sortable (https://cdnjs.com/) -->
<script src="//cdnjs.cloudflare.com/ajax/libs/Sortable/1.5.2/Sortable.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/Sortable/1.5.0-rc1/Sortable.min.js"></script>
<!-- jsDelivr :: Sortable (http://www.jsdelivr.com/) --> <!-- jsDelivr :: Sortable (http://www.jsdelivr.com/) -->
<script src="//cdn.jsdelivr.net/sortable/1.5.2/Sortable.min.js"></script> <script src="//cdn.jsdelivr.net/sortable/1.5.0-rc1/Sortable.min.js"></script>
<!-- jsDelivr :: Sortable :: Latest (http://www.jsdelivr.com/) --> <!-- jsDelivr :: Sortable :: Latest (http://www.jsdelivr.com/) -->

76
Sortable.js

@ -13,10 +13,6 @@
else if (typeof module != "undefined" && typeof module.exports != "undefined") { else if (typeof module != "undefined" && typeof module.exports != "undefined") {
module.exports = factory(); module.exports = factory();
} }
else if (typeof Package !== "undefined") {
//noinspection JSUnresolvedVariable
Sortable = factory(); // export for Meteor.js
}
else { else {
/* jshint sub:true */ /* jshint sub:true */
window["Sortable"] = factory(); window["Sortable"] = factory();
@ -36,6 +32,7 @@
cloneEl, cloneEl,
rootEl, rootEl,
nextEl, nextEl,
lastDownEl,
scrollEl, scrollEl,
scrollParentEl, scrollParentEl,
@ -70,6 +67,8 @@
$ = win.jQuery || win.Zepto, $ = win.jQuery || win.Zepto,
Polymer = win.Polymer, Polymer = win.Polymer,
captureMode = {capture: false, passive: false},
supportDraggable = !!('draggable' in document.createElement('div')), supportDraggable = !!('draggable' in document.createElement('div')),
supportCssPointerEvents = (function (el) { supportCssPointerEvents = (function (el) {
// false when IE11 // false when IE11
@ -85,14 +84,14 @@
abs = Math.abs, abs = Math.abs,
min = Math.min, min = Math.min,
slice = [].slice,
touchDragOverListeners = [], touchDragOverListeners = [],
_autoScroll = _throttle(function (/**Event*/evt, /**Object*/options, /**HTMLElement*/rootEl) { _autoScroll = _throttle(function (/**Event*/evt, /**Object*/options, /**HTMLElement*/rootEl) {
// Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=505521 // Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=505521
if (rootEl && options.scroll) { if (rootEl && options.scroll) {
var el, var _this = rootEl[expando],
el,
rect, rect,
sens = options.scrollSensitivity, sens = options.scrollSensitivity,
speed = options.scrollSpeed, speed = options.scrollSpeed,
@ -249,6 +248,7 @@
dragClass: 'sortable-drag', dragClass: 'sortable-drag',
ignore: 'a, img', ignore: 'a, img',
filter: null, filter: null,
preventOnFilter: true,
animation: 0, animation: 0,
setData: function (dataTransfer, dragEl) { setData: function (dataTransfer, dragEl) {
dataTransfer.setData('Text', dragEl.textContent); dataTransfer.setData('Text', dragEl.textContent);
@ -285,6 +285,7 @@
// Bind events // Bind events
_on(el, 'mousedown', this._onTapStart); _on(el, 'mousedown', this._onTapStart);
_on(el, 'touchstart', this._onTapStart); _on(el, 'touchstart', this._onTapStart);
_on(el, 'pointerdown', this._onTapStart);
if (this.nativeDraggable) { if (this.nativeDraggable) {
_on(el, 'dragover', this); _on(el, 'dragover', this);
@ -305,6 +306,7 @@
var _this = this, var _this = this,
el = this.el, el = this.el,
options = this.options, options = this.options,
preventOnFilter = options.preventOnFilter,
type = evt.type, type = evt.type,
touch = evt.touches && evt.touches[0], touch = evt.touches && evt.touches[0],
target = (touch || evt).target, target = (touch || evt).target,
@ -321,9 +323,6 @@
return; // only left button or enabled return; // only left button or enabled
} }
if (options.handle && !_closest(originalTarget, options.handle, el)) {
return;
}
target = _closest(target, options.draggable, el); target = _closest(target, options.draggable, el);
@ -331,6 +330,11 @@
return; return;
} }
if (lastDownEl === target) {
// Ignoring duplicate `down`
return;
}
// Get the index of the dragged element within its parent // Get the index of the dragged element within its parent
startIndex = _index(target, options.draggable); startIndex = _index(target, options.draggable);
@ -338,7 +342,7 @@
if (typeof filter === 'function') { if (typeof filter === 'function') {
if (filter.call(this, evt, target, this)) { if (filter.call(this, evt, target, this)) {
_dispatchEvent(_this, originalTarget, 'filter', target, el, startIndex); _dispatchEvent(_this, originalTarget, 'filter', target, el, startIndex);
evt.preventDefault(); preventOnFilter && evt.preventDefault();
return; // cancel dnd return; // cancel dnd
} }
} }
@ -353,11 +357,15 @@
}); });
if (filter) { if (filter) {
evt.preventDefault(); preventOnFilter && evt.preventDefault();
return; // cancel dnd return; // cancel dnd
} }
} }
if (options.handle && !_closest(originalTarget, options.handle, el)) {
return;
}
// Prepare `dragstart` // Prepare `dragstart`
this._prepareDragStart(evt, touch, target, startIndex); this._prepareDragStart(evt, touch, target, startIndex);
}, },
@ -376,6 +384,7 @@
dragEl = target; dragEl = target;
parentEl = dragEl.parentNode; parentEl = dragEl.parentNode;
nextEl = dragEl.nextSibling; nextEl = dragEl.nextSibling;
lastDownEl = target
activeGroup = options.group; activeGroup = options.group;
oldIndex = startIndex; oldIndex = startIndex;
@ -396,7 +405,7 @@
_toggleClass(dragEl, options.chosenClass, true); _toggleClass(dragEl, options.chosenClass, true);
// Bind the events: dragstart/dragend // Bind the events: dragstart/dragend
_this._triggerDragStart(touch); _this._triggerDragStart(evt, touch);
// Drag start event // Drag start event
_dispatchEvent(_this, rootEl, 'choose', dragEl, rootEl, oldIndex); _dispatchEvent(_this, rootEl, 'choose', dragEl, rootEl, oldIndex);
@ -410,6 +419,7 @@
_on(ownerDocument, 'mouseup', _this._onDrop); _on(ownerDocument, 'mouseup', _this._onDrop);
_on(ownerDocument, 'touchend', _this._onDrop); _on(ownerDocument, 'touchend', _this._onDrop);
_on(ownerDocument, 'touchcancel', _this._onDrop); _on(ownerDocument, 'touchcancel', _this._onDrop);
_on(ownerDocument, 'pointercancel', _this._onDrop);
if (options.delay) { if (options.delay) {
// If the user moves the pointer or let go the click or touch // If the user moves the pointer or let go the click or touch
@ -420,11 +430,16 @@
_on(ownerDocument, 'touchcancel', _this._disableDelayedDrag); _on(ownerDocument, 'touchcancel', _this._disableDelayedDrag);
_on(ownerDocument, 'mousemove', _this._disableDelayedDrag); _on(ownerDocument, 'mousemove', _this._disableDelayedDrag);
_on(ownerDocument, 'touchmove', _this._disableDelayedDrag); _on(ownerDocument, 'touchmove', _this._disableDelayedDrag);
_on(ownerDocument, 'pointermove', _this._disableDelayedDrag);
_this._dragStartTimer = setTimeout(dragStartFn, options.delay); _this._dragStartTimer = setTimeout(dragStartFn, options.delay);
} else { } else {
dragStartFn(); dragStartFn();
} }
if (options.forceFallback) {
evt.preventDefault();
}
} }
}, },
@ -437,9 +452,11 @@
_off(ownerDocument, 'touchcancel', this._disableDelayedDrag); _off(ownerDocument, 'touchcancel', this._disableDelayedDrag);
_off(ownerDocument, 'mousemove', this._disableDelayedDrag); _off(ownerDocument, 'mousemove', this._disableDelayedDrag);
_off(ownerDocument, 'touchmove', this._disableDelayedDrag); _off(ownerDocument, 'touchmove', this._disableDelayedDrag);
_off(ownerDocument, 'pointermove', this._disableDelayedDrag);
}, },
_triggerDragStart: function (/** Touch */touch) { _triggerDragStart: function (/** Event */evt, /** Touch */touch) {
touch = touch || (evt.pointerType == 'touch' ? evt : null);
if (touch) { if (touch) {
// Touch device support // Touch device support
tapEvt = { tapEvt = {
@ -483,6 +500,8 @@
// Drag start event // Drag start event
_dispatchEvent(this, rootEl, 'start', dragEl, rootEl, oldIndex); _dispatchEvent(this, rootEl, 'start', dragEl, rootEl, oldIndex);
} else {
this._nulling();
} }
}, },
@ -604,9 +623,15 @@
this._offUpEvents(); this._offUpEvents();
if (activeGroup.checkPull(this, this, dragEl, evt) == 'clone') { if (activeGroup.checkPull(this, this, dragEl, evt)) {
cloneEl = _clone(dragEl); cloneEl = _clone(dragEl);
cloneEl.draggable = false;
cloneEl.style['will-change'] = '';
_css(cloneEl, 'display', 'none'); _css(cloneEl, 'display', 'none');
_toggleClass(cloneEl, this.options.chosenClass, false);
rootEl.insertBefore(cloneEl, dragEl); rootEl.insertBefore(cloneEl, dragEl);
_dispatchEvent(this, rootEl, 'clone', dragEl); _dispatchEvent(this, rootEl, 'clone', dragEl);
} }
@ -619,6 +644,8 @@
_on(document, 'touchmove', this._onTouchMove); _on(document, 'touchmove', this._onTouchMove);
_on(document, 'touchend', this._onDrop); _on(document, 'touchend', this._onDrop);
_on(document, 'touchcancel', this._onDrop); _on(document, 'touchcancel', this._onDrop);
_on(document, 'pointermove', this._onTouchMove);
_on(document, 'pointerup', this._onDrop);
} else { } else {
// Old brwoser // Old brwoser
_on(document, 'mousemove', this._onTouchMove); _on(document, 'mousemove', this._onTouchMove);
@ -657,12 +684,15 @@
moved = true; moved = true;
if (activeGroup && !options.disabled && if (activeSortable && !options.disabled &&
(isOwner (isOwner
? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list ? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list
: ( : (
putSortable === this || putSortable === this ||
activeGroup.checkPull(this, activeSortable, dragEl, evt) && group.checkPut(this, activeSortable, dragEl, evt) (
(activeSortable.lastPullMode = activeGroup.checkPull(this, activeSortable, dragEl, evt)) &&
group.checkPut(this, activeSortable, dragEl, evt)
)
) )
) && ) &&
(evt.rootEl === void 0 || evt.rootEl === this.el) // touch fallback (evt.rootEl === void 0 || evt.rootEl === this.el) // touch fallback
@ -813,8 +843,10 @@
var ownerDocument = this.el.ownerDocument; var ownerDocument = this.el.ownerDocument;
_off(document, 'touchmove', this._onTouchMove); _off(document, 'touchmove', this._onTouchMove);
_off(document, 'pointermove', this._onTouchMove);
_off(ownerDocument, 'mouseup', this._onDrop); _off(ownerDocument, 'mouseup', this._onDrop);
_off(ownerDocument, 'touchend', this._onDrop); _off(ownerDocument, 'touchend', this._onDrop);
_off(ownerDocument, 'pointerup', this._onDrop);
_off(ownerDocument, 'touchcancel', this._onDrop); _off(ownerDocument, 'touchcancel', this._onDrop);
}, },
@ -916,6 +948,7 @@
ghostEl = ghostEl =
nextEl = nextEl =
cloneEl = cloneEl =
lastDownEl =
scrollEl = scrollEl =
scrollParentEl = scrollParentEl =
@ -1047,6 +1080,7 @@
_off(el, 'mousedown', this._onTapStart); _off(el, 'mousedown', this._onTapStart);
_off(el, 'touchstart', this._onTapStart); _off(el, 'touchstart', this._onTapStart);
_off(el, 'pointerdown', this._onTapStart);
if (this.nativeDraggable) { if (this.nativeDraggable) {
_off(el, 'dragover', this); _off(el, 'dragover', this);
@ -1068,6 +1102,10 @@
function _cloneHide(sortable, state) { function _cloneHide(sortable, state) {
if (sortable.lastPullMode !== 'clone') {
state = true;
}
if (cloneEl && (cloneEl.state !== state)) { if (cloneEl && (cloneEl.state !== state)) {
_css(cloneEl, 'display', state ? 'none' : ''); _css(cloneEl, 'display', state ? 'none' : '');
@ -1119,12 +1157,12 @@
function _on(el, event, fn) { function _on(el, event, fn) {
el.addEventListener(event, fn, false); el.addEventListener(event, fn, captureMode);
} }
function _off(el, event, fn) { function _off(el, event, fn) {
el.removeEventListener(event, fn, false); el.removeEventListener(event, fn, captureMode);
} }
@ -1387,6 +1425,6 @@
// Export // Export
Sortable.version = '1.5.2'; Sortable.version = '1.5.0-rc1';
return Sortable; return Sortable;
}); });

4
Sortable.min.js vendored

File diff suppressed because one or more lines are too long

2
component.json

@ -1,7 +1,7 @@
{ {
"name": "Sortable", "name": "Sortable",
"main": "Sortable.js", "main": "Sortable.js",
"version": "1.5.2", "version": "1.5.0-rc1",
"homepage": "http://rubaxa.github.io/Sortable/", "homepage": "http://rubaxa.github.io/Sortable/",
"repo": "RubaXa/Sortable", "repo": "RubaXa/Sortable",
"authors": [ "authors": [

239
knockout-sortable.js

@ -1,239 +0,0 @@
/*global ko*/
(function (factory) {
"use strict";
//get ko ref via global or require
var koRef;
if (typeof ko !== 'undefined') {
//global ref already defined
koRef = ko;
}
else if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
//commonjs / node.js
koRef = require('knockout');
}
//get sortable ref via global or require
var sortableRef;
if (typeof Sortable !== 'undefined') {
//global ref already defined
sortableRef = Sortable;
}
else if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
//commonjs / node.js
sortableRef = require('sortablejs');
}
//use references if we found them
if (koRef !== undefined && sortableRef !== undefined) {
factory(koRef, sortableRef);
}
//if both references aren't found yet, get via AMD if available
else if (typeof define === 'function' && define.amd) {
//we may have a reference to only 1, or none
if (koRef !== undefined && sortableRef === undefined) {
define(['./Sortable'], function (amdSortableRef) {
factory(koRef, amdSortableRef);
});
}
else if (koRef === undefined && sortableRef !== undefined) {
define(['knockout'], function (amdKnockout) {
factory(amdKnockout, sortableRef);
});
}
else if (koRef === undefined && sortableRef === undefined) {
define(['knockout', './Sortable'], factory);
}
}
//no more routes to get references
else {
//report specific error
if (koRef !== undefined && sortableRef === undefined) {
throw new Error('knockout-sortable could not get reference to Sortable');
}
else if (koRef === undefined && sortableRef !== undefined) {
throw new Error('knockout-sortable could not get reference to Knockout');
}
else if (koRef === undefined && sortableRef === undefined) {
throw new Error('knockout-sortable could not get reference to Knockout or Sortable');
}
}
})(function (ko, Sortable) {
"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', 'onMove', 'onClone'].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]);
});
var sortableElement = Sortable.create(element, options);
// Destroy the sortable if knockout disposes the element it's connected to
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
sortableElement.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 "from" collection to the "to" 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),
newIndex = e.newIndex;
// We have to find out the actual desired index of the to array,
// as this might be a computed array. We could otherwise potentially
// drop an item above the 3rd visible item, but the 2nd visible item
// has an actual index of 5.
if (e.item.previousElementSibling) {
newIndex = to().indexOf(ko.dataFor(e.item.previousElementSibling)) + 1;
}
// 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) {
// Read the item
fromArray.splice(originalIndex, 0, itemVM);
// Force knockout to update
from.valueHasMutated();
}
// Force deferred tasks to run now, registering the removal
ko.tasks.runEarly();
// Insert the item on its new position
to().splice(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);
}
};
});

6
package.json

@ -1,7 +1,7 @@
{ {
"name": "sortablejs", "name": "sortablejs",
"exportName": "Sortable", "exportName": "Sortable",
"version": "1.5.2", "version": "1.5.0-rc1",
"devDependencies": { "devDependencies": {
"grunt": "*", "grunt": "*",
"grunt-version": "*", "grunt-version": "*",
@ -17,6 +17,10 @@
"type": "git", "type": "git",
"url": "git://github.com/rubaxa/Sortable.git" "url": "git://github.com/rubaxa/Sortable.git"
}, },
"files": [
"Sortable.js",
"Sortable.min.js"
],
"keywords": [ "keywords": [
"sortable", "sortable",
"reorder", "reorder",

Loading…
Cancel
Save