diff --git a/API.md b/API.md
new file mode 100644
index 0000000..afd9223
--- /dev/null
+++ b/API.md
@@ -0,0 +1,57 @@
+# ui.item.sortable API documentation
+
+## Properties
+
+### dropindex
+Type: [Integer](http://api.jquery.com/Types/#Integer)
+Holds the index of the drop target that the dragged item was dropped.
+
+
+### droptarget
+Type: [jQuery](http://api.jquery.com/Types/#jQuery)
+Holds the ui-sortable element that the dragged item was dropped on.
+
+### droptargetModel
+Type: [Array](http://api.jquery.com/Types/#Array)
+Holds the array that is specified by the `ng-model` attribute of the [`droptarget`](#droptarget) ui-sortable element.
+
+### index
+Type: [Integer](http://api.jquery.com/Types/#Integer)
+Holds the original index of the item dragged.
+
+### model
+Type: [Object](http://api.jquery.com/Types/#Object)
+Holds the JavaScript object that is used as the model of the dragged item, as specified by the ng-repeat of the [`source`](#source) ui-sortable element and the item's [`index`](#index).
+
+### moved
+Type: [Object](http://api.jquery.com/Types/#Object)/`undefined`
+Holds the model of the dragged item only when a sorting happens between two connected ui-sortable elements.
+In other words: `'moved' in ui.item.sortable` will return false only when a sorting is withing the same ui-sortable element ([`source`](#source) equals to the [`droptarget`](#droptarget)).
+
+### received
+Type: [Boolean](http://api.jquery.com/Types/#Boolean)
+When sorting between two connected sortables, it will be set to true inside the `update` callback of the [`droptarget`](#droptarget).
+
+### source
+Type: [jQuery](http://api.jquery.com/Types/#jQuery)
+Holds the ui-sortable element that the dragged item originated from.
+
+### sourceModel
+Type: [Array](http://api.jquery.com/Types/#Array)
+Holds the array that is specified by the `ng-model` of the [`source`](#source) ui-sortable element.
+
+
+## Methods
+
+### cancel[()](http://api.jquery.com/Types/#Function)
+Returns: Nothing
+Can be called inside the `update` callback, in order to prevent/revert a sorting.
+Should be used instead of the [jquery-ui-sortable cancel()](http://api.jqueryui.com/sortable/#method-cancel) method.
+
+### isCanceled[()](http://api.jquery.com/Types/#Function)
+Returns: [Boolean](http://api.jquery.com/Types/#Boolean)
+Returns whether the current sorting is marked as canceled, by an earlier call to [`ui.item.sortable.cancel()`](#cancel).
+
+### isCustomHelperUsed[()](http://api.jquery.com/Types/#Function)
+Returns: [Boolean](http://api.jquery.com/Types/#Boolean)
+Returns whether the [`helper`](http://api.jqueryui.com/sortable/#option-helper) element used for the current sorting, is one of the original ui-sortable list elements.
diff --git a/README.md b/README.md
index a2c445a..72c1a94 100644
--- a/README.md
+++ b/README.md
@@ -42,11 +42,12 @@ Apply the directive to your form elements:
* `ui-sortable` element should only contain one `ng-repeat` and not any other elements (above or below).
Otherwise the index matching of the generated DOM elements and the `ng-model`'s items will break.
**In other words: The items of `ng-model` must match the indexes of the generated DOM elements.**
-* `ui-sortable` lists containing many 'types' of items can be implemented by using dynamic template loading [with ng-include](http://stackoverflow.com/questions/14607879/angularjs-load-dynamic-template-html-within-directive/14621927#14621927) or a [loader directive](https://gist.github.com/thgreasi/7152499c0e91973c4820), to determine how each model item should be rendered. Also take a look at the [Tree with dynamic template](http://codepen.io/thgreasi/pen/uyHFC) example.
+* `ui-sortable` lists containing many 'types' of items can be implemented by using dynamic template loading [with ng-include](http://stackoverflow.com/questions/14607879/angularjs-load-dynamic-template-html-within-directive/14621927#14621927) or a [loader directive](https://github.com/thgreasi/tg-dynamic-directive), to determine how each model item should be rendered. Also take a look at the [Tree with dynamic template](http://codepen.io/thgreasi/pen/uyHFC) example.
### Options
-All the [jQueryUI Sortable options](http://api.jqueryui.com/sortable/) can be passed through the directive.
+All the [jQueryUI Sortable options](http://api.jqueryui.com/sortable/) can be passed through the directive.
+Additionally, the `ui` argument of the available callbacks gets enriched with some extra properties as specified to the [API.md file](API.md#uiitemsortable-api-documentation).
```js
@@ -69,6 +70,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
+
+```
+
+**OR**
+
+```js
+$scope.sortableOptions = {
+ 'ui-floating': true
+};
+```
+```html
+
+```
+
+
+**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 +174,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)
diff --git a/bower.json b/bower.json
index e5fe9f1..b4c923b 100644
--- a/bower.json
+++ b/bower.json
@@ -1,6 +1,6 @@
{
"name": "angular-ui-sortable",
- "version": "0.12.11",
+ "version": "0.13.0",
"description": "This directive allows you to jQueryUI Sortable.",
"author": "https://github.com/angular-ui/ui-sortable/graphs/contributors",
"license": "MIT",
@@ -16,11 +16,11 @@
"package.json"
],
"dependencies": {
- "angular": "~1.2.x",
+ "angular": ">=1.2.x",
"jquery-ui": ">=1.9"
},
"devDependencies": {
- "angular-mocks": "~1.2.x",
+ "angular-mocks": ">=1.2.x",
"jquery-simulate": "latest"
}
}
diff --git a/package.json b/package.json
index 77b2d78..b865815 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "angular-ui-sortable",
- "version": "0.12.11",
+ "version": "0.13.0",
"description": "This directive allows you to jQueryUI Sortable.",
"author": "https://github.com/angular-ui/ui-sortable/graphs/contributors",
"license": "MIT",
diff --git a/src/sortable.js b/src/sortable.js
index 065179c..7eb5e39 100644
--- a/src/sortable.js
+++ b/src/sortable.js
@@ -28,8 +28,22 @@ 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'));
+ }
+
+ function afterStop(e, ui) {
+ ui.item.sortable._destroy();
+ }
+
var opts = {};
+ // directive specific options
+ var directiveOpts = {
+ 'ui-floating': undefined
+ };
+
var callbacks = {
receive: null,
remove:null,
@@ -42,7 +56,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,9 +79,19 @@ 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 = {
+ model: ngModel.$modelValue[ui.item.index()],
index: ui.item.index(),
+ source: ui.item.parent(),
+ sourceModel: ngModel.$modelValue,
cancel: function () {
ui.item.sortable._isCanceled = true;
},
@@ -78,7 +102,12 @@ angular.module('ui.sortable', [])
return !!ui.item.sortable._isCustomHelperUsed;
},
_isCanceled: false,
- _isCustomHelperUsed: ui.item.sortable._isCustomHelperUsed
+ _isCustomHelperUsed: ui.item.sortable._isCustomHelperUsed,
+ _destroy: function () {
+ angular.forEach(ui.item.sortable, function(value, key) {
+ ui.item.sortable[key] = undefined;
+ });
+ }
};
};
@@ -119,7 +148,9 @@ angular.module('ui.sortable', [])
// the value will be overwritten with the old value
if(!ui.item.sortable.received) {
ui.item.sortable.dropindex = ui.item.index();
- ui.item.sortable.droptarget = ui.item.parent();
+ var droptarget = ui.item.parent();
+ ui.item.sortable.droptarget = droptarget;
+ ui.item.sortable.droptargetModel = droptarget.scope().$eval(droptarget.attr('ng-model'));
// Cancel the sort (let ng-repeat do the sort for us)
// Don't cancel if this is the received list because it has
@@ -229,11 +260,24 @@ 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(
value, function() { scope.$apply(); });
+
+ value = combineCallbacks(value, afterStop);
}
// wrap the callback
value = combineCallbacks(callbacks[key], value);
@@ -241,6 +285,7 @@ angular.module('ui.sortable', [])
value = wrappers[key](value);
}
+ opts[key] = value;
element.sortable('option', key, value);
});
}
@@ -248,6 +293,9 @@ angular.module('ui.sortable', [])
angular.forEach(callbacks, function(value, key) {
opts[key] = combineCallbacks(value, opts[key]);
+ if( key === 'stop' ){
+ opts[key] = combineCallbacks(opts[key], afterStop);
+ }
});
} else {
diff --git a/test/sortable.e2e.callbacks.spec.js b/test/sortable.e2e.callbacks.spec.js
index 3902792..50b182b 100644
--- a/test/sortable.e2e.callbacks.spec.js
+++ b/test/sortable.e2e.callbacks.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, hasUndefinedProperties;
beforeEach(inject(function (sortableTestHelper) {
EXTRA_DY_PERCENTAGE = sortableTestHelper.EXTRA_DY_PERCENTAGE;
listContent = sortableTestHelper.listContent;
+ hasUndefinedProperties = sortableTestHelper.hasUndefinedProperties;
}));
describe('Callbacks related', function() {
@@ -72,6 +73,9 @@ describe('uiSortable', function() {
element = $compile('')($rootScope);
$rootScope.$apply(function() {
$rootScope.opts = {
+ helper: function (e, item) {
+ return item;
+ },
update: function(e, ui) {
if (ui.item.scope().item === 'Two') {
ui.item.sortable.cancel();
@@ -188,6 +192,133 @@ describe('uiSortable', function() {
});
});
+ it('should properly set ui.item.sortable properties', function() {
+ inject(function($compile, $rootScope) {
+ var element, updateCallbackExpectations;
+ element = $compile('')($rootScope);
+ $rootScope.$apply(function() {
+ $rootScope.opts = {
+ update: function(e, ui) {
+ if (ui.item.scope().item === 'Two') {
+ ui.item.sortable.cancel();
+ }
+ updateCallbackExpectations(ui.item.sortable);
+ }
+ };
+ $rootScope.items = ['One', 'Two', 'Three'];
+ });
+
+ host.append(element);
+
+ $rootScope.$apply(function() {
+ });
+ var li = element.find(':eq(1)');
+ var dy = (1 + EXTRA_DY_PERCENTAGE) * li.outerHeight();
+ updateCallbackExpectations = function(uiItemSortable) {
+ expect(uiItemSortable.model).toEqual('Two');
+ expect(uiItemSortable.index).toEqual(1);
+ expect(uiItemSortable.source.length).toEqual(1);
+ expect(uiItemSortable.source[0]).toBe(host.children()[0]);
+ expect(uiItemSortable.sourceModel).toBe($rootScope.items);
+ expect(uiItemSortable.isCanceled()).toBe(true);
+ expect(uiItemSortable.isCustomHelperUsed()).toBe(false);
+ };
+ li.simulate('drag', { dy: dy });
+ expect($rootScope.items).toEqual(['One', 'Two', 'Three']);
+ expect($rootScope.items).toEqual(listContent(element));
+ updateCallbackExpectations = undefined;
+
+ li = element.find(':eq(0)');
+ dy = (2 + EXTRA_DY_PERCENTAGE) * li.outerHeight();
+ updateCallbackExpectations = function(uiItemSortable) {
+ expect(uiItemSortable.model).toEqual('One');
+ expect(uiItemSortable.index).toEqual(0);
+ expect(uiItemSortable.source.length).toEqual(1);
+ expect(uiItemSortable.source[0]).toBe(host.children()[0]);
+ expect(uiItemSortable.sourceModel).toBe($rootScope.items);
+ expect(uiItemSortable.isCanceled()).toBe(false);
+ expect(uiItemSortable.isCustomHelperUsed()).toBe(false);
+ };
+ li.simulate('drag', { dy: dy });
+ expect($rootScope.items).toEqual(['Two', 'Three', 'One']);
+ expect($rootScope.items).toEqual(listContent(element));
+ updateCallbackExpectations = undefined;
+
+ li = element.find(':eq(2)');
+ dy = -(2 + EXTRA_DY_PERCENTAGE) * li.outerHeight();
+ updateCallbackExpectations = function(uiItemSortable) {
+ expect(uiItemSortable.model).toEqual('One');
+ expect(uiItemSortable.index).toEqual(2);
+ expect(uiItemSortable.source.length).toEqual(1);
+ expect(uiItemSortable.source[0]).toBe(host.children()[0]);
+ expect(uiItemSortable.sourceModel).toBe($rootScope.items);
+ expect(uiItemSortable.isCanceled()).toBe(false);
+ expect(uiItemSortable.isCustomHelperUsed()).toBe(false);
+ };
+ li.simulate('drag', { dy: dy });
+ expect($rootScope.items).toEqual(['One', 'Two', 'Three']);
+ expect($rootScope.items).toEqual(listContent(element));
+ updateCallbackExpectations = undefined;
+
+ $(element).remove();
+ });
+ });
+
+ it('should properly free ui.item.sortable object', function() {
+ inject(function($compile, $rootScope) {
+ var element, uiItem, uiItemSortable_Destroy;
+ element = $compile('')($rootScope);
+ $rootScope.$apply(function() {
+ $rootScope.opts = {
+ start: function (e, ui) {
+ uiItem = ui.item;
+ spyOn(ui.item.sortable, '_destroy').andCallThrough();
+ uiItemSortable_Destroy = ui.item.sortable._destroy;
+ },
+ update: function(e, ui) {
+ if (ui.item.scope().item === 'Two') {
+ ui.item.sortable.cancel();
+ }
+ }
+ };
+ $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', 'Two', 'Three']);
+ expect($rootScope.items).toEqual(listContent(element));
+ expect(uiItemSortable_Destroy).toHaveBeenCalled();
+ expect(hasUndefinedProperties(uiItem.sortable)).toBe(true);
+ uiItem = uiItemSortable_Destroy = undefined;
+
+
+ li = element.find(':eq(0)');
+ dy = (2 + EXTRA_DY_PERCENTAGE) * li.outerHeight();
+ li.simulate('drag', { dy: dy });
+ expect($rootScope.items).toEqual(['Two', 'Three', 'One']);
+ expect($rootScope.items).toEqual(listContent(element));
+ expect(uiItemSortable_Destroy).toHaveBeenCalled();
+ expect(hasUndefinedProperties(uiItem.sortable)).toBe(true);
+ uiItem = uiItemSortable_Destroy = undefined;
+
+
+ li = element.find(':eq(2)');
+ dy = -(2 + EXTRA_DY_PERCENTAGE) * li.outerHeight();
+ li.simulate('drag', { dy: dy });
+ expect($rootScope.items).toEqual(['One', 'Two', 'Three']);
+ expect($rootScope.items).toEqual(listContent(element));
+ expect(uiItemSortable_Destroy).toHaveBeenCalled();
+ expect(hasUndefinedProperties(uiItem.sortable)).toBe(true);
+ uiItem = uiItemSortable_Destroy = undefined;
+
+ $(element).remove();
+ });
+ });
+
});
});
\ No newline at end of file
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('')($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('')($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('')($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('')($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.e2e.multi.spec.js b/test/sortable.e2e.multi.spec.js
index 860d349..a9cebae 100644
--- a/test/sortable.e2e.multi.spec.js
+++ b/test/sortable.e2e.multi.spec.js
@@ -6,13 +6,14 @@ describe('uiSortable', function() {
beforeEach(module('ui.sortable'));
beforeEach(module('ui.sortable.testHelper'));
- var EXTRA_DY_PERCENTAGE, listContent, listInnerContent, simulateElementDrag;
+ var EXTRA_DY_PERCENTAGE, listContent, listInnerContent, simulateElementDrag, hasUndefinedProperties;
beforeEach(inject(function (sortableTestHelper) {
EXTRA_DY_PERCENTAGE = sortableTestHelper.EXTRA_DY_PERCENTAGE;
listContent = sortableTestHelper.listContent;
listInnerContent = sortableTestHelper.listInnerContent;
simulateElementDrag = sortableTestHelper.simulateElementDrag;
+ hasUndefinedProperties = sortableTestHelper.hasUndefinedProperties;
}));
describe('Multiple sortables related', function() {
@@ -470,6 +471,222 @@ describe('uiSortable', function() {
});
});
+ it('should properly set ui.item.sortable properties', function() {
+ inject(function($compile, $rootScope) {
+ var elementTop, elementBottom, updateCallbackExpectations, stopCallbackExpectations;
+ elementTop = $compile('')($rootScope);
+ elementBottom = $compile('')($rootScope);
+ $rootScope.$apply(function() {
+ $rootScope.itemsTop = ['Top One', 'Top Two', 'Top Three'];
+ $rootScope.itemsBottom = ['Bottom One', 'Bottom Two', 'Bottom Three'];
+ $rootScope.opts = {
+ connectWith: '.cross-sortable',
+ update: function(e, ui) {
+ if (ui.item.scope() &&
+ (typeof ui.item.scope().item === 'string') &&
+ ui.item.scope().item.indexOf('Two') >= 0) {
+ ui.item.sortable.cancel();
+ }
+ updateCallbackExpectations(ui.item.sortable);
+ },
+ stop: function(e, ui) {
+ stopCallbackExpectations(ui.item.sortable);
+ }
+ };
+ });
+
+ host.append(elementTop).append(elementBottom).append('');
+
+ var li1 = elementTop.find(':eq(1)');
+ var li2 = elementBottom.find(':eq(0)');
+ updateCallbackExpectations = function(uiItemSortable) {
+ expect(uiItemSortable.model).toEqual('Top Two');
+ expect(uiItemSortable.index).toEqual(1);
+ expect(uiItemSortable.source.length).toEqual(1);
+ expect(uiItemSortable.source[0]).toBe(host.children()[0]);
+ expect(uiItemSortable.sourceModel).toBe($rootScope.itemsTop);
+ expect(uiItemSortable.isCanceled()).toBe(true);
+ expect(uiItemSortable.isCustomHelperUsed()).toBe(false);
+
+ expect(uiItemSortable.dropindex).toEqual(1);
+ expect(uiItemSortable.droptarget.length).toBe(1);
+ expect(uiItemSortable.droptarget[0]).toBe(host.children()[1]);
+ expect(uiItemSortable.droptargetModel).toBe($rootScope.itemsBottom);
+ };
+ stopCallbackExpectations = function(uiItemSortable) {
+ expect(uiItemSortable.received).toBe(true);
+ expect(uiItemSortable.moved).toBe(undefined);
+ };
+ simulateElementDrag(li1, li2, { place: 'below', extradx: -20, extrady: -10 });
+ expect($rootScope.itemsTop).toEqual(['Top One', 'Top Two', 'Top Three']);
+ expect($rootScope.itemsBottom).toEqual(['Bottom One', 'Bottom Two', 'Bottom Three']);
+ expect($rootScope.itemsTop).toEqual(listContent(elementTop));
+ expect($rootScope.itemsBottom).toEqual(listContent(elementBottom));
+ updateCallbackExpectations = stopCallbackExpectations = undefined;
+
+ li1 = elementBottom.find(':eq(1)');
+ li2 = elementTop.find(':eq(1)');
+ updateCallbackExpectations = function(uiItemSortable) {
+ expect(uiItemSortable.model).toEqual('Bottom Two');
+ expect(uiItemSortable.index).toEqual(1);
+ expect(uiItemSortable.source.length).toEqual(1);
+ expect(uiItemSortable.source[0]).toBe(host.children()[1]);
+ expect(uiItemSortable.sourceModel).toBe($rootScope.itemsBottom);
+ expect(uiItemSortable.isCanceled()).toBe(true);
+ expect(uiItemSortable.isCustomHelperUsed()).toBe(false);
+
+ expect(uiItemSortable.dropindex).toEqual(1);
+ expect(uiItemSortable.droptarget.length).toBe(1);
+ expect(uiItemSortable.droptarget[0]).toBe(host.children()[0]);
+ expect(uiItemSortable.droptargetModel).toBe($rootScope.itemsTop);
+ };
+ stopCallbackExpectations = function(uiItemSortable) {
+ expect(uiItemSortable.received).toBe(true);
+ expect(uiItemSortable.moved).toBe(undefined);
+ };
+ simulateElementDrag(li1, li2, { place: 'above', extradx: -20, extrady: -10 });
+ expect($rootScope.itemsTop).toEqual(['Top One', 'Top Two', 'Top Three']);
+ expect($rootScope.itemsBottom).toEqual(['Bottom One', 'Bottom Two', 'Bottom Three']);
+ expect($rootScope.itemsTop).toEqual(listContent(elementTop));
+ expect($rootScope.itemsBottom).toEqual(listContent(elementBottom));
+ updateCallbackExpectations = stopCallbackExpectations = undefined;
+
+ li1 = elementTop.find(':eq(0)');
+ li2 = elementBottom.find(':eq(0)');
+ updateCallbackExpectations = function(uiItemSortable) {
+ expect(uiItemSortable.model).toEqual('Top One');
+ expect(uiItemSortable.index).toEqual(0);
+ expect(uiItemSortable.source.length).toEqual(1);
+ expect(uiItemSortable.source[0]).toBe(host.children()[0]);
+ expect(uiItemSortable.sourceModel).toBe($rootScope.itemsTop);
+ expect(uiItemSortable.isCanceled()).toBe(false);
+ expect(uiItemSortable.isCustomHelperUsed()).toBe(false);
+
+ expect(uiItemSortable.dropindex).toEqual(1);
+ expect(uiItemSortable.droptarget.length).toBe(1);
+ expect(uiItemSortable.droptarget[0]).toBe(host.children()[1]);
+ expect(uiItemSortable.droptargetModel).toBe($rootScope.itemsBottom);
+ };
+ stopCallbackExpectations = function(uiItemSortable) {
+ expect(uiItemSortable.received).toBe(true);
+ expect(uiItemSortable.moved).toBe('Top One');
+ };
+ simulateElementDrag(li1, li2, 'below');
+ expect($rootScope.itemsTop).toEqual(['Top Two', 'Top Three']);
+ expect($rootScope.itemsBottom).toEqual(['Bottom One', 'Top One', 'Bottom Two', 'Bottom Three']);
+ expect($rootScope.itemsTop).toEqual(listContent(elementTop));
+ expect($rootScope.itemsBottom).toEqual(listContent(elementBottom));
+ updateCallbackExpectations = stopCallbackExpectations = undefined;
+
+ li1 = elementBottom.find(':eq(1)');
+ li2 = elementTop.find(':eq(1)');
+ updateCallbackExpectations = function(uiItemSortable) {
+ expect(uiItemSortable.model).toEqual('Top One');
+ expect(uiItemSortable.index).toEqual(1);
+ expect(uiItemSortable.source.length).toEqual(1);
+ expect(uiItemSortable.source[0]).toBe(host.children()[1]);
+ expect(uiItemSortable.sourceModel).toBe($rootScope.itemsBottom);
+ expect(uiItemSortable.isCanceled()).toBe(false);
+ expect(uiItemSortable.isCustomHelperUsed()).toBe(false);
+
+ expect(uiItemSortable.dropindex).toEqual(1);
+ expect(uiItemSortable.droptarget.length).toBe(1);
+ expect(uiItemSortable.droptarget[0]).toBe(host.children()[0]);
+ expect(uiItemSortable.droptargetModel).toBe($rootScope.itemsTop);
+ };
+ stopCallbackExpectations = function(uiItemSortable) {
+ expect(uiItemSortable.received).toBe(true);
+ expect(uiItemSortable.moved).toBe('Top One');
+ };
+ simulateElementDrag(li1, li2, { place: 'above', extradx: -20, extrady: -10 });
+ expect($rootScope.itemsTop).toEqual(['Top Two', 'Top One', 'Top Three']);
+ expect($rootScope.itemsBottom).toEqual(['Bottom One', 'Bottom Two', 'Bottom Three']);
+ expect($rootScope.itemsTop).toEqual(listContent(elementTop));
+ expect($rootScope.itemsBottom).toEqual(listContent(elementBottom));
+ updateCallbackExpectations = stopCallbackExpectations = undefined;
+
+ $(elementTop).remove();
+ $(elementBottom).remove();
+ });
+ });
+
+ it('should properly free ui.item.sortable object', function() {
+ inject(function($compile, $rootScope) {
+ var elementTop, elementBottom, uiItem, uiItemSortable_Destroy;
+ elementTop = $compile('')($rootScope);
+ elementBottom = $compile('')($rootScope);
+ $rootScope.$apply(function() {
+ $rootScope.itemsTop = ['Top One', 'Top Two', 'Top Three'];
+ $rootScope.itemsBottom = ['Bottom One', 'Bottom Two', 'Bottom Three'];
+ $rootScope.opts = {
+ connectWith: '.cross-sortable',
+ start: function (e, ui) {
+ uiItem = ui.item;
+ spyOn(ui.item.sortable, '_destroy').andCallThrough();
+ uiItemSortable_Destroy = ui.item.sortable._destroy;
+ },
+ update: function(e, ui) {
+ uiItem.sortable = ui.item.sortable;
+ if (ui.item.scope() &&
+ (typeof ui.item.scope().item === 'string') &&
+ ui.item.scope().item.indexOf('Two') >= 0) {
+ ui.item.sortable.cancel();
+ }
+ }
+ };
+ });
+
+ host.append(elementTop).append(elementBottom).append('');
+
+ var li1 = elementTop.find(':eq(1)');
+ var li2 = elementBottom.find(':eq(0)');
+ simulateElementDrag(li1, li2, 'below');
+ expect($rootScope.itemsTop).toEqual(['Top One', 'Top Two', 'Top Three']);
+ expect($rootScope.itemsBottom).toEqual(['Bottom One', 'Bottom Two', 'Bottom Three']);
+ expect($rootScope.itemsTop).toEqual(listContent(elementTop));
+ expect($rootScope.itemsBottom).toEqual(listContent(elementBottom));
+ expect(uiItemSortable_Destroy).toHaveBeenCalled();
+ expect(hasUndefinedProperties(uiItem.sortable)).toBe(true);
+ uiItem = uiItemSortable_Destroy = undefined;
+
+ li1 = elementBottom.find(':eq(1)');
+ li2 = elementTop.find(':eq(1)');
+ simulateElementDrag(li1, li2, { place: 'above', extradx: -20, extrady: -10 });
+ expect($rootScope.itemsTop).toEqual(['Top One', 'Top Two', 'Top Three']);
+ expect($rootScope.itemsBottom).toEqual(['Bottom One', 'Bottom Two', 'Bottom Three']);
+ expect($rootScope.itemsTop).toEqual(listContent(elementTop));
+ expect($rootScope.itemsBottom).toEqual(listContent(elementBottom));
+ expect(uiItemSortable_Destroy).toHaveBeenCalled();
+ expect(hasUndefinedProperties(uiItem.sortable)).toBe(true);
+ uiItem = uiItemSortable_Destroy = undefined;
+
+ li1 = elementTop.find(':eq(0)');
+ li2 = elementBottom.find(':eq(0)');
+ simulateElementDrag(li1, li2, 'below');
+ expect($rootScope.itemsTop).toEqual(['Top Two', 'Top Three']);
+ expect($rootScope.itemsBottom).toEqual(['Bottom One', 'Top One', 'Bottom Two', 'Bottom Three']);
+ expect($rootScope.itemsTop).toEqual(listContent(elementTop));
+ expect($rootScope.itemsBottom).toEqual(listContent(elementBottom));
+ expect(uiItemSortable_Destroy).toHaveBeenCalled();
+ expect(hasUndefinedProperties(uiItem.sortable)).toBe(true);
+ uiItem = uiItemSortable_Destroy = undefined;
+
+ li1 = elementBottom.find(':eq(1)');
+ li2 = elementTop.find(':eq(1)');
+ simulateElementDrag(li1, li2, { place: 'above', extradx: -20, extrady: -10 });
+ expect($rootScope.itemsTop).toEqual(['Top Two', 'Top One', 'Top Three']);
+ expect($rootScope.itemsBottom).toEqual(['Bottom One', 'Bottom Two', 'Bottom Three']);
+ expect($rootScope.itemsTop).toEqual(listContent(elementTop));
+ expect($rootScope.itemsBottom).toEqual(listContent(elementBottom));
+ expect(uiItemSortable_Destroy).toHaveBeenCalled();
+ expect(hasUndefinedProperties(uiItem.sortable)).toBe(true);
+ uiItem = uiItemSortable_Destroy = undefined;
+
+ $(elementTop).remove();
+ $(elementBottom).remove();
+ });
+ });
+
});
});
\ No newline at end of file
diff --git a/test/sortable.test-helper.js b/test/sortable.test-helper.js
index 2b5bf90..4da4d45 100644
--- a/test/sortable.test-helper.js
+++ b/test/sortable.test-helper.js
@@ -62,10 +62,20 @@ angular.module('ui.sortable.testHelper', [])
draggedElement.simulate('drag', dragOptions);
}
+ function hasUndefinedProperties(testObject) {
+ return testObject && Object.keys(testObject)
+ .filter(function(key) {
+ return testObject.hasOwnProperty(key) &&
+ testObject[key] !== undefined;
+ })
+ .length === 0;
+ }
+
return {
EXTRA_DY_PERCENTAGE: EXTRA_DY_PERCENTAGE,
listContent: listContent,
listInnerContent: listInnerContent,
- simulateElementDrag: simulateElementDrag
+ simulateElementDrag: simulateElementDrag,
+ hasUndefinedProperties: hasUndefinedProperties
};
});
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;
}