|
|
|
@ -35,169 +35,174 @@
|
|
|
|
|
.constant('ngSortableConfig', {}) |
|
|
|
|
.directive('ngSortable', ['$parse', 'ngSortableConfig', function ($parse, ngSortableConfig) { |
|
|
|
|
var removed, |
|
|
|
|
nextSibling, |
|
|
|
|
getSourceFactory = function getSourceFactory(el, scope) { |
|
|
|
|
var ngRepeat = [].filter.call(el.childNodes, function (node) { |
|
|
|
|
nextSibling; |
|
|
|
|
|
|
|
|
|
// Export
|
|
|
|
|
return { |
|
|
|
|
restrict: 'AC', |
|
|
|
|
scope: { ngSortable: "=?" }, |
|
|
|
|
priority: 1001, |
|
|
|
|
compile: function ($element, $attr) { |
|
|
|
|
|
|
|
|
|
var ngRepeat = [].filter.call($element[0].childNodes, function (node) { |
|
|
|
|
return ( |
|
|
|
|
(node.nodeType === 8) && |
|
|
|
|
(node.nodeValue.indexOf('ngRepeat:') !== -1) |
|
|
|
|
(node.nodeType === 1) && |
|
|
|
|
(node.attributes['ng-repeat']) |
|
|
|
|
); |
|
|
|
|
})[0]; |
|
|
|
|
|
|
|
|
|
if (!ngRepeat) { |
|
|
|
|
// Without ng-repeat
|
|
|
|
|
return function () { |
|
|
|
|
return null; |
|
|
|
|
}; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// tests: http://jsbin.com/kosubutilo/1/edit?js,output
|
|
|
|
|
ngRepeat = ngRepeat.nodeValue.match(/ngRepeat:\s*(?:\(.*?,\s*)?([^\s)]+)[\s)]+in\s+([^\s|]+)/); |
|
|
|
|
var expression = ngRepeat.attributes['ng-repeat'].nodeValue; |
|
|
|
|
var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/); |
|
|
|
|
|
|
|
|
|
var itemsExpr = $parse(ngRepeat[2]); |
|
|
|
|
if (!match) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return function () { |
|
|
|
|
return itemsExpr(scope.$parent) || []; |
|
|
|
|
}; |
|
|
|
|
}; |
|
|
|
|
var rhs = match[2]; |
|
|
|
|
|
|
|
|
|
return function postLink(scope, $el) { |
|
|
|
|
var itemsExpr = $parse(rhs); |
|
|
|
|
var getSource = function getSource() { |
|
|
|
|
return itemsExpr(scope.$parent) || []; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// Export
|
|
|
|
|
return { |
|
|
|
|
restrict: 'AC', |
|
|
|
|
scope: { ngSortable: "=?" }, |
|
|
|
|
link: function (scope, $el) { |
|
|
|
|
var el = $el[0], |
|
|
|
|
options = angular.extend(scope.ngSortable || {}, ngSortableConfig), |
|
|
|
|
watchers = [], |
|
|
|
|
getSource = getSourceFactory(el, scope), |
|
|
|
|
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 |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var el = $el[0], |
|
|
|
|
options = angular.extend(scope.ngSortable || {}, ngSortableConfig), |
|
|
|
|
watchers = [], |
|
|
|
|
offDestroy, |
|
|
|
|
sortable |
|
|
|
|
; |
|
|
|
|
|
|
|
|
|
function _sync(/**Event*/evt) { |
|
|
|
|
var items = getSource(); |
|
|
|
|
el[expando] = getSource; |
|
|
|
|
|
|
|
|
|
if (!items) { |
|
|
|
|
// Without ng-repeat
|
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
function _emitEvent(/**Event*/evt, /*Mixed*/item) { |
|
|
|
|
var name = 'on' + evt.type.charAt(0).toUpperCase() + evt.type.substr(1); |
|
|
|
|
var source = getSource(); |
|
|
|
|
|
|
|
|
|
var oldIndex = evt.oldIndex, |
|
|
|
|
newIndex = evt.newIndex; |
|
|
|
|
/* jshint expr:true */ |
|
|
|
|
options[name] && options[name]({ |
|
|
|
|
model: item || source[evt.newIndex], |
|
|
|
|
models: source, |
|
|
|
|
oldIndex: evt.oldIndex, |
|
|
|
|
newIndex: evt.newIndex, |
|
|
|
|
originalEvent: evt |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (el !== evt.from) { |
|
|
|
|
var prevItems = evt.from[expando](); |
|
|
|
|
|
|
|
|
|
removed = prevItems[oldIndex]; |
|
|
|
|
function _sync(/**Event*/evt) { |
|
|
|
|
var items = getSource(); |
|
|
|
|
|
|
|
|
|
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); |
|
|
|
|
if (!items) { |
|
|
|
|
// Without ng-repeat
|
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
items.splice(newIndex, 0, removed); |
|
|
|
|
var oldIndex = evt.oldIndex, |
|
|
|
|
newIndex = evt.newIndex; |
|
|
|
|
|
|
|
|
|
evt.from.insertBefore(evt.item, nextSibling); // revert element
|
|
|
|
|
} |
|
|
|
|
else { |
|
|
|
|
items.splice(newIndex, 0, items.splice(oldIndex, 1)[0]); |
|
|
|
|
if (el !== evt.from) { |
|
|
|
|
var prevItems = evt.from[expando](); |
|
|
|
|
|
|
|
|
|
// move ng-repeat comment node to right position
|
|
|
|
|
if (nextSibling.nodeType === Node.COMMENT_NODE) { |
|
|
|
|
evt.from.insertBefore(nextSibling, evt.item.nextSibling); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
removed = prevItems[oldIndex]; |
|
|
|
|
|
|
|
|
|
scope.$apply(); |
|
|
|
|
} |
|
|
|
|
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); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function _destroy() { |
|
|
|
|
offDestroy(); |
|
|
|
|
items.splice(newIndex, 0, removed); |
|
|
|
|
|
|
|
|
|
angular.forEach(watchers, function (/** Function */unwatch) { |
|
|
|
|
unwatch(); |
|
|
|
|
}); |
|
|
|
|
evt.from.insertBefore(evt.item, nextSibling); // revert element
|
|
|
|
|
} |
|
|
|
|
else { |
|
|
|
|
items.splice(newIndex, 0, items.splice(oldIndex, 1)[0]); |
|
|
|
|
|
|
|
|
|
sortable.destroy(); |
|
|
|
|
// move ng-repeat comment node to right position
|
|
|
|
|
if (nextSibling.nodeType === Node.COMMENT_NODE) { |
|
|
|
|
evt.from.insertBefore(nextSibling, evt.item.nextSibling); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
el[expando] = null; |
|
|
|
|
el = null; |
|
|
|
|
watchers = null; |
|
|
|
|
sortable = null; |
|
|
|
|
nextSibling = null; |
|
|
|
|
} |
|
|
|
|
scope.$apply(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function _destroy() { |
|
|
|
|
offDestroy(); |
|
|
|
|
|
|
|
|
|
// 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); |
|
|
|
|
angular.forEach(watchers, function (/** Function */unwatch) { |
|
|
|
|
unwatch(); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
sortable.destroy(); |
|
|
|
|
|
|
|
|
|
el[expando] = null; |
|
|
|
|
el = null; |
|
|
|
|
watchers = null; |
|
|
|
|
sortable = null; |
|
|
|
|
nextSibling = null; |
|
|
|
|
} |
|
|
|
|
})); |
|
|
|
|
|
|
|
|
|
// 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); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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); |
|
|
|
|
} |
|
|
|
|
})); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
offDestroy = scope.$on('$destroy', _destroy); |
|
|
|
|
// 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); |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
}]); |
|
|
|
|