From d5365e7be8762104f0d3077f635b1736d13b5253 Mon Sep 17 00:00:00 2001 From: deedee Date: Sat, 6 Sep 2014 02:20:07 +0200 Subject: [PATCH 1/7] fix(sortable): manage appendTo option appendTo option mess up ng-repeat comments --- src/sortable.js | 3 ++- test/sortable.e2e.spec.js | 26 +++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/sortable.js b/src/sortable.js index a18f046..065179c 100644 --- a/src/sortable.js +++ b/src/sortable.js @@ -133,7 +133,8 @@ angular.module('ui.sortable', []) // the start and stop of repeat sections and sortable doesn't // respect their order (even if we cancel, the order of the // comments are still messed up). - if (hasSortingHelper(element, ui) && !ui.item.sortable.received) { + if (hasSortingHelper(element, ui) && !ui.item.sortable.received && + element.sortable( 'option', 'appendTo' ) === 'parent') { // restore all the savedNodes except .ui-sortable-helper element // (which is placed last). That way it will be garbage collected. savedNodes = savedNodes.not(savedNodes.last()); diff --git a/test/sortable.e2e.spec.js b/test/sortable.e2e.spec.js index dc2cdaf..21923d2 100644 --- a/test/sortable.e2e.spec.js +++ b/test/sortable.e2e.spec.js @@ -381,6 +381,30 @@ describe('uiSortable', function() { }); }); + it('should work when "helper: clone" and "appendTo" options are used together', function() { + inject(function($compile, $rootScope) { + var element; + element = $compile('')($rootScope); + $rootScope.$apply(function() { + $rootScope.opts = { + helper: 'clone', + appendTo: 'body' + }; + $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', 'Three', 'Two']); + expect($rootScope.items).toEqual(listContent(element)); + + $(element).remove(); + }); + }); + it('should work when "helper: clone" and "placeholder" options are used together.', function() { inject(function($compile, $rootScope) { var element; @@ -648,4 +672,4 @@ describe('uiSortable', function() { }); -}); \ No newline at end of file +}); From 2ce617f1a00df6f8e562a6cf65c4212284f6dcb6 Mon Sep 17 00:00:00 2001 From: Thodoris Greasidis Date: Sun, 7 Sep 2014 21:03:40 +0300 Subject: [PATCH 2/7] tests(sortable): add one more sorting to 'appendTo' test case --- test/sortable.e2e.spec.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/sortable.e2e.spec.js b/test/sortable.e2e.spec.js index 21923d2..987a3e7 100644 --- a/test/sortable.e2e.spec.js +++ b/test/sortable.e2e.spec.js @@ -401,6 +401,12 @@ describe('uiSortable', function() { expect($rootScope.items).toEqual(['One', 'Three', 'Two']); expect($rootScope.items).toEqual(listContent(element)); + li = element.find(':eq(2)'); + 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)); + $(element).remove(); }); }); From 28b82f9c7500d8992aad72583dd9577b12e1ba11 Mon Sep 17 00:00:00 2001 From: Thodoris Greasidis Date: Sun, 7 Sep 2014 21:05:15 +0300 Subject: [PATCH 3/7] chore: increase version number to v0.12.11 --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index 7e98e27..e5fe9f1 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "angular-ui-sortable", - "version": "0.12.10", + "version": "0.12.11", "description": "This directive allows you to jQueryUI Sortable.", "author": "https://github.com/angular-ui/ui-sortable/graphs/contributors", "license": "MIT", diff --git a/package.json b/package.json index f056905..77b2d78 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-ui-sortable", - "version": "0.12.10", + "version": "0.12.11", "description": "This directive allows you to jQueryUI Sortable.", "author": "https://github.com/angular-ui/ui-sortable/graphs/contributors", "license": "MIT", From 2fb4240d151b8d9c02cb4ae046b70431a85c7559 Mon Sep 17 00:00:00 2001 From: Thodoris Greasidis Date: Wed, 9 Jul 2014 00:51:06 +0300 Subject: [PATCH 4/7] feat(sortable): update local variable with active options --- src/sortable.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sortable.js b/src/sortable.js index 065179c..951d4c4 100644 --- a/src/sortable.js +++ b/src/sortable.js @@ -241,6 +241,7 @@ angular.module('ui.sortable', []) value = wrappers[key](value); } + opts[key] = value; element.sortable('option', key, value); }); } From 104405bdcc8b6efc61fb14e00ab12697d90a48dc Mon Sep 17 00:00:00 2001 From: Thodoris Greasidis Date: Wed, 9 Jul 2014 01:37:28 +0300 Subject: [PATCH 5/7] feat(sortable): add workaround for horizontal lists --- src/sortable.js | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/sortable.js b/src/sortable.js index 951d4c4..3353f9d 100644 --- a/src/sortable.js +++ b/src/sortable.js @@ -28,8 +28,18 @@ angular.module('ui.sortable', []) return helperOption === 'clone' || (typeof helperOption === 'function' && ui.item.sortable.isCustomHelperUsed()); } + // thanks jquery-ui + function isFloating (item) { + return (/left|right/).test(item.css('float')) || (/inline|table-cell/).test(item.css('display')); + } + var opts = {}; + // directive specific options + var directiveOpts = { + 'ui-floating': undefined + }; + var callbacks = { receive: null, remove:null, @@ -42,7 +52,7 @@ angular.module('ui.sortable', []) helper: null }; - angular.extend(opts, uiSortableConfig, scope.$eval(attrs.uiSortable)); + angular.extend(opts, directiveOpts, uiSortableConfig, scope.$eval(attrs.uiSortable)); if (!angular.element.fn || !angular.element.fn.jquery) { $log.error('ui.sortable: jQuery should be included before AngularJS!'); @@ -65,6 +75,13 @@ angular.module('ui.sortable', []) }); callbacks.start = function(e, ui) { + if (opts['ui-floating'] === 'auto') { + // since the drag has started, the element will be + // absolutely positioned, so we check its siblings + var siblings = ui.item.siblings(); + angular.element(e.target).data('ui-sortable').floating = isFloating(siblings); + } + // Save the starting position of dragged item ui.item.sortable = { index: ui.item.index(), @@ -229,7 +246,18 @@ angular.module('ui.sortable', []) // is still bound to the directive's element if (!!element.data('ui-sortable')) { angular.forEach(newVal, function(value, key) { - if(callbacks[key]) { + // if it's a custom option of the directive, + // handle it approprietly + if (key in directiveOpts) { + if (key === 'ui-floating' && (value === false || value === true)) { + element.data('ui-sortable').floating = value; + } + + opts[key] = value; + return; + } + + if (callbacks[key]) { if( key === 'stop' ){ // call apply after stop value = combineCallbacks( From 830831cb59c3b00c67d6e76a710e236920b9da28 Mon Sep 17 00:00:00 2001 From: Thodoris Greasidis Date: Wed, 9 Jul 2014 01:37:52 +0300 Subject: [PATCH 6/7] test(sortable): add tests for horizontal lists workaround --- test/sortable.e2e.directiveoptions.spec.js | 172 +++++++++++++++++++++ test/sortable.tests.css | 5 + 2 files changed, 177 insertions(+) create mode 100644 test/sortable.e2e.directiveoptions.spec.js diff --git a/test/sortable.e2e.directiveoptions.spec.js b/test/sortable.e2e.directiveoptions.spec.js new file mode 100644 index 0000000..bd2461c --- /dev/null +++ b/test/sortable.e2e.directiveoptions.spec.js @@ -0,0 +1,172 @@ +'use strict'; + +describe('uiSortable', function() { + + // Ensure the sortable angular module is loaded + beforeEach(module('ui.sortable')); + beforeEach(module('ui.sortable.testHelper')); + + var EXTRA_DY_PERCENTAGE, listContent; + + beforeEach(inject(function (sortableTestHelper) { + EXTRA_DY_PERCENTAGE = sortableTestHelper.EXTRA_DY_PERCENTAGE; + listContent = sortableTestHelper.listContent; + })); + + describe('Custom directive options related', function() { + + var host; + + beforeEach(inject(function() { + host = $('
'); + $('body').append(host); + })); + + afterEach(function() { + host.remove(); + host = null; + }); + + it('should work when "ui-floating: false" option is used', function() { + inject(function($compile, $rootScope) { + var element; + element = $compile('
  • {{ item }}
