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

Commit becdd70

Browse files
committed
feat(sortable): support helper functions returning an existing list element
1 parent 988644f commit becdd70

File tree

4 files changed

+241
-5
lines changed

4 files changed

+241
-5
lines changed

src/sortable.js

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ angular.module('ui.sortable', [])
2323
return first;
2424
}
2525

26-
function hasSortingHelper (element) {
26+
function hasSortingHelper (element, ui) {
2727
var helperOption = element.sortable('option','helper');
28-
return helperOption === 'clone' || typeof helperOption === 'function';
28+
return helperOption === 'clone' || (typeof helperOption === 'function' && ui.item.sortable.isCustomHelperUsed());
2929
}
3030

3131
var opts = {};
@@ -38,6 +38,10 @@ angular.module('ui.sortable', [])
3838
update:null
3939
};
4040

41+
var wrappers = {
42+
helper: null
43+
};
44+
4145
angular.extend(opts, uiSortableConfig, scope.$eval(attrs.uiSortable));
4246

4347
if (!angular.element.fn || !angular.element.fn.jquery) {
@@ -70,7 +74,11 @@ angular.module('ui.sortable', [])
7074
isCanceled: function () {
7175
return ui.item.sortable._isCanceled;
7276
},
73-
_isCanceled: false
77+
isCustomHelperUsed: function () {
78+
return !!ui.item.sortable._isCustomHelperUsed;
79+
},
80+
_isCanceled: false,
81+
_isCustomHelperUsed: ui.item.sortable._isCustomHelperUsed
7482
};
7583
};
7684

@@ -125,7 +133,7 @@ angular.module('ui.sortable', [])
125133
// the start and stop of repeat sections and sortable doesn't
126134
// respect their order (even if we cancel, the order of the
127135
// comments are still messed up).
128-
if (hasSortingHelper(element) && !ui.item.sortable.received) {
136+
if (hasSortingHelper(element, ui) && !ui.item.sortable.received) {
129137
// restore all the savedNodes except .ui-sortable-helper element
130138
// (which is placed last). That way it will be garbage collected.
131139
savedNodes = savedNodes.not(savedNodes.last());
@@ -161,7 +169,7 @@ angular.module('ui.sortable', [])
161169
// if the item was not moved, then restore the elements
162170
// so that the ngRepeat's comment are correct.
163171
if ((!('dropindex' in ui.item.sortable) || ui.item.sortable.isCanceled()) &&
164-
!hasSortingHelper(element)) {
172+
!hasSortingHelper(element, ui)) {
165173
savedNodes.appendTo(element);
166174
}
167175
}
@@ -192,6 +200,17 @@ angular.module('ui.sortable', [])
192200
}
193201
};
194202

203+
wrappers.helper = function (inner) {
204+
if (inner && typeof inner === 'function') {
205+
return function (e, item) {
206+
var innerResult = inner(e, item);
207+
item.sortable._isCustomHelperUsed = item !== innerResult;
208+
return innerResult;
209+
};
210+
}
211+
return inner;
212+
};
213+
195214
scope.$watch(attrs.uiSortable, function(newVal /*, oldVal*/) {
196215
// ensure that the jquery-ui-sortable widget instance
197216
// is still bound to the directive's element
@@ -205,6 +224,8 @@ angular.module('ui.sortable', [])
205224
}
206225
// wrap the callback
207226
value = combineCallbacks(callbacks[key], value);
227+
} else if (wrappers[key]) {
228+
value = wrappers[key](value);
208229
}
209230

210231
element.sortable('option', key, value);

test/sortable.e2e.callbacks.spec.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,45 @@ describe('uiSortable', function() {
6666
});
6767
});
6868

