From 25c7363b4f9fac406e4a6cd9c3ca8014092d9c71 Mon Sep 17 00:00:00 2001 From: Thodoris Greasidis Date: Wed, 9 Jul 2014 00:51:06 +0300 Subject: [PATCH 1/4] 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 a18f046..c1d4fa7 100644 --- a/src/sortable.js +++ b/src/sortable.js @@ -240,6 +240,7 @@ angular.module('ui.sortable', []) value = wrappers[key](value); } + opts[key] = value; element.sortable('option', key, value); }); } From 68cd3706a6b4aef272f7d0eb1b451587bd8be88b Mon Sep 17 00:00:00 2001 From: Thodoris Greasidis Date: Wed, 9 Jul 2014 01:37:28 +0300 Subject: [PATCH 2/4] 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 c1d4fa7..616d239 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(), @@ -228,7 +245,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 5c0e4035a833a7cc3a400aec88b101b7d4d99e5b Mon Sep 17 00:00:00 2001 From: Thodoris Greasidis Date: Wed, 9 Jul 2014 01:37:52 +0300 Subject: [PATCH 3/4] 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 11e0af4747927b21c6aeba91bddaab52e4b00a30 Mon Sep 17 00:00:00 2001 From: Thodoris Greasidis Date: Tue, 8 Jul 2014 23:55:52 +0300 Subject: [PATCH 4/4] 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)