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(
+ '
',
+ '- ',
+ '
',
+ '
{{item.text}}',
+ '
',
+ '- ',
+ '{{i.text}}',
+ '
',
+ '
',
+ '
',
+ ' ',
+ '
',
+ ''))($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 [];
}