')($rootScope); + $rootScope.$apply(function() { + $rootScope.opts = { + 'ui-floating': false + }; + $rootScope.items = ['One', 'Two', 'Three']; + }); + + host.append(element); + + var li = element.find(':eq(0)'); + var dy = (1 + EXTRA_DY_PERCENTAGE) * li.outerHeight(); + li.simulate('drag', { dy: dy }); + expect($rootScope.items).toEqual(['Two', 'One', 'Three']); + expect($rootScope.items).toEqual(listContent(element)); + + li = element.find(':eq(1)'); + dy = (1 + EXTRA_DY_PERCENTAGE) * li.outerHeight(); + li.simulate('drag', { dy: dy }); + expect($rootScope.items).toEqual(['Two', 'Three', 'One']); + expect($rootScope.items).toEqual(listContent(element)); + + li = element.find(':eq(1)'); + dy = (1 + EXTRA_DY_PERCENTAGE) * li.outerHeight(); + li.simulate('drag', { dy: dy }); + expect($rootScope.items).toEqual(['Two', 'One', 'Three']); + expect($rootScope.items).toEqual(listContent(element)); + + $(element).remove(); + }); + }); + + it('should work when "ui-floating: true" option is used', function() { + inject(function($compile, $rootScope) { + var element; + element = $compile('
  • {{ item }}
