diff --git a/ng-sortable.js b/ng-sortable.js index 5f41940..cbe1189 100644 --- a/ng-sortable.js +++ b/ng-sortable.js @@ -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); + + } } }; }]);