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. 288
      Sortable.js
  4. 4
      Sortable.min.js
  5. 2
      bower.json
  6. 2
      component.json
  7. 161
      knockout-sortable.js
  8. 16
      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: { jquery: {
files: { files: {}
'jquery.fn.sortable.min.js': 'jquery.fn.sortable.js'
}
} }
}, },
@ -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'), var fs = require('fs'),
filename = 'jquery.fn.sortable.js'; filename = 'jquery.fn.' + exportName + '.js';
grunt.log.oklns(filename); grunt.log.oklns(filename);
fs.writeFileSync( fs.writeFileSync(
filename, filename,
(fs.readFileSync('jquery.binding.js') + '') (fs.readFileSync('jquery.binding.js') + '')
.replace('$.fn.sortable', '$.fn.' + exportName)
.replace('/* CODE */', .replace('/* CODE */',
(fs.readFileSync('Sortable.js') + '') (fs.readFileSync('Sortable.js') + '')
.replace(/^[\s\S]*?function[\s\S]*?(var[\s\S]+)\/\/\s+Export[\s\S]+/, '$1') .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'); 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) filter: ".ignore-elements", // Selectors that do not lead to dragging (String or Function)
draggable: ".item", // Specifies which items inside the element should be sortable draggable: ".item", // Specifies which items inside the element should be sortable
ghostClass: "sortable-ghost", // Class name for the drop placeholder ghostClass: "sortable-ghost", // Class name for the drop placeholder
dataIdAttr: 'data-id',
scroll: true, // or HTMLElement scroll: true, // or HTMLElement
scrollSensitivity: 30, // px, how near the mouse must be to an edge to start scrolling. 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 // Attempt to drag a filtered element
onFilter: function (/**Event*/evt) { onFilter: function (/**Event*/evt) {
var itemEl = evt.item; // HTMLElement receiving the `mousedown|tapstart` event. 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 ### Method
@ -399,7 +436,7 @@ For each element in the set, get the first element that matches the selector by
##### toArray():`String[]` ##### 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[]`) ##### sort(order:`String[]`)
@ -536,11 +573,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.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/) --> <!-- 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/) --> <!-- 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 $("#list").sortable("{method-name}", "foo", "bar"); // call an instance method with parameters
``` ```
And `grunt jquery:mySortableFunc` → `jquery.fn.mySortableFunc.js`
--- ---

288
Sortable.js

@ -45,6 +45,9 @@
tapEvt, tapEvt,
touchEvt, touchEvt,
/** @const */
RSPACE = /\s+/g,
expando = 'Sortable' + (new Date).getTime(), expando = 'Sortable' + (new Date).getTime(),
win = window, win = window,
@ -53,28 +56,8 @@
supportDraggable = !!('draggable' in document.createElement('div')), supportDraggable = !!('draggable' in document.createElement('div')),
_silent = false, _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, abs = Math.abs,
slice = [].slice, slice = [].slice,
@ -144,7 +127,7 @@
if (el) { if (el) {
autoScroll.pid = setInterval(function () { autoScroll.pid = setInterval(function () {
if (el === win) { if (el === win) {
win.scrollTo(win.scrollX + vx * speed, win.scrollY + vy * speed); win.scrollTo(win.pageXOffset + vx * speed, win.pageYOffset + vy * speed);
} else { } else {
vy && (el.scrollTop += vy * speed); vy && (el.scrollTop += vy * speed);
vx && (el.scrollLeft += vx * speed); vx && (el.scrollLeft += vx * speed);
@ -165,7 +148,11 @@
*/ */
function Sortable(el, options) { function Sortable(el, options) {
this.el = el; // root element this.el = el; // root element
this.options = options = (options || {}); this.options = options = _extend({}, options);
// Export instance
el[expando] = this;
// Default options // Default options
@ -187,7 +174,9 @@
dataTransfer.setData('Text', dragEl.textContent); dataTransfer.setData('Text', dragEl.textContent);
}, },
dropBubble: false, 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(' ') : '') + ' '; options.groups = ' ' + group.name + (group.put.join ? ' ' + group.put.join(' ') : '') + ' ';
el[expando] = options;
// Bind all private methods // Bind all private methods
@ -248,29 +228,17 @@
Sortable.prototype = /** @lends Sortable.prototype */ { Sortable.prototype = /** @lends Sortable.prototype */ {
constructor: Sortable, 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) { _onTapStart: function (/** Event|TouchEvent */evt) {
var type = evt.type, var _this = this,
el = this.el,
options = this.options,
type = evt.type,
touch = evt.touches && evt.touches[0], touch = evt.touches && evt.touches[0],
target = (touch || evt).target, target = (touch || evt).target,
originalTarget = target, originalTarget = target,
options = this.options,
el = this.el,
filter = options.filter; filter = options.filter;
if (type === 'mousedown' && evt.button !== 0 || options.disabled) { if (type === 'mousedown' && evt.button !== 0 || options.disabled) {
return; // only left button or enabled return; // only left button or enabled
} }
@ -287,7 +255,7 @@
// Check filter // Check filter
if (typeof filter === 'function') { if (typeof filter === 'function') {
if (filter.call(this, evt, target, this)) { if (filter.call(this, evt, target, this)) {
_dispatchEvent(originalTarget, 'filter', target, el, oldIndex); _dispatchEvent(_this, originalTarget, 'filter', target, el, oldIndex);
evt.preventDefault(); evt.preventDefault();
return; // cancel dnd return; // cancel dnd
} }
@ -297,7 +265,7 @@
criteria = _closest(originalTarget, criteria.trim(), el); criteria = _closest(originalTarget, criteria.trim(), el);
if (criteria) { if (criteria) {
_dispatchEvent(criteria, 'filter', target, el, oldIndex); _dispatchEvent(_this, criteria, 'filter', target, el, oldIndex);
return true; return true;
} }
}); });
@ -315,42 +283,84 @@
// Prepare `dragstart` // 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)) { if (target && !dragEl && (target.parentNode === el)) {
tapEvt = evt; tapEvt = evt;
rootEl = this.el; rootEl = el;
dragEl = target; dragEl = target;
nextEl = dragEl.nextSibling; nextEl = dragEl.nextSibling;
activeGroup = this.options.group; activeGroup = options.group;
dragStartFn = function () {
// Delayed drag has been triggered
// we can re-enable the events: touchmove/mousemove
_this._disableDelayedDrag();
// Make the element draggable
dragEl.draggable = true; dragEl.draggable = true;
// Disable "draggable" // Disable "draggable"
options.ignore.split(',').forEach(function (criteria) { options.ignore.split(',').forEach(function (criteria) {
_find(target, criteria.trim(), _disableDraggable); _find(dragEl, criteria.trim(), _disableDraggable);
}); });
// 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();
}
}
},
_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) { if (touch) {
// Touch device support // Touch device support
tapEvt = { tapEvt = {
target: target, target: dragEl,
clientX: touch.clientX, clientX: touch.clientX,
clientY: touch.clientY clientY: touch.clientY
}; };
this._onDragStart(tapEvt, 'touch'); this._onDragStart(tapEvt, 'touch');
evt.preventDefault();
} }
else if (!supportDraggable) {
_on(document, 'mouseup', this._onDrop); this._onDragStart(tapEvt, true);
_on(document, 'touchend', this._onDrop); }
_on(document, 'touchcancel', this._onDrop); else {
_on(dragEl, 'dragend', this); _on(dragEl, 'dragend', this);
_on(rootEl, 'dragstart', this._onDragStart); _on(rootEl, 'dragstart', this._onDragStart);
if (!supportDraggable) {
this._onDragStart(tapEvt, true);
} }
try { try {
@ -361,6 +371,17 @@
} }
} catch (err) { } 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) { if (parent) {
do { do {
if (parent[expando] && parent[expando].groups.indexOf(groupName) > -1) { if (parent[expando] && parent[expando].options.groups.indexOf(groupName) > -1) {
while (i--) { while (i--) {
touchDragOverListeners[i]({ touchDragOverListeners[i]({
clientX: touchEvt.clientX, clientX: touchEvt.clientX,
@ -488,10 +509,6 @@
isOwner = (activeGroup === group), isOwner = (activeGroup === group),
canSort = options.sort; canSort = options.sort;
if (!dragEl) {
return;
}
if (evt.preventDefault !== void 0) { if (evt.preventDefault !== void 0) {
evt.preventDefault(); evt.preventDefault();
!options.dragoverBubble && evt.stopPropagation(); !options.dragoverBubble && evt.stopPropagation();
@ -499,13 +516,13 @@
if (activeGroup && !options.disabled && if (activeGroup && !options.disabled &&
(isOwner (isOwner
? canSort || (revert = !rootEl.contains(dragEl)) ? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list
: activeGroup.pull && groupPut && ( : activeGroup.pull && groupPut && (
(activeGroup.name === group.name) || // by Name (activeGroup.name === group.name) || // by Name
(groupPut.indexOf && ~groupPut.indexOf(activeGroup.name)) // by Array (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 // Smart auto-scrolling
_autoScroll(evt, options, this.el); _autoScroll(evt, options, this.el);
@ -544,10 +561,12 @@
_cloneHide(isOwner); _cloneHide(isOwner);
if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect) !== false) {
el.appendChild(dragEl); el.appendChild(dragEl);
this._animate(dragRect, dragEl); this._animate(dragRect, dragEl);
target && this._animate(targetRect, target); target && this._animate(targetRect, target);
} }
}
else if (target && !target.animated && target !== dragEl && (target.parentNode[expando] !== void 0)) { else if (target && !target.animated && target !== dragEl && (target.parentNode[expando] !== void 0)) {
if (lastEl !== target) { if (lastEl !== target) {
lastEl = target; lastEl = target;
@ -563,15 +582,20 @@
isLong = (target.offsetHeight > dragEl.offsetHeight), isLong = (target.offsetHeight > dragEl.offsetHeight),
halfway = (floating ? (evt.clientX - targetRect.left) / width : (evt.clientY - targetRect.top) / height) > 0.5, halfway = (floating ? (evt.clientX - targetRect.left) / width : (evt.clientY - targetRect.top) / height) > 0.5,
nextSibling = target.nextElementSibling, nextSibling = target.nextElementSibling,
moveVector = _onMove(rootEl, el, dragEl, dragRect, target, targetRect),
after after
; ;
if (moveVector !== false) {
_silent = true; _silent = true;
setTimeout(_unsilent, 30); setTimeout(_unsilent, 30);
_cloneHide(isOwner); _cloneHide(isOwner);
if (floating) { if (moveVector === 1 || moveVector === -1) {
after = (moveVector === 1);
}
else if (floating) {
after = (target.previousElementSibling === dragEl) && !isWide || halfway && isWide; after = (target.previousElementSibling === dragEl) && !isWide || halfway && isWide;
} else { } else {
after = (nextSibling !== dragEl) && !isLong || halfway && isLong; after = (nextSibling !== dragEl) && !isLong || halfway && isLong;
@ -587,6 +611,7 @@
this._animate(targetRect, target); this._animate(targetRect, target);
} }
} }
}
}, },
_animate: function (prevRect, target) { _animate: function (prevRect, target) {
@ -616,10 +641,12 @@
}, },
_offUpEvents: function () { _offUpEvents: function () {
_off(document, 'mouseup', this._onDrop); var ownerDocument = this.el.ownerDocument;
_off(document, 'touchmove', this._onTouchMove); _off(document, 'touchmove', this._onTouchMove);
_off(document, 'touchend', this._onDrop); _off(ownerDocument, 'mouseup', this._onDrop);
_off(document, 'touchcancel', this._onDrop); _off(ownerDocument, 'touchend', this._onDrop);
_off(ownerDocument, 'touchcancel', this._onDrop);
}, },
_onDrop: function (/**Event*/evt) { _onDrop: function (/**Event*/evt) {
@ -629,6 +656,8 @@
clearInterval(this._loopId); clearInterval(this._loopId);
clearInterval(autoScroll.pid); clearInterval(autoScroll.pid);
clearTimeout(this.dragStartTimer);
// Unbind events // Unbind events
_off(document, 'drop', this); _off(document, 'drop', this);
_off(document, 'mousemove', this._onTouchMove); _off(document, 'mousemove', this._onTouchMove);
@ -652,14 +681,14 @@
newIndex = _index(dragEl); newIndex = _index(dragEl);
// drag from one list and drop into another // drag from one list and drop into another
_dispatchEvent(dragEl.parentNode, 'sort', dragEl, rootEl, oldIndex, newIndex); _dispatchEvent(null, dragEl.parentNode, 'sort', dragEl, rootEl, oldIndex, newIndex);
_dispatchEvent(rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex); _dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
// Add event // Add event
_dispatchEvent(dragEl, 'add', dragEl, rootEl, oldIndex, newIndex); _dispatchEvent(null, dragEl.parentNode, 'add', dragEl, rootEl, oldIndex, newIndex);
// Remove event // Remove event
_dispatchEvent(rootEl, 'remove', dragEl, rootEl, oldIndex, newIndex); _dispatchEvent(this, rootEl, 'remove', dragEl, rootEl, oldIndex, newIndex);
} }
else { else {
// Remove clone // Remove clone
@ -670,13 +699,18 @@
newIndex = _index(dragEl); newIndex = _index(dragEl);
// drag & drop within the same list // drag & drop within the same list
_dispatchEvent(rootEl, 'update', dragEl, rootEl, oldIndex, newIndex); _dispatchEvent(this, rootEl, 'update', dragEl, rootEl, oldIndex, newIndex);
_dispatchEvent(rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex); _dispatchEvent(this, rootEl, 'sort', dragEl, rootEl, oldIndex, newIndex);
} }
} }
if (Sortable.active) {
// Drag end event // Drag end event
Sortable.active && _dispatchEvent(rootEl, 'end', dragEl, rootEl, oldIndex, newIndex); _dispatchEvent(this, rootEl, 'end', dragEl, rootEl, oldIndex, newIndex);
// Save sorting
this.save();
}
} }
// Nulling // Nulling
@ -697,9 +731,6 @@
activeGroup = activeGroup =
Sortable.active = null; Sortable.active = null;
// Save sorting
this.save();
} }
}, },
@ -708,9 +739,11 @@
var type = evt.type; var type = evt.type;
if (type === 'dragover' || type === 'dragenter') { if (type === 'dragover' || type === 'dragenter') {
if (dragEl) {
this._onDragOver(evt); this._onDragOver(evt);
_globalDragOver(evt); _globalDragOver(evt);
} }
}
else if (type === 'drop' || type === 'dragend') { else if (type === 'drop' || type === 'dragend') {
this._onDrop(evt); this._onDrop(evt);
} }
@ -726,12 +759,13 @@
el, el,
children = this.el.children, children = this.el.children,
i = 0, i = 0,
n = children.length; n = children.length,
options = this.options;
for (; i < n; i++) { for (; i < n; i++) {
el = children[i]; el = children[i];
if (_closest(el, this.options.draggable, this.el)) { if (_closest(el, options.draggable, this.el)) {
order.push(el.getAttribute('data-id') || _generateId(el)); order.push(el.getAttribute(options.dataIdAttr) || _generateId(el));
} }
} }
@ -804,11 +838,9 @@
* Destroy * Destroy
*/ */
destroy: function () { destroy: function () {
var el = this.el, options = this.options; var el = this.el;
_customEvents.forEach(function (name) { el[expando] = null;
_off(el, name.substr(2).toLowerCase(), options[name]);
});
_off(el, 'mousedown', this._onTapStart); _off(el, 'mousedown', this._onTapStart);
_off(el, 'touchstart', this._onTapStart); _off(el, 'touchstart', this._onTapStart);
@ -816,7 +848,7 @@
_off(el, 'dragover', this); _off(el, 'dragover', this);
_off(el, 'dragenter', this); _off(el, 'dragenter', this);
//remove draggable attributes // Remove draggable attributes
Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function (el) { Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function (el) {
el.removeAttribute('draggable'); el.removeAttribute('draggable');
}); });
@ -825,7 +857,7 @@
this._onDrop(); this._onDrop();
this.el = null; this.el = el = null;
} }
}; };
@ -894,8 +926,8 @@
el.classList[state ? 'add' : 'remove'](name); el.classList[state ? 'add' : 'remove'](name);
} }
else { else {
var className = (' ' + el.className + ' ').replace(/\s+/g, ' ').replace(' ' + name + ' ', ''); var className = (' ' + el.className + ' ').replace(RSPACE, ' ').replace(' ' + name + ' ', ' ');
el.className = className + (state ? ' ' + 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) { function _disableDraggable(el) {
el.draggable = false; 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 // Export utils
Sortable.utils = { Sortable.utils = {
@ -1026,15 +1118,15 @@
is: function (el, selector) { is: function (el, selector) {
return !!_closest(el, selector, el); return !!_closest(el, selector, el);
}, },
extend: _extend,
throttle: _throttle, throttle: _throttle,
closest: _closest, closest: _closest,
toggleClass: _toggleClass, toggleClass: _toggleClass,
dispatchEvent: _dispatchEvent,
index: _index 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", "name": "Sortable",
"main": "Sortable.js", "main": "Sortable.js",
"version": "1.1.1", "version": "1.2.0",
"homepage": "http://rubaxa.github.io/Sortable/", "homepage": "http://rubaxa.github.io/Sortable/",
"authors": [ "authors": [
"RubaXa <ibnRubaXa@gmail.com>" "RubaXa <ibnRubaXa@gmail.com>"

2
component.json

@ -1,7 +1,7 @@
{ {
"name": "Sortable", "name": "Sortable",
"main": "Sortable.js", "main": "Sortable.js",
"version": "1.1.1", "version": "1.2.0",
"homepage": "http://rubaxa.github.io/Sortable/", "homepage": "http://rubaxa.github.io/Sortable/",
"repo": "RubaXa/Sortable", "repo": "RubaXa/Sortable",
"authors": [ "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);
}
};
})();

16
ng-sortable.js

@ -25,7 +25,7 @@
angular.module('ng-sortable', []) angular.module('ng-sortable', [])
.constant('$version', '0.3.5') .constant('version', '0.3.7')
.directive('ngSortable', ['$parse', function ($parse) { .directive('ngSortable', ['$parse', function ($parse) {
var removed, var removed,
nextSibling; nextSibling;
@ -78,7 +78,7 @@
/* jshint expr:true */ /* jshint expr:true */
options[name] && options[name]({ options[name] && options[name]({
model: item, model: item || source && source.item(evt.item),
models: source && source.items(), models: source && source.items(),
oldIndex: evt.oldIndex, oldIndex: evt.oldIndex,
newIndex: evt.newIndex newIndex: evt.newIndex
@ -143,13 +143,13 @@
}, },
onUpdate: function (/**Event*/evt) { onUpdate: function (/**Event*/evt) {
_sync(evt); _sync(evt);
_emitEvent(evt, source && source.item(evt.item)); _emitEvent(evt);
}, },
onRemove: function (/**Event*/evt) { onRemove: function (/**Event*/evt) {
_emitEvent(evt, removed); _emitEvent(evt, removed);
}, },
onSort: function (/**Event*/evt) { onSort: function (/**Event*/evt) {
_emitEvent(evt, source && source.item(evt.item)); _emitEvent(evt);
} }
})); }));
@ -160,12 +160,18 @@
}); });
if (ngSortable && !/{|}/.test(ngSortable)) { // todo: ugly 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) { scope.$watch(ngSortable + '.' + name, function (value) {
if (value !== void 0) { if (value !== void 0) {
options[name] = value; options[name] = value;
if (!/^on[A-Z]/.test(name)) {
sortable.option(name, value); sortable.option(name, value);
} }
}
}); });
}); });
} }

2
package.json

@ -1,7 +1,7 @@
{ {
"name": "sortablejs", "name": "sortablejs",
"exportName": "Sortable", "exportName": "Sortable",
"version": "1.1.1", "version": "1.2.0",
"devDependencies": { "devDependencies": {
"grunt": "*", "grunt": "*",
"grunt-version": "*", "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