')($rootScope); + $rootScope.$apply(function() { + $rootScope.opts = { + 'ui-floating': true + }; + $rootScope.items = ['One', 'Two', 'Three']; + }); + + host.append(element).append('
'); + + var li = element.find(':eq(0)'); + var dx = (1 + EXTRA_DY_PERCENTAGE) * li.outerWidth(); + li.simulate('drag', { dx: dx }); + expect($rootScope.items).toEqual(['Two', 'One', 'Three']); + expect($rootScope.items).toEqual(listContent(element)); + + li = element.find(':eq(1)'); + dx = (1 + EXTRA_DY_PERCENTAGE) * li.outerWidth(); + li.simulate('drag', { dx: dx }); + expect($rootScope.items).toEqual(['Two', 'Three', 'One']); + expect($rootScope.items).toEqual(listContent(element)); + + li = element.find(':eq(1)'); + dx = (1 + EXTRA_DY_PERCENTAGE) * li.outerWidth(); + li.simulate('drag', { dx: dx, moves: 5 }); + expect($rootScope.items).toEqual(['Two', 'One', 'Three']); + expect($rootScope.items).toEqual(listContent(element)); + + $(element).remove(); + }); + }); + + it('should work when "ui-floating: \'auto\'" option is used and elements are "float"ing', function() { + inject(function($compile, $rootScope) { + var element; + element = $compile('
  • {{ item }}
')($rootScope); + $rootScope.$apply(function() { + $rootScope.opts = { + 'ui-floating': 'auto' + }; + $rootScope.items = ['One', 'Two', 'Three']; + }); + + host.append(element).append('
'); + + var li = element.find(':eq(0)'); + var dx = (1 + EXTRA_DY_PERCENTAGE) * li.outerWidth(); + li.simulate('drag', { dx: dx }); + expect($rootScope.items).toEqual(['Two', 'One', 'Three']); + expect($rootScope.items).toEqual(listContent(element)); + + li = element.find(':eq(1)'); + dx = (1 + EXTRA_DY_PERCENTAGE) * li.outerWidth(); + li.simulate('drag', { dx: dx }); + expect($rootScope.items).toEqual(['Two', 'Three', 'One']); + expect($rootScope.items).toEqual(listContent(element)); + + li = element.find(':eq(1)'); + dx = (1 + EXTRA_DY_PERCENTAGE) * li.outerWidth(); + li.simulate('drag', { dx: dx, moves: 5 }); + expect($rootScope.items).toEqual(['Two', 'One', 'Three']); + expect($rootScope.items).toEqual(listContent(element)); + + $(element).remove(); + }); + }); + + it('should work when "ui-floating: \'auto\'" option is used and elements are "display: inline-block"', function() { + inject(function($compile, $rootScope) { + var element; + element = $compile('
  • {{ item }}
