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

Commit c66dd94

Browse files
authored
Merge pull request #543 from thgreasi/ui-preserve-size
feat: add ui-preserve-size option
2 parents 17c4158 + c062ff5 commit c66dd94

File tree

5 files changed

+229
-21
lines changed

5 files changed

+229
-21
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: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,19 @@ angular.module('ui.sortable', [])
6262
return null;
6363
}
6464

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+
6578
function patchSortableOption(key, value) {
6679
if (callbacks[key]) {
6780
if( key === 'stop' ){
@@ -85,7 +98,8 @@ angular.module('ui.sortable', [])
8598
return value;
8699
}
87100

88-
function patchUISortableOptions(newVal, oldVal, sortableWidgetInstance) {
101+
function patchUISortableOptions(newOpts, oldOpts, sortableWidgetInstance) {
102+
89103
function addDummyOptionKey(value, key) {
90104
if (!(key in opts)) {
91105
// add the key in the opts object so that
@@ -100,11 +114,11 @@ angular.module('ui.sortable', [])
100114
// update some options of the sortable
101115
var optsDiff = null;
102116

103-
if (oldVal) {
117+
if (oldOpts) {
104118
// reset deleted options to default
105119
var defaultOptions;
106-
angular.forEach(oldVal, function(oldValue, key) {
107-
if (!newVal || !(key in newVal)) {
120+
angular.forEach(oldOpts, function(oldValue, key) {
121+
if (!newOpts || !(key in newOpts)) {
108122
if (key in directiveOpts) {
109123
if (key === 'ui-floating') {
110124
opts[key] = 'auto';
@@ -129,16 +143,33 @@ angular.module('ui.sortable', [])
129143
});
130144
}
131145

146+
newOpts = angular.extend({}, newOpts);
132147
// update changed options
133-
angular.forEach(newVal, function(value, key) {
134-
// if it's a custom option of the directive,
135-
// handle it approprietly
148+
// handle the custom option of the directive first
149+
angular.forEach(newOpts, function(value, key) {
136150
if (key in directiveOpts) {
137151
if (key === 'ui-floating' && (value === false || value === true) && sortableWidgetInstance) {
138152
sortableWidgetInstance.floating = value;
139153
}
140154

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+
141165
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
142173
return;
143174
}
144175

@@ -197,7 +228,7 @@ angular.module('ui.sortable', [])
197228
}
198229

199230
// thanks jquery-ui
200-
function isFloating (item) {
231+
function isFloating(item) {
201232
return (/left|right/).test(item.css('float')) || (/inline|table-cell/).test(item.css('display'));
202233
}
203234

@@ -228,7 +259,8 @@ angular.module('ui.sortable', [])
228259
// directive specific options
229260
var directiveOpts = {
230261
'ui-floating': undefined,
231-
'ui-model-items': uiSortableConfig.items
262+
'ui-model-items': uiSortableConfig.items,
263+
'ui-preserve-size': undefined
232264
};
233265

234266
var callbacks = {
@@ -480,6 +512,7 @@ angular.module('ui.sortable', [])
480512
return function (e, item) {
481513
var oldItemSortable = item.sortable;
482514
var index = getItemIndex(item);
515+
483516
item.sortable = {
484517
model: ngModel.$modelValue[index],
485518
index: index,
@@ -504,12 +537,12 @@ angular.module('ui.sortable', [])
504537
return inner;
505538
};
506539

507-
scope.$watchCollection('uiSortable', function(newVal, oldVal) {
540+
scope.$watchCollection('uiSortable', function(newOpts, oldOpts) {
508541
// ensure that the jquery-ui-sortable widget instance
509542
// is still bound to the directive's element
510543
var sortableWidgetInstance = getSortableWidgetInstance(element);
511544
if (!!sortableWidgetInstance) {
512-
var optsDiff = patchUISortableOptions(newVal, oldVal, sortableWidgetInstance);
545+
var optsDiff = patchUISortableOptions(newOpts, oldOpts, sortableWidgetInstance);
513546

514547
if (optsDiff) {
515548
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)