Skip to content
This repository has been archived by the owner on Sep 8, 2020. It is now read-only.

Commit

Permalink
Merge pull request #258 from thgreasi/floating-src
Browse files Browse the repository at this point in the history
feat(sortable): add workaround for horizontal lists
  • Loading branch information
thgreasi committed Sep 1, 2014
2 parents 5168154 + 11e0af4 commit 6a7579f
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 2 deletions.
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<ul ui-sortable="{ 'ui-floating': true }" ng-model="items">
<li ng-repeat="item in items">{{ item }}</li>
</ul>
```

**OR**

```js
$scope.sortableOptions = {
'ui-floating': true
};
```
```html
<ul ui-sortable="sortableOptions" ng-model="items">
<li ng-repeat="item in items">{{ item }}</li>
</ul>
```


**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.
Expand Down Expand Up @@ -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)
Expand Down
33 changes: 31 additions & 2 deletions src/sortable.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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!');
Expand All @@ -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(),
Expand Down Expand Up @@ -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(
Expand All @@ -240,6 +268,7 @@ angular.module('ui.sortable', [])
value = wrappers[key](value);
}

opts[key] = value;
element.sortable('option', key, value);
});
}
Expand Down
172 changes: 172 additions & 0 deletions test/sortable.e2e.directiveoptions.spec.js
Original file line number Diff line number Diff line change
@@ -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 = $('<div id="test-host"></div>');
$('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('<ul ui-sortable="opts" ng-model="items"><li ng-repeat="item in items" id="s-{{$index}}" class="sortable-item">{{ item }}</li></ul>')($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('<ul ui-sortable="opts" ng-model="items"><li class="floatleft" ng-repeat="item in items" id="s-{{$index}}" class="sortable-item">{{ item }}</li></ul>')($rootScope);
$rootScope.$apply(function() {
$rootScope.opts = {
'ui-floating': true
};
$rootScope.items = ['One', 'Two', 'Three'];
});

host.append(element).append('<div class="clear"></div>');

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('<ul ui-sortable="opts" ng-model="items"><li class="floatleft" ng-repeat="item in items" id="s-{{$index}}" class="sortable-item">{{ item }}</li></ul>')($rootScope);
$rootScope.$apply(function() {
$rootScope.opts = {
'ui-floating': 'auto'
};
$rootScope.items = ['One', 'Two', 'Three'];
});

host.append(element).append('<div class="clear"></div>');

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('<ul ui-sortable="opts" ng-model="items"><li class="inline-block" ng-repeat="item in items" id="s-{{$index}}" class="sortable-item">{{ item }}</li></ul>')($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();
});
});

});

});
5 changes: 5 additions & 0 deletions test/sortable.tests.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
.inline-block {
display: inline-block;
}

.floatleft,
.cross-sortable {
float: left;
}
Expand Down

0 comments on commit 6a7579f

Please sign in to comment.