mirror of https://github.com/RubaXa/Sortable.git
RubaXa
9 years ago
5 changed files with 20 additions and 572 deletions
@ -1,210 +0,0 @@
|
||||
/** |
||||
* @author RubaXa <trash@rubaxa.org> |
||||
* @licence MIT |
||||
*/ |
||||
(function (factory) { |
||||
'use strict'; |
||||
|
||||
if (typeof define === 'function' && define.amd) { |
||||
define(['angular', './Sortable'], factory); |
||||
} |
||||
else if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') { |
||||
require('angular'); |
||||
factory(angular, require('./Sortable')); |
||||
module.exports = 'ng-sortable'; |
||||
} |
||||
else if (window.angular && window.Sortable) { |
||||
factory(angular, Sortable); |
||||
} |
||||
})(function (angular, Sortable) { |
||||
'use strict'; |
||||
|
||||
|
||||
/** |
||||
* @typedef {Object} ngSortEvent |
||||
* @property {*} model List item |
||||
* @property {Object|Array} models List of items |
||||
* @property {number} oldIndex before sort |
||||
* @property {number} newIndex after sort |
||||
*/ |
||||
|
||||
var expando = 'Sortable:ng-sortable'; |
||||
|
||||
angular.module('ng-sortable', []) |
||||
.constant('ngSortableVersion', '0.4.0') |
||||
.constant('ngSortableConfig', {}) |
||||
.directive('ngSortable', ['$parse', 'ngSortableConfig', function ($parse, ngSortableConfig) { |
||||
var removed, |
||||
nextSibling; |
||||
|
||||
function getNgRepeatExpression(node) { |
||||
return node.getAttribute('ng-repeat') || node.getAttribute('data-ng-repeat') || node.getAttribute('x-ng-repeat'); |
||||
} |
||||
|
||||
// Export
|
||||
return { |
||||
restrict: 'AC', |
||||
scope: { ngSortable: "=?" }, |
||||
priority: 1001, |
||||
compile: function ($element, $attr) { |
||||
|
||||
var ngRepeat = [].filter.call($element[0].childNodes, function (node) { |
||||
return node.nodeType === Node.ELEMENT_NODE && getNgRepeatExpression(node); |
||||
})[0]; |
||||
|
||||
if (!ngRepeat) { |
||||
return; |
||||
} |
||||
|
||||
var match = getNgRepeatExpression(ngRepeat) |
||||
.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/); |
||||
|
||||
if (!match) { |
||||
return; |
||||
} |
||||
|
||||
var rhs = match[2]; |
||||
|
||||
return function postLink(scope, $el) { |
||||
var itemsExpr = $parse(rhs); |
||||
var getSource = function getSource() { |
||||
return itemsExpr(scope.$parent) || []; |
||||
}; |
||||
|
||||
|
||||
var el = $el[0], |
||||
options = angular.extend(scope.ngSortable || {}, ngSortableConfig), |
||||
watchers = [], |
||||
offDestroy, |
||||
sortable |
||||
; |
||||
|
||||
el[expando] = getSource; |
||||
|
||||
function _emitEvent(/**Event*/evt, /*Mixed*/item) { |
||||
var name = 'on' + evt.type.charAt(0).toUpperCase() + evt.type.substr(1); |
||||
var source = getSource(); |
||||
|
||||
/* jshint expr:true */ |
||||
options[name] && options[name]({ |
||||
model: item || source[evt.newIndex], |
||||
models: source, |
||||
oldIndex: evt.oldIndex, |
||||
newIndex: evt.newIndex, |
||||
originalEvent: evt |
||||
}); |
||||
} |
||||
|
||||
|
||||
function _sync(/**Event*/evt) { |
||||
var items = getSource(); |
||||
|
||||
if (!items) { |
||||
// Without ng-repeat
|
||||
return; |
||||
} |
||||
|
||||
var oldIndex = evt.oldIndex, |
||||
newIndex = evt.newIndex; |
||||
|
||||
if (el !== evt.from) { |
||||
var prevItems = evt.from[expando](); |
||||
|
||||
removed = prevItems[oldIndex]; |
||||
|
||||
if (evt.clone) { |
||||
removed = angular.copy(removed); |
||||
prevItems.splice(Sortable.utils.index(evt.clone, sortable.options.draggable), 0, prevItems.splice(oldIndex, 1)[0]); |
||||
evt.from.removeChild(evt.clone); |
||||
} |
||||
else { |
||||
prevItems.splice(oldIndex, 1); |
||||
} |
||||
|
||||
items.splice(newIndex, 0, removed); |
||||
|
||||
evt.from.insertBefore(evt.item, nextSibling); // revert element
|
||||
} |
||||
else { |
||||
items.splice(newIndex, 0, items.splice(oldIndex, 1)[0]); |
||||
|
||||
// move ng-repeat comment node to right position
|
||||
if (nextSibling.nodeType === Node.COMMENT_NODE) { |
||||
evt.from.insertBefore(nextSibling, evt.item.nextSibling); |
||||
} |
||||
} |
||||
|
||||
scope.$apply(); |
||||
} |
||||
|
||||
function _destroy() { |
||||
offDestroy(); |
||||
|
||||
angular.forEach(watchers, function (/** Function */unwatch) { |
||||
unwatch(); |
||||
}); |
||||
|
||||
sortable.destroy(); |
||||
|
||||
el[expando] = null; |
||||
el = null; |
||||
watchers = null; |
||||
sortable = null; |
||||
nextSibling = null; |
||||
} |
||||
|
||||
|
||||
// Initialization
|
||||
sortable = Sortable.create(el, Object.keys(options).reduce(function (opts, name) { |
||||
opts[name] = opts[name] || options[name]; |
||||
return opts; |
||||
}, { |
||||
onStart: function (/**Event*/evt) { |
||||
nextSibling = evt.from === evt.item.parentNode ? evt.item.nextSibling : evt.clone.nextSibling; |
||||
_emitEvent(evt); |
||||
scope.$apply(); |
||||
}, |
||||
onEnd: function (/**Event*/evt) { |
||||
_emitEvent(evt, removed); |
||||
scope.$apply(); |
||||
}, |
||||
onAdd: function (/**Event*/evt) { |
||||
_sync(evt); |
||||
_emitEvent(evt, removed); |
||||
scope.$apply(); |
||||
}, |
||||
onUpdate: function (/**Event*/evt) { |
||||
_sync(evt); |
||||
_emitEvent(evt); |
||||
}, |
||||
onRemove: function (/**Event*/evt) { |
||||
_emitEvent(evt, removed); |
||||
}, |
||||
onSort: function (/**Event*/evt) { |
||||
_emitEvent(evt); |
||||
} |
||||
})); |
||||
|
||||
// Create watchers for `options`
|
||||
angular.forEach([ |
||||
'sort', 'disabled', 'draggable', 'handle', 'animation', 'group', 'ghostClass', 'filter', |
||||
'onStart', 'onEnd', 'onAdd', 'onUpdate', 'onRemove', 'onSort', 'onMove', 'onClone', 'setData' |
||||
], function (name) { |
||||
watchers.push(scope.$watch('ngSortable.' + name, function (value) { |
||||
if (value !== void 0) { |
||||
options[name] = value; |
||||
|
||||
if (!/^on[A-Z]/.test(name)) { |
||||
sortable.option(name, value); |
||||
} |
||||
} |
||||
})); |
||||
}); |
||||
|
||||
offDestroy = scope.$on('$destroy', _destroy); |
||||
|
||||
} |
||||
} |
||||
}; |
||||
}]); |
||||
}); |
@ -1,169 +0,0 @@
|
||||
/** |
||||
* @author RubaXa <trash@rubaxa.org> |
||||
* @licence MIT |
||||
*/ |
||||
|
||||
(function (factory) { |
||||
'use strict'; |
||||
|
||||
if (typeof module != 'undefined' && typeof module.exports != 'undefined') { |
||||
module.exports = factory(require('./Sortable')); |
||||
} |
||||
else if (typeof define === 'function' && define.amd) { |
||||
define(['./Sortable'], factory); |
||||
} |
||||
else { |
||||
/* jshint sub:true */ |
||||
window['SortableMixin'] = factory(Sortable); |
||||
} |
||||
})(function (/** Sortable */Sortable) { |
||||
'use strict'; |
||||
|
||||
var _nextSibling; |
||||
|
||||
var _activeComponent; |
||||
|
||||
var _defaultOptions = { |
||||
ref: 'list', |
||||
model: 'items', |
||||
|
||||
animation: 100, |
||||
onStart: 'handleStart', |
||||
onEnd: 'handleEnd', |
||||
onAdd: 'handleAdd', |
||||
onUpdate: 'handleUpdate', |
||||
onRemove: 'handleRemove', |
||||
onSort: 'handleSort', |
||||
onFilter: 'handleFilter', |
||||
onMove: 'handleMove', |
||||
onClone: 'handleClone' |
||||
}; |
||||
|
||||
|
||||
function _getModelName(component) { |
||||
return component.sortableOptions && component.sortableOptions.model || _defaultOptions.model; |
||||
} |
||||
|
||||
|
||||
function _getModelItems(component) { |
||||
var name = _getModelName(component), |
||||
items = component.state && component.state[name] || component.props[name]; |
||||
|
||||
return items.slice(); |
||||
} |
||||
|
||||
|
||||
function _extend(dst, src) { |
||||
for (var key in src) { |
||||
if (src.hasOwnProperty(key)) { |
||||
dst[key] = src[key]; |
||||
} |
||||
} |
||||
|
||||
return dst; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Simple and easy mixin-wrapper for rubaxa/Sortable library, in order to |
||||
* make reorderable drag-and-drop lists on modern browsers and touch devices. |
||||
* |
||||
* @mixin |
||||
*/ |
||||
var SortableMixin = { |
||||
sortableMixinVersion: '0.1.1', |
||||
|
||||
|
||||
/** |
||||
* @type {Sortable} |
||||
* @private |
||||
*/ |
||||
_sortableInstance: null, |
||||
|
||||
|
||||
componentDidMount: function () { |
||||
var DOMNode, options = _extend(_extend({}, _defaultOptions), this.sortableOptions || {}), |
||||
copyOptions = _extend({}, options), |
||||
|
||||
emitEvent = function (/** string */type, /** Event */evt) { |
||||
var method = options[type]; |
||||
if (method && typeof method === "string") { |
||||
method = this[method]; |
||||
} |
||||
method && typeof method === "function" && method.call(this, evt, this._sortableInstance); |
||||
}.bind(this); |
||||
|
||||
|
||||
// Bind callbacks so that "this" refers to the component
|
||||
'onStart onEnd onAdd onSort onUpdate onRemove onFilter onMove'.split(' ').forEach(function (/** string */name) { |
||||
copyOptions[name] = function (evt) { |
||||
if (name === 'onStart') { |
||||
_nextSibling = evt.item.nextElementSibling; |
||||
_activeComponent = this; |
||||
} |
||||
else if (name === 'onAdd' || name === 'onUpdate') { |
||||
evt.from.insertBefore(evt.item, _nextSibling); |
||||
|
||||
var newState = {}, |
||||
remoteState = {}, |
||||
oldIndex = evt.oldIndex, |
||||
newIndex = evt.newIndex, |
||||
items = _getModelItems(this), |
||||
remoteItems, |
||||
item; |
||||
|
||||
if (name === 'onAdd') { |
||||
remoteItems = _getModelItems(_activeComponent); |
||||
item = remoteItems.splice(oldIndex, 1)[0]; |
||||
items.splice(newIndex, 0, item); |
||||
|
||||
remoteState[_getModelName(_activeComponent)] = remoteItems; |
||||
} |
||||
else { |
||||
items.splice(newIndex, 0, items.splice(oldIndex, 1)[0]); |
||||
} |
||||
|
||||
newState[_getModelName(this)] = items; |
||||
|
||||
if (copyOptions.stateHandler) { |
||||
this[copyOptions.stateHandler](newState); |
||||
} else { |
||||
this.setState(newState); |
||||
} |
||||
|
||||
(this !== _activeComponent) && _activeComponent.setState(remoteState); |
||||
} |
||||
|
||||
setTimeout(function () { |
||||
emitEvent(name, evt); |
||||
}, 0); |
||||
}.bind(this); |
||||
}, this); |
||||
|
||||
DOMNode = typeof this.getDOMNode === 'function' ? (this.refs[options.ref] || this).getDOMNode() : this.refs[options.ref] || this; |
||||
|
||||
/** @namespace this.refs — http://facebook.github.io/react/docs/more-about-refs.html */ |
||||
this._sortableInstance = Sortable.create(DOMNode, copyOptions); |
||||
}, |
||||
|
||||
componentWillReceiveProps: function (nextProps) { |
||||
var newState = {}, |
||||
modelName = _getModelName(this), |
||||
items = nextProps[modelName]; |
||||
|
||||
if (items) { |
||||
newState[modelName] = items; |
||||
this.setState(newState); |
||||
} |
||||
}, |
||||
|
||||
componentWillUnmount: function () { |
||||
this._sortableInstance.destroy(); |
||||
this._sortableInstance = null; |
||||
} |
||||
}; |
||||
|
||||
|
||||
// Export
|
||||
return SortableMixin; |
||||
}); |
Loading…
Reference in new issue