69+
it('should cancel sorting of node "Two" and "helper: function" that returns a list element is used', function() {
70+
inject(function($compile, $rootScope) {
71+
var element;
72+
element = $compile('<ul ui-sortable="opts" ng-model="items"><li ng-repeat="item in items" id="s-{{$index}}">{{ item }}</li></ul>')($rootScope);
73+
$rootScope.$apply(function() {
74+
$rootScope.opts = {
75+
update: function(e, ui) {
76+
if (ui.item.scope().item === 'Two') {
77+
ui.item.sortable.cancel();
78+
}
79+
}
80+
};
81+
$rootScope.items = ['One', 'Two', 'Three'];
82+
});
83+
84+
host.append(element);
85+
86+
var li = element.find(':eq(1)');
87+
var dy = (1 + EXTRA_DY_PERCENTAGE) * li.outerHeight();
88+
li.simulate('drag', { dy: dy });
89+
expect($rootScope.items).toEqual(['One', 'Two', 'Three']);
90+
expect($rootScope.items).toEqual(listContent(element));
91+
92+
li = element.find(':eq(0)');
93+
dy = (2 + EXTRA_DY_PERCENTAGE) * li.outerHeight();
94+
li.simulate('drag', { dy: dy });
95+
expect($rootScope.items).toEqual(['Two', 'Three', 'One']);
96+
expect($rootScope.items).toEqual(listContent(element));
97+
98+
li = element.find(':eq(2)');
99+
dy = -(2 + EXTRA_DY_PERCENTAGE) * li.outerHeight();
100+
li.simulate('drag', { dy: dy });
101+
expect($rootScope.items).toEqual(['One', 'Two', 'Three']);
102+
expect($rootScope.items).toEqual(listContent(element));
103+
104+
$(element).remove();
105+
});
106+
});
107+
69108
it('should cancel sorting of nodes that contain "Two"', function() {
70109
inject(function($compile, $rootScope) {
71110
var elementTop, elementBottom;

test/sortable.e2e.multi.spec.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,89 @@ describe('uiSortable', function() {
347347
});
348348
});
349349

350+
it('should work when "helper: function" that returns a list element is used', function() {
351+
inject(function($compile, $rootScope) {
352+
var elementTop, elementBottom;
353+
elementTop = $compile('<ul ui-sortable="opts" class="cross-sortable" ng-model="itemsTop"><li ng-repeat="item in itemsTop" id="s-top-{{$index}}" class="sortable-item">{{ item }}</li></ul>')($rootScope);
354+
elementBottom = $compile('<ul ui-sortable="opts" class="cross-sortable" ng-model="itemsBottom"><li ng-repeat="item in itemsBottom" id="s-bottom-{{$index}}" class="sortable-item">{{ item }}</li></ul>')($rootScope);
355+
$rootScope.$apply(function() {
356+
$rootScope.itemsTop = ['Top One', 'Top Two', 'Top Three'];
357+
$rootScope.itemsBottom = ['Bottom One', 'Bottom Two', 'Bottom Three'];
358+
$rootScope.opts = {
359+
helper: function (e, item) {
360+
return item;
361+
},
362+
connectWith: '.cross-sortable'
363+
};
364+
});
365+
366+
host.append(elementTop).append(elementBottom);
367+
368+
var li1 = elementTop.find(':eq(0)');
369+
var li2 = elementBottom.find(':eq(0)');
370+
var dy = EXTRA_DY_PERCENTAGE * li1.outerHeight() + (li2.position().top - li1.position().top);
371+
li1.simulate('drag', { dy: dy });
372+
expect($rootScope.itemsTop).toEqual(['Top Two', 'Top Three']);
373+
expect($rootScope.itemsBottom).toEqual(['Bottom One', 'Top One', 'Bottom Two', 'Bottom Three']);
374+
expect($rootScope.itemsTop).toEqual(listContent(elementTop));
375+
expect($rootScope.itemsBottom).toEqual(listContent(elementBottom));
376+
377+
li1 = elementBottom.find(':eq(1)');
378+
li2 = elementTop.find(':eq(1)');
379+
dy = -EXTRA_DY_PERCENTAGE * li1.outerHeight() - (li1.position().top - li2.position().top);
380+
li1.simulate('drag', { dy: dy });
381+
expect($rootScope.itemsTop).toEqual(['Top Two', 'Top One', 'Top Three']);
382+
expect($rootScope.itemsBottom).toEqual(['Bottom One', 'Bottom Two', 'Bottom Three']);
383+
expect($rootScope.itemsTop).toEqual(listContent(elementTop));
384+
expect($rootScope.itemsBottom).toEqual(listContent(elementBottom));
385+
386+
$(elementTop).remove();
387+
$(elementBottom).remove();
388+
});
389+
});
390+
391+
it('should work when "placeholder" and "helper: function" that returns a list element are used', function() {
392+
inject(function($compile, $rootScope) {
393+
var elementTop, elementBottom;
394+
elementTop = $compile('<ul ui-sortable="opts" class="cross-sortable" ng-model="itemsTop"><li ng-repeat="item in itemsTop" id="s-top-{{$index}}" class="sortable-item">{{ item }}</li></ul>')($rootScope);
395+
elementBottom = $compile('<ul ui-sortable="opts" class="cross-sortable" ng-model="itemsBottom"><li ng-repeat="item in itemsBottom" id="s-bottom-{{$index}}" class="sortable-item">{{ item }}</li></ul>')($rootScope);
396+
$rootScope.$apply(function() {
397+
$rootScope.itemsTop = ['Top One', 'Top Two', 'Top Three'];
398+
$rootScope.itemsBottom = ['Bottom One', 'Bottom Two', 'Bottom Three'];
399+
$rootScope.opts = {
400+
helper: function (e, item) {
401+
return item;
402+
},
403+
placeholder: 'sortable-item-placeholder',
404+
connectWith: '.cross-sortable'
405+
};
406+
});
407+
408+
host.append(elementTop).append(elementBottom);
409+
410+
var li1 = elementTop.find(':eq(0)');
411+
var li2 = elementBottom.find(':eq(0)');
412+
var dy = EXTRA_DY_PERCENTAGE * li1.outerHeight() + (li2.position().top - li1.position().top);
413+
li1.simulate('drag', { dy: dy });
414+
expect($rootScope.itemsTop).toEqual(['Top Two', 'Top Three']);
415+
expect($rootScope.itemsBottom).toEqual(['Bottom One', 'Top One', 'Bottom Two', 'Bottom Three']);
416+
expect($rootScope.itemsTop).toEqual(listContent(elementTop));
417+
expect($rootScope.itemsBottom).toEqual(listContent(elementBottom));
418+
419+
li1 = elementBottom.find(':eq(1)');
420+
li2 = elementTop.find(':eq(1)');
421+
dy = -EXTRA_DY_PERCENTAGE * li1.outerHeight() - (li1.position().top - li2.position().top);
422+
li1.simulate('drag', { dy: dy });
423+
expect($rootScope.itemsTop).toEqual(['Top Two', 'Top One', 'Top Three']);
424+
expect($rootScope.itemsBottom).toEqual(['Bottom One', 'Bottom Two', 'Bottom Three']);
425+
expect($rootScope.itemsTop).toEqual(listContent(elementTop));
426+
expect($rootScope.itemsBottom).toEqual(listContent(elementBottom));
427+
428+
$(elementTop).remove();
429+
$(elementBottom).remove();
430+
});
431+
});
432+
350433
it('should update model when sorting between nested sortables', function() {
351434
inject(function($compile, $rootScope) {
352435
var elementTree, li1, li2, dy;

test/sortable.e2e.spec.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,99 @@ describe('uiSortable', function() {
553553
});
554554
});
555555

556+
it('should work when "helper: function" that returns a list element is used', function() {
557+
inject(function($compile, $rootScope) {
558+
var element;
559+
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);
560+
$rootScope.$apply(function() {
561+
$rootScope.opts = {
562+
helper: function (e, item) {
563+
return item;
564+
}
565+
};
566+
$rootScope.items = ['One', 'Two', 'Three'];
567+
});
568+
569+
host.append(element);
570+
571+
var li = element.find(':eq(0)');
572+
var dy = (2 + EXTRA_DY_PERCENTAGE) * li.outerHeight();
573+
li.simulate('dragAndRevert', { dy: dy });
574+
expect($rootScope.items).toEqual(['One', 'Two', 'Three']);
575+
expect($rootScope.items).toEqual(listContent(element));
576+
577+
li = element.find(':eq(0)');
578+
dy = (1 + EXTRA_DY_PERCENTAGE) * li.outerHeight();
579+
li.simulate('drag', { dy: dy });
580+
expect($rootScope.items).toEqual(['Two', 'One', 'Three']);
581+
expect($rootScope.items).toEqual(listContent(element));
582+
583+
li = element.find(':eq(1)');
584+
dy = (1 + EXTRA_DY_PERCENTAGE) * li.outerHeight();
585+
li.simulate('drag', { dy: dy });
586+
expect($rootScope.items).toEqual(['Two', 'Three', 'One']);
587+
expect($rootScope.items).toEqual(listContent(element));
588+
589+
li = element.find(':eq(1)');
590+
dy = (1 + EXTRA_DY_PERCENTAGE) * li.outerHeight();
591+
li.simulate('drag', { dy: dy });
592+
expect($rootScope.items).toEqual(['Two', 'One', 'Three']);
593+
expect($rootScope.items).toEqual(listContent(element));
594+
595+
$(element).remove();
596+
});
597+
});
598+
599+
it('should work when "helper: function" that returns a list element and "placeholder" options are used together.', function() {
600+
inject(function($compile, $rootScope) {
601+
var element;
602+
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);
603+
$rootScope.$apply(function() {
604+
$rootScope.opts = {
605+
helper: function (e, item) {
606+
return item;
607+
},
608+
placeholder: 'sortable-item'
609+
};
610+
$rootScope.items = ['One', 'Two', 'Three'];
611+
});
612+
613+
host.append(element);
614+
615+
var li = element.find(':eq(0)');
616+
var dy = (2 + EXTRA_DY_PERCENTAGE) * li.outerHeight();
617+
li.simulate('dragAndRevert', { dy: dy });
618+
expect($rootScope.items).toEqual(['One', 'Two', 'Three']);
619+
expect($rootScope.items).toEqual(listContent(element));
620+
621+
li = element.find(':eq(0)');
622+
dy = (1 + EXTRA_DY_PERCENTAGE) * li.outerHeight();
623+
li.simulate('drag', { dy: dy });
624+
expect($rootScope.items).toEqual(['Two', 'One', 'Three']);
625+
expect($rootScope.items).toEqual(listContent(element));
626+
627+
li = element.find(':eq(1)');
628+
dy = (1 + EXTRA_DY_PERCENTAGE) * li.outerHeight();
629+
li.simulate('drag', { dy: dy });
630+
expect($rootScope.items).toEqual(['Two', 'Three', 'One']);
631+
expect($rootScope.items).toEqual(listContent(element));
632+
633+
li = element.find(':eq(1)');
634+
dy = (1 + EXTRA_DY_PERCENTAGE) * li.outerHeight();
635+
li.simulate('dragAndRevert', { dy: dy });
636+
expect($rootScope.items).toEqual(['Two', 'Three', 'One']);
637+
expect($rootScope.items).toEqual(listContent(element));
638+
639+
li = element.find(':eq(1)');
640+
dy = (1 + EXTRA_DY_PERCENTAGE) * li.outerHeight();
641+
li.simulate('drag', { dy: dy });
642+
expect($rootScope.items).toEqual(['Two', 'One', 'Three']);
643+
expect($rootScope.items).toEqual(listContent(element));
644+
645+
$(element).remove();
646+
});
647+
});
648+
556649
});
557650

558651
});

0 commit comments

Comments
 (0)