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

Commit c062ff5

Browse files
committed
Add ui-preserve-size option
1 parent d151ca3 commit c062ff5

File tree

5 files changed

+228
-31
lines changed

5 files changed

+228
-31
lines changed

API.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# ui.item.sortable API documentation
22

3+
This refers to the additional properties that are exposed through the `ui` parameter in the provided callback hooks. eg:
4+
```js
5+
$scope.sortableOptions = {
6+
update: function(e, ui) {
7+
if (ui.item.sortable.model == "can't be moved") {
8+
ui.item.sortable.cancel();
9+
}
10+
}
11+
};
12+
```
13+
314
## Properties
415

516
**Note:**

README.md

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,15 @@ myAppModule.controller('MyController', function($scope) {
9090
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).
9191
The suggested pattern is to use callbacks for emmiting events and altering the scope (inside the 'Angular world').
9292

93-
#### Floating
93+
#### ui-floating
94+
95+
**ui-floating** (default: undefined)
96+
Description: Enables a workaround for smooth horizontal sorting.
97+
Type: [Boolean](http://api.jquery.com/Types/#Boolean)/[String](http://api.jquery.com/Types/#String)/`undefined`
98+
* **undefined**: Relies on jquery.ui to detect the list's orientation.
99+
* **false**: Forces jquery.ui.sortable to detect the list as vertical.
100+
* **true**: Forces jquery.ui.sortable to detect the list as horizontal.
101+
* **"auto"**: Detects on each drag `start` if the element is floating or not.
94102

95103
To have a smooth horizontal-list reordering, jquery.ui.sortable needs to detect the orientation of the list.
96104
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).
@@ -118,14 +126,24 @@ $scope.sortableOptions = {
118126
```
119127

120128

121-
**ui-floating** (default: undefined)
122-
Type: [Boolean](http://api.jquery.com/Types/#Boolean)/[String](http://api.jquery.com/Types/#String)/`undefined`
123-
* **undefined**: Relies on jquery.ui to detect the list's orientation.
124-
* **false**: Forces jquery.ui.sortable to detect the list as vertical.
125-
* **true**: Forces jquery.ui.sortable to detect the list as horizontal.
126-
* **"auto"**: Detects on each drag `start` if the element is floating or not.
129+
#### ui-model-items
130+
131+
**ui-model-items** (default: `> [ng-repeat],> [data-ng-repeat],> [x-ng-repeat]`)
132+
Description: Defines which elements should be considered as part of your model.
133+
Type: [CSS selector](http://api.jquery.com/Types/#Selector)/[String](http://api.jquery.com/Types/#String)
134+
135+
This is the model related counterpart option of [jQuery's items option](http://api.jqueryui.com/sortable/#option-items).
136+
137+
#### ui-preserve-size
138+
139+
**ui-preserve-size** (default: undefined)
140+
Description: Set's the size of the sorting helper to the size of the original element before the sorting.
141+
Type: [Boolean](http://api.jquery.com/Types/#Boolean)/`undefined`
142+
143+
This is useful for elements that their size is dependent to other page characteristics.
144+
A representative example of such cases are `<table>` `<tr>`s and `<td>`s.
127145

128-
#### Attributes For Event Handling
146+
### Attributes For Event Handling
129147

130148
To handle events with html bindings just define any expression to listed event attributes.
131149
If you defined an attribute for this events and defined callback function in sortableOptions at the same time, the attribute based callback will be called first.
@@ -161,7 +179,7 @@ $scope.sortableOptions = {
161179
</ul>
162180
```
163181

164-
#### Canceling
182+
### Canceling
165183

166184
Inside the `update` callback, you can check the item that is dragged and cancel the sorting.
167185

src/sortable.js

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ angular.module('ui.sortable', [])
1818
scope: {
1919
ngModel:'=',
2020
uiSortable:'=',
21-
uiPreserveSize: '=uiPreserveSize',
2221
////Expression bindings from html.
2322
create:'&uiSortableCreate',
2423
// helper:'&uiSortableHelper',
@@ -63,6 +62,19 @@ angular.module('ui.sortable', [])
6362
return null;
6463
}
6564

65+
function setItemChildrenWidth(item) {
66+
item.children().each(function() {
67+
var $el = angular.element(this);
68+
69+
// Preserve the with of the element
70+
$el.width($el.width());
71+
});
72+
}
73+
74+
function dummyHelper(e, item) {
75+
return item;
76+
}
77+
6678
function patchSortableOption(key, value) {
6779
if (callbacks[key]) {
6880
if( key === 'stop' ){
@@ -86,7 +98,8 @@ angular.module('ui.sortable', [])
8698
return value;
8799
}
88100

89-
function patchUISortableOptions(newVal, oldVal, sortableWidgetInstance) {
101+
function patchUISortableOptions(newOpts, oldOpts, sortableWidgetInstance) {
102+
90103
function addDummyOptionKey(value, key) {
91104
if (!(key in opts)) {
92105
// add the key in the opts object so that
@@ -101,11 +114,11 @@ angular.module('ui.sortable', [])
101114
// update some options of the sortable
102115
var optsDiff = null;
103116

104-
if (oldVal) {
117+
if (oldOpts) {
105118
// reset deleted options to default
106119
var defaultOptions;
107-
angular.forEach(oldVal, function(oldValue, key) {
108-
if (!newVal || !(key in newVal)) {
120+
angular.forEach(oldOpts, function(oldValue, key) {
121+
if (!newOpts || !(key in newOpts)) {
109122
if (key in directiveOpts) {
110123
if (key === 'ui-floating') {
111124
opts[key] = 'auto';
@@ -130,16 +143,33 @@ angular.module('ui.sortable', [])
130143
});
131144
}
132145

146+
newOpts = angular.extend({}, newOpts);
133147
// update changed options
134-
angular.forEach(newVal, function(value, key) {
135-
// if it's a custom option of the directive,
136-
// handle it approprietly
148+
// handle the custom option of the directive first
149+
angular.forEach(newOpts, function(value, key) {
137150
if (key in directiveOpts) {
138151
if (key === 'ui-floating' && (value === false || value === true) && sortableWidgetInstance) {
139152
sortableWidgetInstance.floating = value;
140153
}
141154

155+
if (key === 'ui-preserve-size' && (value === false || value === true)) {
156+
var userProvidedHelper = opts.helper;
157+
newOpts.helper = function(e, item) {
158+
if (opts['ui-preserve-size'] === true) {
159+
setItemChildrenWidth(item);
160+
}
161+
return (userProvidedHelper || dummyHelper).apply(this, arguments);
162+
};
163+
}
164+
142165
opts[key] = patchSortableOption(key, value);
166+
}
167+
});
168+
169+
// handle the normal option of the directive
170+
angular.forEach(newOpts, function(value, key) {
171+
if (key in directiveOpts) {
172+
// the custom option of the directive are already handled
143173
return;
144174
}
145175

@@ -198,7 +228,7 @@ angular.module('ui.sortable', [])
198228
}
199229

200230
// thanks jquery-ui
201-
function isFloating (item) {
231+
function isFloating(item) {
202232
return (/left|right/).test(item.css('float')) || (/inline|table-cell/).test(item.css('display'));
203233
}
204234

@@ -229,7 +259,8 @@ angular.module('ui.sortable', [])
229259
// directive specific options
230260
var directiveOpts = {
231261
'ui-floating': undefined,
232-
'ui-model-items': uiSortableConfig.items
262+
'ui-model-items': uiSortableConfig.items,
263+
'ui-preserve-size': undefined
233264
};
234265

235266
var callbacks = {
@@ -482,15 +513,6 @@ angular.module('ui.sortable', [])
482513
var oldItemSortable = item.sortable;
483514
var index = getItemIndex(item);
484515

485-
if (typeof scope.uiPreserveSize === 'boolean' && scope.uiPreserveSize !== false) {
486-
item.children().each(function () {
487-
var $el = angular.element(this);
488-
489-
// Preserve the with of the element
490-
$el.width($el.width());
491-
});
492-
}
493-
494516
item.sortable = {
495517
model: ngModel.$modelValue[index],
496518
index: index,
@@ -515,12 +537,12 @@ angular.module('ui.sortable', [])
515537
return inner;
516538
};
517539

518-
scope.$watchCollection('uiSortable', function(newVal, oldVal) {
540+
scope.$watchCollection('uiSortable', function(newOpts, oldOpts) {
519541
// ensure that the jquery-ui-sortable widget instance
520542
// is still bound to the directive's element
521543
var sortableWidgetInstance = getSortableWidgetInstance(element);
522544
if (!!sortableWidgetInstance) {
523-
var optsDiff = patchUISortableOptions(newVal, oldVal, sortableWidgetInstance);
545+
var optsDiff = patchUISortableOptions(newOpts, oldOpts, sortableWidgetInstance);
524546

525547
if (optsDiff) {
526548
element.sortable('option', optsDiff);

test/sortable.e2e.directiveoptions.spec.js

Lines changed: 145 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ describe('uiSortable', function() {
1212
beforeEach(module('ui.sortable'));
1313
beforeEach(module('ui.sortable.testHelper'));
1414

15-
var EXTRA_DY_PERCENTAGE, listContent, beforeLiElement, afterLiElement;
15+
var EXTRA_DY_PERCENTAGE, listContent, beforeLiElement, afterLiElement, beforeTrElement, afterTrElement;
1616

1717
beforeEach(inject(function (sortableTestHelper) {
1818
EXTRA_DY_PERCENTAGE = sortableTestHelper.EXTRA_DY_PERCENTAGE;
1919
listContent = sortableTestHelper.listContent;
2020
beforeLiElement = sortableTestHelper.extraElements && sortableTestHelper.extraElements.beforeLiElement;
2121
afterLiElement = sortableTestHelper.extraElements && sortableTestHelper.extraElements.afterLiElement;
22+
beforeTrElement = sortableTestHelper.extraElements && sortableTestHelper.extraElements.beforeTrElement;
23+
afterTrElement = sortableTestHelper.extraElements && sortableTestHelper.extraElements.afterTrElement;
2224
}));
2325

2426
tests.description = 'Custom directive options related';
@@ -32,6 +34,7 @@ describe('uiSortable', function() {
3234

3335
if (!useExtraElements) {
3436
beforeLiElement = afterLiElement = '';
37+
beforeTrElement = afterTrElement = '';
3538
}
3639
}));
3740

@@ -393,6 +396,147 @@ describe('uiSortable', function() {
393396
});
394397
});
395398

399+
it('should work when the "ui-preserve-size" option is used', function() {
400+
inject(function($compile, $rootScope) {
401+
var width = '200px';
402+
var element;
403+
element = $compile(''.concat(
404+
'<table>',
405+
'<tbody><tr><td style="width: ' + width + ';"></td></tr></tbody>',
406+
'<tbody ui-sortable="opts" ng-model="items">',
407+
beforeTrElement,
408+
'<tr ng-repeat="item in items" id="s-{{$index}}">',
409+
'<td class="sortable-item">{{ item }}</td>',
410+
'</tr>',
411+
afterTrElement,
412+
'</tbody>',
413+
'</table>'
414+
))($rootScope);
415+
416+
var itemsSelector = '.sortable-item';
417+
$rootScope.$apply(function() {
418+
$rootScope.opts = {
419+
'ui-preserve-size': true,
420+
stop: function(e, ui) {
421+
expect(ui.item.children().css('width')).toEqual(width);
422+
}
423+
};
424+
$rootScope.items = ['One', 'Two', 'Three'];
425+
});
426+
427+
host.append(element).append('<div class="clear"></div>');
428+
429+
var tr = element.find(itemsSelector + ':eq(1)');
430+
var dy = (1 + EXTRA_DY_PERCENTAGE) * tr.outerHeight();
431+
tr.simulate('drag', { dy: dy });
432+
expect($rootScope.items).toEqual(['One', 'Three', 'Two']);
433+
expect($rootScope.items).toEqual(listContent(element.find('tbody')).map($).map($.text));
434+
435+
tr = element.find(itemsSelector + ':eq(1)');
436+
dy = -(1 + EXTRA_DY_PERCENTAGE) * tr.outerHeight();
437+
tr.simulate('drag', { dy: dy });
438+
expect($rootScope.items).toEqual(['Three', 'One', 'Two']);
439+
expect($rootScope.items).toEqual(listContent(element.find('tbody')).map($).map($.text));
440+
441+
$(element).remove();
442+
});
443+
});
444+
445+
it('should work when the "ui-preserve-size" option is false', function() {
446+
inject(function($compile, $rootScope) {
447+
var width = '200px';
448+
var element;
449+
element = $compile(''.concat(
450+
'<table>',
451+
'<tbody><tr><td style="width: ' + width + ';"></td></tr></tbody>',
452+
'<tbody ui-sortable="opts" ng-model="items">',
453+
beforeTrElement,
454+
'<tr ng-repeat="item in items" id="s-{{$index}}">',
455+
'<td class="sortable-item">{{ item }}</td>',
456+
'</tr>',
457+
afterTrElement,
458+
'</tbody>',
459+
'</table>'
460+
))($rootScope);
461+
462+
var itemsSelector = '.sortable-item';
463+
$rootScope.$apply(function() {
464+
$rootScope.opts = {
465+
'ui-preserve-size': false,
466+
stop: function(e, ui) {
467+
expect(ui.item.children().attr('style')).toEqual(undefined);
468+
}
469+
};
470+
$rootScope.items = ['One', 'Two', 'Three'];
471+
});
472+
473+
host.append(element).append('<div class="clear"></div>');
474+
475+
var tr = element.find(itemsSelector + ':eq(1)');
476+
var dy = (1 + EXTRA_DY_PERCENTAGE) * tr.outerHeight();
477+
tr.simulate('drag', { dy: dy });
478+
expect($rootScope.items).toEqual(['One', 'Three', 'Two']);
479+
expect($rootScope.items).toEqual(listContent(element.find('tbody')).map($).map($.text));
480+
481+
tr = element.find(itemsSelector + ':eq(1)');
482+
dy = -(1 + EXTRA_DY_PERCENTAGE) * tr.outerHeight();
483+
tr.simulate('drag', { dy: dy });
484+
expect($rootScope.items).toEqual(['Three', 'One', 'Two']);
485+
expect($rootScope.items).toEqual(listContent(element.find('tbody')).map($).map($.text));
486+
487+
$(element).remove();
488+
});
489+
});
490+
491+
it('should work when the "ui-preserve-size" & helper options are used', function() {
492+
inject(function($compile, $rootScope) {
493+
var width = '200px';
494+
var element;
495+
element = $compile(''.concat(
496+
'<table>',
497+
'<tbody><tr><td style="width: ' + width + ';"></td></tr></tbody>',
498+
'<tbody ui-sortable="opts" ng-model="items">',
499+
beforeTrElement,
500+
'<tr ng-repeat="item in items" id="s-{{$index}}">',
501+
'<td class="sortable-item">{{ item }}</td>',
502+
'</tr>',
503+
afterTrElement,
504+
'</tbody>',
505+
'</table>'
506+
))($rootScope);
507+
508+
var itemsSelector = '.sortable-item';
509+
$rootScope.$apply(function() {
510+
$rootScope.opts = {
511+
'ui-preserve-size': true,
512+
helper: function (e, item) {
513+
return item.clone();
514+
},
515+
stop: function(e, ui) {
516+
expect(ui.item.children().css('width')).toEqual(width);
517+
}
518+
};
519+
$rootScope.items = ['One', 'Two', 'Three'];
520+
});
521+
522+
host.append(element).append('<div class="clear"></div>');
523+
524+
var tr = element.find(itemsSelector + ':eq(1)');
525+
var dy = (1 + EXTRA_DY_PERCENTAGE) * tr.outerHeight();
526+
tr.simulate('drag', { dy: dy });
527+
expect($rootScope.items).toEqual(['One', 'Three', 'Two']);
528+
expect($rootScope.items).toEqual(listContent(element.find('tbody')).map($).map($.text));
529+
530+
tr = element.find(itemsSelector + ':eq(1)');
531+
dy = -(1 + EXTRA_DY_PERCENTAGE) * tr.outerHeight();
532+
tr.simulate('drag', { dy: dy });
533+
expect($rootScope.items).toEqual(['Three', 'One', 'Two']);
534+
expect($rootScope.items).toEqual(listContent(element.find('tbody')).map($).map($.text));
535+
536+
$(element).remove();
537+
});
538+
});
539+
396540
}
397541

398542
[0, 1].forEach(function(useExtraElements){

test/sortable.test-helper.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ angular.module('ui.sortable.testHelper', [])
102102
extraElements: {
103103
beforeLiElement: '<li>extra element</li>',
104104
afterLiElement: '<li>extra element</li>',
105+
beforeTrElement: '<tr><td>extra element</td></tr>',
106+
afterTrElement: '<tr><td>extra element</td></tr>',
105107
beforeDivElement: '<div>extra element</div>',
106108
afterDivElement: '<div>extra element</div>'
107109
}

0 commit comments

Comments
 (0)