diff --git a/src/sortable.js b/src/sortable.js index 9f86dcd..9b2fb5a 100644 --- a/src/sortable.js +++ b/src/sortable.js @@ -174,6 +174,14 @@ angular.module('ui.sortable', []) }; callbacks.remove = function(e, ui) { + // Workaround for a problem observed in nested connected lists. + // There should be an 'update' event before 'remove' when moving + // elements. If the event did not fire, cancel sorting. + if (!('dropindex' in ui.item.sortable)) { + element.sortable('cancel'); + ui.item.sortable.cancel(); + } + // Remove the item from this list's model and copy data into item, // so the next list can retrive it if (!ui.item.sortable.isCanceled()) { diff --git a/test/sortable.e2e.multi.spec.js b/test/sortable.e2e.multi.spec.js index 3a16df7..e1dd5d4 100644 --- a/test/sortable.e2e.multi.spec.js +++ b/test/sortable.e2e.multi.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, listInnerContent; beforeEach(inject(function (sortableTestHelper) { EXTRA_DY_PERCENTAGE = sortableTestHelper.EXTRA_DY_PERCENTAGE; listContent = sortableTestHelper.listContent; + listInnerContent = sortableTestHelper.listInnerContent; })); describe('Multiple sortables related', function() { @@ -346,6 +347,110 @@ describe('uiSortable', function() { }); }); + it('should update model when sorting between nested sortables', function() { + inject(function($compile, $rootScope) { + var elementTree, li1, li2, dy; + + elementTree = $compile(''.concat( + '', + '
'))($rootScope); + + $rootScope.$apply(function() { + $rootScope.items = [ + { + text: 'Item 1', + items: [] + }, + { + text: 'Item 2', + items: [ + { text: 'Item 2.1', items: [] }, + { text: 'Item 2.2', items: [] } + ] + } + ]; + + $rootScope.sortableOptions = { + connectWith: '.apps-container' + }; + }); + + host.append(elementTree); + + // this should drag the item out of the list and + // the item should return back to its original position + li1 = elementTree.find('.innerList:last').find(':last'); + li1.simulate('drag', { dx: -200, moves: 30 }); + expect($rootScope.items.map(function(x){ return x.text; })) + .toEqual(['Item 1', 'Item 2']); + expect($rootScope.items.map(function(x){ return x.text; })) + .toEqual(listInnerContent(elementTree, '.lvl1ItemContent')); + expect($rootScope.items[0].items.map(function(x){ return x.text; })) + .toEqual([]); + expect($rootScope.items[0].items.map(function(x){ return x.text; })) + .toEqual(listInnerContent(elementTree.find('.innerList:eq(0)'), '.lvl2ItemContent')); + expect($rootScope.items[1].items.map(function(x){ return x.text; })) + .toEqual(['Item 2.1', 'Item 2.2']); + expect($rootScope.items[1].items.map(function(x){ return x.text; })) + .toEqual(listInnerContent(elementTree.find('.innerList:eq(1)'), '.lvl2ItemContent')); + + // this should drag the item from the inner list and + // drop it to the outter list + li1 = elementTree.find('.innerList:last').find(':last'); + li2 = elementTree.find('> li:last'); + dy = EXTRA_DY_PERCENTAGE * li1.outerHeight() + (li2.position().top - li1.position().top); + li1.simulate('drag', { dy: dy }); + expect($rootScope.items.map(function(x){ return x.text; })) + .toEqual(['Item 1', 'Item 2.2', 'Item 2']); + expect($rootScope.items.map(function(x){ return x.text; })) + .toEqual(listInnerContent(elementTree, '.lvl1ItemContent')); + expect($rootScope.items[0].items.map(function(x){ return x.text; })) + .toEqual([]); + expect($rootScope.items[0].items.map(function(x){ return x.text; })) + .toEqual(listInnerContent(elementTree.find('.innerList:eq(0)'), '.lvl2ItemContent')); + expect($rootScope.items[1].items.map(function(x){ return x.text; })) + .toEqual([]); + expect($rootScope.items[1].items.map(function(x){ return x.text; })) + .toEqual(listInnerContent(elementTree.find('.innerList:eq(1)'), '.lvl2ItemContent')); + expect($rootScope.items[2].items.map(function(x){ return x.text; })) + .toEqual(['Item 2.1']); + expect($rootScope.items[2].items.map(function(x){ return x.text; })) + .toEqual(listInnerContent(elementTree.find('.innerList:eq(2)'), '.lvl2ItemContent')); + + // this should drag the item from the outter list and + // drop it to the inner list + li1 = elementTree.find('> li:first'); + li2 = elementTree.find('.innerList:last').find(':last'); + dy = -EXTRA_DY_PERCENTAGE * li1.outerHeight() + (li2.position().top - li1.position().top); + li1.simulate('drag', { dy: dy }); + expect($rootScope.items.map(function(x){ return x.text; })) + .toEqual(['Item 2.2', 'Item 2']); + expect($rootScope.items.map(function(x){ return x.text; })) + .toEqual(listInnerContent(elementTree, '.lvl1ItemContent')); + expect($rootScope.items[0].items.map(function(x){ return x.text; })) + .toEqual([]); + expect($rootScope.items[0].items.map(function(x){ return x.text; })) + .toEqual(listInnerContent(elementTree.find('.innerList:eq(0)'), '.lvl2ItemContent')); + expect($rootScope.items[1].items.map(function(x){ return x.text; })) + .toEqual(['Item 1', 'Item 2.1']); + expect($rootScope.items[1].items.map(function(x){ return x.text; })) + .toEqual(listInnerContent(elementTree.find('.innerList:eq(1)'), '.lvl2ItemContent')); + + $(elementTree).remove(); + }); + }); + }); }); \ No newline at end of file diff --git a/test/sortable.test-helper.js b/test/sortable.test-helper.js index d0c298f..11141d1 100644 --- a/test/sortable.test-helper.js +++ b/test/sortable.test-helper.js @@ -11,9 +11,13 @@ angular.module('ui.sortable.testHelper', []) return []; } - function listInnerContent (list) { + function listInnerContent (list, contentSelector) { + if (!contentSelector) { + contentSelector = '.itemContent'; + } + if (list && list.length) { - return list.children().map(function(){ return $(this).find('.itemContent').html(); }).toArray(); + return list.children().map(function(){ return $(this).find(contentSelector).html(); }).toArray(); } return []; }