Browse Source

Merge branch 'dev' into gh-pages

gh-pages
RubaXa 10 years ago
parent
commit
93d982fe46
  1. 25
      Gruntfile.js
  2. 44
      README.md
  3. 362
      Sortable.js
  4. 4
      Sortable.min.js
  5. 2
      bower.json
  6. 2
      component.json
  7. 161
      knockout-sortable.js
  8. 18
      ng-sortable.js
  9. 2
      package.json
  10. 32
      st/iframe/frame.html
  11. 49
      st/iframe/index.html

25
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');
}
});

44
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(<div>
---
<a name="ko"></a>
### Support KnockoutJS
Include [knockout-sortable.js](knockout-sortable.js)
```html
<div data-bind="sortable: {foreach: yourObservableArray, options: {/* sortable options here */}}">
<!-- optional item template here -->
</div>
<div data-bind="draggable: {foreach: yourObservableArray, options: {/* sortable options here */}}">
<!-- optional item template here -->
</div>
```
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
<!-- CDNJS :: Sortable (https://cdnjs.com/) -->
<script src="//cdnjs.cloudflare.com/ajax/libs/Sortable/1.1.1/Sortable.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/Sortable/1.2.0/Sortable.min.js"></script>
<!-- jsDelivr :: Sortable (http://www.jsdelivr.com/) -->
<script src="//cdn.jsdelivr.net/sortable/1.1.1/Sortable.min.js"></script>
<script src="//cdn.jsdelivr.net/sortable/1.2.0/Sortable.min.js"></script>
<!-- jsDelivr :: Sortable :: Latest (http://www.jsdelivr.com/) -->
@ -576,6 +613,7 @@ Now you can use `jquery.fn.sortable.js`:<br/>
$("#list").sortable("{method-name}", "foo", "bar"); // call an instance method with parameters
```
And `grunt jquery:mySortableFunc` → `jquery.fn.mySortableFunc.js`
---

362
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';
/**

4
Sortable.min.js vendored

File diff suppressed because one or more lines are too long

2
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 <ibnRubaXa@gmail.com>"

2
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": [

161
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);
}
};
})();

18
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);
}
}
});
});

2
package.json

@ -1,7 +1,7 @@
{
"name": "sortablejs",
"exportName": "Sortable",
"version": "1.1.1",
"version": "1.2.0",
"devDependencies": {
"grunt": "*",
"grunt-version": "*",

32
st/iframe/frame.html

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css"/>
<!-- List with handle -->
<div id="listWithHandle" class="list-group">
<div class="list-group-item">
<span class="badge">14</span>
<span class="glyphicon glyphicon-move" aria-hidden="true"></span>
Drag me by the handle
</div>
<div class="list-group-item">
<span class="badge">2</span>
<span class="glyphicon glyphicon-move" aria-hidden="true"></span>
You can also select text
</div>
<div class="list-group-item">
<span class="badge">1</span>
<span class="glyphicon glyphicon-move" aria-hidden="true"></span>
Best of both worlds!
</div>
</div>
</body>
</html>

49
st/iframe/index.html

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>IFrame playground</title>
</head>
<body>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css"/>
<!-- Latest Sortable -->
<script src="../../Sortable.js"></script>
<!-- Simple List -->
<div id="simpleList" class="list-group">
<div class="list-group-item">This is <a href="http://rubaxa.github.io/Sortable/">Sortable</a></div>
<div class="list-group-item">It works with Bootstrap...</div>
<div class="list-group-item">...out of the box.</div>
<div class="list-group-item">It has support for touch devices.</div>
<div class="list-group-item">Just drag some elements around.</div>
</div>
<script>
(function () {
Sortable.create(simpleList, {group: 'shared'});
var iframe = document.createElement('iframe');
iframe.src = 'frame.html';
iframe.width = '100%';
iframe.onload = function () {
var doc = iframe.contentDocument,
list = doc.getElementById('listWithHandle');
Sortable.create(list, {group: 'shared'});
};
document.body.appendChild(iframe);
})();
</script>
</body>
</html>
Loading…
Cancel
Save