diff --git a/src/sortable.js b/src/sortable.js index 3353f9d..7eb5e39 100644 --- a/src/sortable.js +++ b/src/sortable.js @@ -33,6 +33,10 @@ angular.module('ui.sortable', []) return (/left|right/).test(item.css('float')) || (/inline|table-cell/).test(item.css('display')); } + function afterStop(e, ui) { + ui.item.sortable._destroy(); + } + var opts = {}; // directive specific options @@ -84,7 +88,10 @@ angular.module('ui.sortable', []) // Save the starting position of dragged item ui.item.sortable = { + model: ngModel.$modelValue[ui.item.index()], index: ui.item.index(), + source: ui.item.parent(), + sourceModel: ngModel.$modelValue, cancel: function () { ui.item.sortable._isCanceled = true; }, @@ -95,7 +102,12 @@ angular.module('ui.sortable', []) return !!ui.item.sortable._isCustomHelperUsed; }, _isCanceled: false, - _isCustomHelperUsed: ui.item.sortable._isCustomHelperUsed + _isCustomHelperUsed: ui.item.sortable._isCustomHelperUsed, + _destroy: function () { + angular.forEach(ui.item.sortable, function(value, key) { + ui.item.sortable[key] = undefined; + }); + } }; }; @@ -136,7 +148,9 @@ angular.module('ui.sortable', []) // the value will be overwritten with the old value if(!ui.item.sortable.received) { ui.item.sortable.dropindex = ui.item.index(); - ui.item.sortable.droptarget = ui.item.parent(); + var droptarget = ui.item.parent(); + ui.item.sortable.droptarget = droptarget; + ui.item.sortable.droptargetModel = droptarget.scope().$eval(droptarget.attr('ng-model')); // Cancel the sort (let ng-repeat do the sort for us) // Don't cancel if this is the received list because it has @@ -262,6 +276,8 @@ angular.module('ui.sortable', []) // call apply after stop value = combineCallbacks( value, function() { scope.$apply(); }); + + value = combineCallbacks(value, afterStop); } // wrap the callback value = combineCallbacks(callbacks[key], value); @@ -277,6 +293,9 @@ angular.module('ui.sortable', []) angular.forEach(callbacks, function(value, key) { opts[key] = combineCallbacks(value, opts[key]); + if( key === 'stop' ){ + opts[key] = combineCallbacks(opts[key], afterStop); + } }); } else { diff --git a/test/sortable.e2e.callbacks.spec.js b/test/sortable.e2e.callbacks.spec.js index 3902792..5be87d7 100644 --- a/test/sortable.e2e.callbacks.spec.js +++ b/test/sortable.e2e.callbacks.spec.js @@ -6,11 +6,12 @@ describe('uiSortable', function() { beforeEach(module('ui.sortable')); beforeEach(module('ui.sortable.testHelper')); - var EXTRA_DY_PERCENTAGE, listContent; + var EXTRA_DY_PERCENTAGE, listContent, hasUndefinedProperties; beforeEach(inject(function (sortableTestHelper) { EXTRA_DY_PERCENTAGE = sortableTestHelper.EXTRA_DY_PERCENTAGE; listContent = sortableTestHelper.listContent; + hasUndefinedProperties = sortableTestHelper.hasUndefinedProperties; })); describe('Callbacks related', function() { @@ -188,6 +189,133 @@ describe('uiSortable', function() { }); }); + it('should properly set ui.item.sortable properties', function() { + inject(function($compile, $rootScope) { + var element, updateCallbackExpectations; + element = $compile('')($rootScope); + $rootScope.$apply(function() { + $rootScope.opts = { + update: function(e, ui) { + if (ui.item.scope().item === 'Two') { + ui.item.sortable.cancel(); + } + updateCallbackExpectations(ui.item.sortable); + } + }; + $rootScope.items = ['One', 'Two', 'Three']; + }); + + host.append(element); + + $rootScope.$apply(function() { + }); + var li = element.find(':eq(1)'); + var dy = (1 + EXTRA_DY_PERCENTAGE) * li.outerHeight(); + updateCallbackExpectations = function(uiItemSortable) { + expect(uiItemSortable.model).toEqual('Two'); + expect(uiItemSortable.index).toEqual(1); + expect(uiItemSortable.source.length).toEqual(1); + expect(uiItemSortable.source[0]).toBe(host.children()[0]); + expect(uiItemSortable.sourceModel).toBe($rootScope.items); + expect(uiItemSortable.isCanceled()).toBe(true); + expect(uiItemSortable.isCustomHelperUsed()).toBe(false); + }; + li.simulate('drag', { dy: dy }); + expect($rootScope.items).toEqual(['One', 'Two', 'Three']); + expect($rootScope.items).toEqual(listContent(element)); + updateCallbackExpectations = undefined; + + li = element.find(':eq(0)'); + dy = (2 + EXTRA_DY_PERCENTAGE) * li.outerHeight(); + updateCallbackExpectations = function(uiItemSortable) { + expect(uiItemSortable.model).toEqual('One'); + expect(uiItemSortable.index).toEqual(0); + expect(uiItemSortable.source.length).toEqual(1); + expect(uiItemSortable.source[0]).toBe(host.children()[0]); + expect(uiItemSortable.sourceModel).toBe($rootScope.items); + expect(uiItemSortable.isCanceled()).toBe(false); + expect(uiItemSortable.isCustomHelperUsed()).toBe(false); + }; + li.simulate('drag', { dy: dy }); + expect($rootScope.items).toEqual(['Two', 'Three', 'One']); + expect($rootScope.items).toEqual(listContent(element)); + updateCallbackExpectations = undefined; + + li = element.find(':eq(2)'); + dy = -(2 + EXTRA_DY_PERCENTAGE) * li.outerHeight(); + updateCallbackExpectations = function(uiItemSortable) { + expect(uiItemSortable.model).toEqual('One'); + expect(uiItemSortable.index).toEqual(2); + expect(uiItemSortable.source.length).toEqual(1); + expect(uiItemSortable.source[0]).toBe(host.children()[0]); + expect(uiItemSortable.sourceModel).toBe($rootScope.items); + expect(uiItemSortable.isCanceled()).toBe(false); + expect(uiItemSortable.isCustomHelperUsed()).toBe(false); + }; + li.simulate('drag', { dy: dy }); + expect($rootScope.items).toEqual(['One', 'Two', 'Three']); + expect($rootScope.items).toEqual(listContent(element)); + updateCallbackExpectations = undefined; + + $(element).remove(); + }); + }); + + it('should properly free ui.item.sortable object', function() { + inject(function($compile, $rootScope) { + var element, uiItem, uiItemSortable_Destroy; + element = $compile('')($rootScope); + $rootScope.$apply(function() { + $rootScope.opts = { + start: function (e, ui) { + uiItem = ui.item; + spyOn(ui.item.sortable, '_destroy').andCallThrough(); + uiItemSortable_Destroy = ui.item.sortable._destroy; + }, + update: function(e, ui) { + if (ui.item.scope().item === 'Two') { + ui.item.sortable.cancel(); + } + } + }; + $rootScope.items = ['One', 'Two', 'Three']; + }); + + host.append(element); + + var li = element.find(':eq(1)'); + var dy = (1 + EXTRA_DY_PERCENTAGE) * li.outerHeight(); + li.simulate('drag', { dy: dy }); + expect($rootScope.items).toEqual(['One', 'Two', 'Three']); + expect($rootScope.items).toEqual(listContent(element)); + expect(uiItemSortable_Destroy).toHaveBeenCalled(); + expect(hasUndefinedProperties(uiItem.sortable)).toBe(true); + uiItem = uiItemSortable_Destroy = undefined; + + + li = element.find(':eq(0)'); + dy = (2 + EXTRA_DY_PERCENTAGE) * li.outerHeight(); + li.simulate('drag', { dy: dy }); + expect($rootScope.items).toEqual(['Two', 'Three', 'One']); + expect($rootScope.items).toEqual(listContent(element)); + expect(uiItemSortable_Destroy).toHaveBeenCalled(); + expect(hasUndefinedProperties(uiItem.sortable)).toBe(true); + uiItem = uiItemSortable_Destroy = undefined; + + + li = element.find(':eq(2)'); + dy = -(2 + EXTRA_DY_PERCENTAGE) * li.outerHeight(); + li.simulate('drag', { dy: dy }); + expect($rootScope.items).toEqual(['One', 'Two', 'Three']); + expect($rootScope.items).toEqual(listContent(element)); + expect(uiItemSortable_Destroy).toHaveBeenCalled(); + expect(hasUndefinedProperties(uiItem.sortable)).toBe(true); + uiItem = uiItemSortable_Destroy = undefined; + + $(element).remove(); + }); + }); + }); }); \ No newline at end of file diff --git a/test/sortable.e2e.multi.spec.js b/test/sortable.e2e.multi.spec.js index 860d349..a9cebae 100644 --- a/test/sortable.e2e.multi.spec.js +++ b/test/sortable.e2e.multi.spec.js @@ -6,13 +6,14 @@ describe('uiSortable', function() { beforeEach(module('ui.sortable')); beforeEach(module('ui.sortable.testHelper')); - var EXTRA_DY_PERCENTAGE, listContent, listInnerContent, simulateElementDrag; + var EXTRA_DY_PERCENTAGE, listContent, listInnerContent, simulateElementDrag, hasUndefinedProperties; beforeEach(inject(function (sortableTestHelper) { EXTRA_DY_PERCENTAGE = sortableTestHelper.EXTRA_DY_PERCENTAGE; listContent = sortableTestHelper.listContent; listInnerContent = sortableTestHelper.listInnerContent; simulateElementDrag = sortableTestHelper.simulateElementDrag; + hasUndefinedProperties = sortableTestHelper.hasUndefinedProperties; })); describe('Multiple sortables related', function() { @@ -470,6 +471,222 @@ describe('uiSortable', function() { }); }); + it('should properly set ui.item.sortable properties', function() { + inject(function($compile, $rootScope) { + var elementTop, elementBottom, updateCallbackExpectations, stopCallbackExpectations; + elementTop = $compile('')($rootScope); + elementBottom = $compile('')($rootScope); + $rootScope.$apply(function() { + $rootScope.itemsTop = ['Top One', 'Top Two', 'Top Three']; + $rootScope.itemsBottom = ['Bottom One', 'Bottom Two', 'Bottom Three']; + $rootScope.opts = { + connectWith: '.cross-sortable', + update: function(e, ui) { + if (ui.item.scope() && + (typeof ui.item.scope().item === 'string') && + ui.item.scope().item.indexOf('Two') >= 0) { + ui.item.sortable.cancel(); + } + updateCallbackExpectations(ui.item.sortable); + }, + stop: function(e, ui) { + stopCallbackExpectations(ui.item.sortable); + } + }; + }); + + host.append(elementTop).append(elementBottom).append('
'); + + var li1 = elementTop.find(':eq(1)'); + var li2 = elementBottom.find(':eq(0)'); + updateCallbackExpectations = function(uiItemSortable) { + expect(uiItemSortable.model).toEqual('Top Two'); + expect(uiItemSortable.index).toEqual(1); + expect(uiItemSortable.source.length).toEqual(1); + expect(uiItemSortable.source[0]).toBe(host.children()[0]); + expect(uiItemSortable.sourceModel).toBe($rootScope.itemsTop); + expect(uiItemSortable.isCanceled()).toBe(true); + expect(uiItemSortable.isCustomHelperUsed()).toBe(false); + + expect(uiItemSortable.dropindex).toEqual(1); + expect(uiItemSortable.droptarget.length).toBe(1); + expect(uiItemSortable.droptarget[0]).toBe(host.children()[1]); + expect(uiItemSortable.droptargetModel).toBe($rootScope.itemsBottom); + }; + stopCallbackExpectations = function(uiItemSortable) { + expect(uiItemSortable.received).toBe(true); + expect(uiItemSortable.moved).toBe(undefined); + }; + simulateElementDrag(li1, li2, { place: 'below', extradx: -20, extrady: -10 }); + expect($rootScope.itemsTop).toEqual(['Top One', 'Top Two', 'Top Three']); + expect($rootScope.itemsBottom).toEqual(['Bottom One', 'Bottom Two', 'Bottom Three']); + expect($rootScope.itemsTop).toEqual(listContent(elementTop)); + expect($rootScope.itemsBottom).toEqual(listContent(elementBottom)); + updateCallbackExpectations = stopCallbackExpectations = undefined; + + li1 = elementBottom.find(':eq(1)'); + li2 = elementTop.find(':eq(1)'); + updateCallbackExpectations = function(uiItemSortable) { + expect(uiItemSortable.model).toEqual('Bottom Two'); + expect(uiItemSortable.index).toEqual(1); + expect(uiItemSortable.source.length).toEqual(1); + expect(uiItemSortable.source[0]).toBe(host.children()[1]); + expect(uiItemSortable.sourceModel).toBe($rootScope.itemsBottom); + expect(uiItemSortable.isCanceled()).toBe(true); + expect(uiItemSortable.isCustomHelperUsed()).toBe(false); + + expect(uiItemSortable.dropindex).toEqual(1); + expect(uiItemSortable.droptarget.length).toBe(1); + expect(uiItemSortable.droptarget[0]).toBe(host.children()[0]); + expect(uiItemSortable.droptargetModel).toBe($rootScope.itemsTop); + }; + stopCallbackExpectations = function(uiItemSortable) { + expect(uiItemSortable.received).toBe(true); + expect(uiItemSortable.moved).toBe(undefined); + }; + simulateElementDrag(li1, li2, { place: 'above', extradx: -20, extrady: -10 }); + expect($rootScope.itemsTop).toEqual(['Top One', 'Top Two', 'Top Three']); + expect($rootScope.itemsBottom).toEqual(['Bottom One', 'Bottom Two', 'Bottom Three']); + expect($rootScope.itemsTop).toEqual(listContent(elementTop)); + expect($rootScope.itemsBottom).toEqual(listContent(elementBottom)); + updateCallbackExpectations = stopCallbackExpectations = undefined; + + li1 = elementTop.find(':eq(0)'); + li2 = elementBottom.find(':eq(0)'); + updateCallbackExpectations = function(uiItemSortable) { + expect(uiItemSortable.model).toEqual('Top One'); + expect(uiItemSortable.index).toEqual(0); + expect(uiItemSortable.source.length).toEqual(1); + expect(uiItemSortable.source[0]).toBe(host.children()[0]); + expect(uiItemSortable.sourceModel).toBe($rootScope.itemsTop); + expect(uiItemSortable.isCanceled()).toBe(false); + expect(uiItemSortable.isCustomHelperUsed()).toBe(false); + + expect(uiItemSortable.dropindex).toEqual(1); + expect(uiItemSortable.droptarget.length).toBe(1); + expect(uiItemSortable.droptarget[0]).toBe(host.children()[1]); + expect(uiItemSortable.droptargetModel).toBe($rootScope.itemsBottom); + }; + stopCallbackExpectations = function(uiItemSortable) { + expect(uiItemSortable.received).toBe(true); + expect(uiItemSortable.moved).toBe('Top One'); + }; + simulateElementDrag(li1, li2, 'below'); + expect($rootScope.itemsTop).toEqual(['Top Two', 'Top Three']); + expect($rootScope.itemsBottom).toEqual(['Bottom One', 'Top One', 'Bottom Two', 'Bottom Three']); + expect($rootScope.itemsTop).toEqual(listContent(elementTop)); + expect($rootScope.itemsBottom).toEqual(listContent(elementBottom)); + updateCallbackExpectations = stopCallbackExpectations = undefined; + + li1 = elementBottom.find(':eq(1)'); + li2 = elementTop.find(':eq(1)'); + updateCallbackExpectations = function(uiItemSortable) { + expect(uiItemSortable.model).toEqual('Top One'); + expect(uiItemSortable.index).toEqual(1); + expect(uiItemSortable.source.length).toEqual(1); + expect(uiItemSortable.source[0]).toBe(host.children()[1]); + expect(uiItemSortable.sourceModel).toBe($rootScope.itemsBottom); + expect(uiItemSortable.isCanceled()).toBe(false); + expect(uiItemSortable.isCustomHelperUsed()).toBe(false); + + expect(uiItemSortable.dropindex).toEqual(1); + expect(uiItemSortable.droptarget.length).toBe(1); + expect(uiItemSortable.droptarget[0]).toBe(host.children()[0]); + expect(uiItemSortable.droptargetModel).toBe($rootScope.itemsTop); + }; + stopCallbackExpectations = function(uiItemSortable) { + expect(uiItemSortable.received).toBe(true); + expect(uiItemSortable.moved).toBe('Top One'); + }; + simulateElementDrag(li1, li2, { place: 'above', extradx: -20, extrady: -10 }); + expect($rootScope.itemsTop).toEqual(['Top Two', 'Top One', 'Top Three']); + expect($rootScope.itemsBottom).toEqual(['Bottom One', 'Bottom Two', 'Bottom Three']); + expect($rootScope.itemsTop).toEqual(listContent(elementTop)); + expect($rootScope.itemsBottom).toEqual(listContent(elementBottom)); + updateCallbackExpectations = stopCallbackExpectations = undefined; + + $(elementTop).remove(); + $(elementBottom).remove(); + }); + }); + + it('should properly free ui.item.sortable object', function() { + inject(function($compile, $rootScope) { + var elementTop, elementBottom, uiItem, uiItemSortable_Destroy; + elementTop = $compile('')($rootScope); + elementBottom = $compile('')($rootScope); + $rootScope.$apply(function() { + $rootScope.itemsTop = ['Top One', 'Top Two', 'Top Three']; + $rootScope.itemsBottom = ['Bottom One', 'Bottom Two', 'Bottom Three']; + $rootScope.opts = { + connectWith: '.cross-sortable', + start: function (e, ui) { + uiItem = ui.item; + spyOn(ui.item.sortable, '_destroy').andCallThrough(); + uiItemSortable_Destroy = ui.item.sortable._destroy; + }, + update: function(e, ui) { + uiItem.sortable = ui.item.sortable; + if (ui.item.scope() && + (typeof ui.item.scope().item === 'string') && + ui.item.scope().item.indexOf('Two') >= 0) { + ui.item.sortable.cancel(); + } + } + }; + }); + + host.append(elementTop).append(elementBottom).append('
'); + + var li1 = elementTop.find(':eq(1)'); + var li2 = elementBottom.find(':eq(0)'); + simulateElementDrag(li1, li2, 'below'); + expect($rootScope.itemsTop).toEqual(['Top One', 'Top Two', 'Top Three']); + expect($rootScope.itemsBottom).toEqual(['Bottom One', 'Bottom Two', 'Bottom Three']); + expect($rootScope.itemsTop).toEqual(listContent(elementTop)); + expect($rootScope.itemsBottom).toEqual(listContent(elementBottom)); + expect(uiItemSortable_Destroy).toHaveBeenCalled(); + expect(hasUndefinedProperties(uiItem.sortable)).toBe(true); + uiItem = uiItemSortable_Destroy = undefined; + + li1 = elementBottom.find(':eq(1)'); + li2 = elementTop.find(':eq(1)'); + simulateElementDrag(li1, li2, { place: 'above', extradx: -20, extrady: -10 }); + expect($rootScope.itemsTop).toEqual(['Top One', 'Top Two', 'Top Three']); + expect($rootScope.itemsBottom).toEqual(['Bottom One', 'Bottom Two', 'Bottom Three']); + expect($rootScope.itemsTop).toEqual(listContent(elementTop)); + expect($rootScope.itemsBottom).toEqual(listContent(elementBottom)); + expect(uiItemSortable_Destroy).toHaveBeenCalled(); + expect(hasUndefinedProperties(uiItem.sortable)).toBe(true); + uiItem = uiItemSortable_Destroy = undefined; + + li1 = elementTop.find(':eq(0)'); + li2 = elementBottom.find(':eq(0)'); + simulateElementDrag(li1, li2, 'below'); + expect($rootScope.itemsTop).toEqual(['Top Two', 'Top Three']); + expect($rootScope.itemsBottom).toEqual(['Bottom One', 'Top One', 'Bottom Two', 'Bottom Three']); + expect($rootScope.itemsTop).toEqual(listContent(elementTop)); + expect($rootScope.itemsBottom).toEqual(listContent(elementBottom)); + expect(uiItemSortable_Destroy).toHaveBeenCalled(); + expect(hasUndefinedProperties(uiItem.sortable)).toBe(true); + uiItem = uiItemSortable_Destroy = undefined; + + li1 = elementBottom.find(':eq(1)'); + li2 = elementTop.find(':eq(1)'); + simulateElementDrag(li1, li2, { place: 'above', extradx: -20, extrady: -10 }); + expect($rootScope.itemsTop).toEqual(['Top Two', 'Top One', 'Top Three']); + expect($rootScope.itemsBottom).toEqual(['Bottom One', 'Bottom Two', 'Bottom Three']); + expect($rootScope.itemsTop).toEqual(listContent(elementTop)); + expect($rootScope.itemsBottom).toEqual(listContent(elementBottom)); + expect(uiItemSortable_Destroy).toHaveBeenCalled(); + expect(hasUndefinedProperties(uiItem.sortable)).toBe(true); + uiItem = uiItemSortable_Destroy = undefined; + + $(elementTop).remove(); + $(elementBottom).remove(); + }); + }); + }); }); \ No newline at end of file diff --git a/test/sortable.test-helper.js b/test/sortable.test-helper.js index 2b5bf90..4da4d45 100644 --- a/test/sortable.test-helper.js +++ b/test/sortable.test-helper.js @@ -62,10 +62,20 @@ angular.module('ui.sortable.testHelper', []) draggedElement.simulate('drag', dragOptions); } + function hasUndefinedProperties(testObject) { + return testObject && Object.keys(testObject) + .filter(function(key) { + return testObject.hasOwnProperty(key) && + testObject[key] !== undefined; + }) + .length === 0; + } + return { EXTRA_DY_PERCENTAGE: EXTRA_DY_PERCENTAGE, listContent: listContent, listInnerContent: listInnerContent, - simulateElementDrag: simulateElementDrag + simulateElementDrag: simulateElementDrag, + hasUndefinedProperties: hasUndefinedProperties }; });