')($rootScope); + $rootScope.$apply(function() { + $rootScope.opts = { + 'ui-floating': 'auto' + }; + $rootScope.items = ['One', 'Two', 'Three']; + }); + + host.append(element); + + var li = element.find(':eq(0)'); + var dx = (1 + EXTRA_DY_PERCENTAGE) * li.outerWidth(); + li.simulate('drag', { dx: dx }); + expect($rootScope.items).toEqual(['Two', 'One', 'Three']); + expect($rootScope.items).toEqual(listContent(element)); + + li = element.find(':eq(1)'); + dx = (1 + EXTRA_DY_PERCENTAGE) * li.outerWidth(); + li.simulate('drag', { dx: dx }); + expect($rootScope.items).toEqual(['Two', 'Three', 'One']); + expect($rootScope.items).toEqual(listContent(element)); + + li = element.find(':eq(1)'); + dx = (1 + EXTRA_DY_PERCENTAGE) * li.outerWidth(); + li.simulate('drag', { dx: dx, moves: 5 }); + expect($rootScope.items).toEqual(['Two', 'One', 'Three']); + expect($rootScope.items).toEqual(listContent(element)); + + $(element).remove(); + }); + }); + + }); + +}); \ No newline at end of file diff --git a/test/sortable.tests.css b/test/sortable.tests.css index 0d7bb75..6601ca0 100644 --- a/test/sortable.tests.css +++ b/test/sortable.tests.css @@ -1,3 +1,8 @@ +.inline-block { + display: inline-block; +} + +.floatleft, .cross-sortable { float: left; } From 2e22b0e494e9a8a6493512a6909f1127476b85b6 Mon Sep 17 00:00:00 2001 From: Thodoris Greasidis Date: Tue, 8 Jul 2014 23:55:52 +0300 Subject: [PATCH 7/7] docs(README): add section about floating workaround & example --- README.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/README.md b/README.md index a2c445a..7574742 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,41 @@ myAppModule.controller('MyController', function($scope) { When using event callbacks ([start](http://api.jqueryui.com/sortable/#event-start)/[update](http://api.jqueryui.com/sortable/#event-update)/[stop](http://api.jqueryui.com/sortable/#event-stop)...), avoid manipulating DOM elements (especially the one with the ng-repeat attached). The suggested pattern is to use callbacks for emmiting events and altering the scope (inside the 'Angular world'). +#### Floating + +To have a smooth horizontal-list reordering, jquery.ui.sortable needs to detect the orientation of the list. +This detection takes place during the initialization of the plugin (and some of the checks include: whether the first item is floating left/right or if 'axis' parameter is 'x', etc). +There is also a [known issue](bugs.jqueryui.com/ticket/7498) about initially empty horizontal lists. + +To provide a solution/workaround (till jquery.ui.sortable.refresh() also tests the orientation or a more appropriate method is provided), ui-sortable directive provides a `ui-floating` option as an extra to the [jquery.ui.sortable options](http://api.jqueryui.com/sortable/). + +```html +
    +
  • {{ item }}
  • +
+``` + +**OR** + +```js +$scope.sortableOptions = { + 'ui-floating': true +}; +``` +```html +
    +
  • {{ item }}
  • +
+``` + + +**ui-floating** (default: undefined) +Type: [Boolean](http://api.jquery.com/Types/#Boolean)/[String](http://api.jquery.com/Types/#String)/`undefined` +* **undefined**: Relies on jquery.ui to detect the list's orientation. +* **false**: Forces jquery.ui.sortable to detect the list as vertical. +* **true**: Forces jquery.ui.sortable to detect the list as horizontal. +* **"auto"**: Detects on each drag `start` if the element is floating or not. + #### Canceling Inside the `update` callback, you can check the item that is dragged and cancel the sorting. @@ -138,6 +173,7 @@ For more details about the events check the [jQueryUI API documentation](http:// - [Filtering](http://codepen.io/thgreasi/pen/mzGbq) ([details](https://github.com/angular-ui/ui-sortable/issues/113)) - [Ordering 1](http://codepen.io/thgreasi/pen/iKEHd) & [Ordering 2](http://plnkr.co/edit/XPUzJjdvwE0QWQ6py6mQ?p=preview) ([details](https://github.com/angular-ui/ui-sortable/issues/70)) - [Cloning](http://codepen.io/thgreasi/pen/qmvhG) ([details](https://github.com/angular-ui/ui-sortable/issues/139)) +- [Horizontal List](http://codepen.io/thgreasi/pen/wsfjD) - [Tree with dynamic template](http://codepen.io/thgreasi/pen/uyHFC) - Canceling - [Connected Lists With Max Size](http://codepen.io/thgreasi/pen/IdvFc)