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

Commit 61c50b4

Browse files
committed
Merge pull request #373 from thgreasi/DeletedOptionsAndRefactor
feat(sortable): restore deleted options to default value
2 parents 4023aba + 2905b5d commit 61c50b4

File tree

5 files changed

+245
-52
lines changed

5 files changed

+245
-52
lines changed

gruntFile.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ module.exports = function(grunt) {
77
require('load-grunt-tasks')(grunt);
88

99
// Default task.
10-
grunt.registerTask('default', ['jshint', 'karma:unit']);
10+
grunt.registerTask('default', ['test']);
11+
grunt.registerTask('test', ['jshint', 'karma:unit']);
1112
grunt.registerTask('serve', ['karma:continuous', 'dist', 'build:gh-pages', 'connect:continuous', 'watch']);
1213
grunt.registerTask('dist', ['ngmin', 'surround', 'uglify' ]);
1314
grunt.registerTask('coverage', ['jshint', 'karma:coverage']);

src/sortable.js

Lines changed: 108 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
@param [ui-sortable] {object} Options to pass to $.fn.sortable() merged onto ui.config
55
*/
66
angular.module('ui.sortable', [])
7-
.value('uiSortableConfig',{})
7+
.value('uiSortableConfig',{
8+
// the default for jquery-ui sortable is "> *", we need to restrict this to
9+
// ng-repeat items
10+
// if the user uses
11+
items: '> [ng-repeat],> [data-ng-repeat],> [x-ng-repeat]'
12+
})
813
.directive('uiSortable', [
914
'uiSortableConfig', '$timeout', '$log',
1015
function(uiSortableConfig, $timeout, $log) {
@@ -17,12 +22,16 @@ angular.module('ui.sortable', [])
1722
link: function(scope, element, attrs, ngModel) {
1823
var savedNodes;
1924

20-
function combineCallbacks(first,second){
21-
if(second && (typeof second === 'function')) {
25+
function combineCallbacks(first, second){
26+
var firstIsFunc = first && (typeof first === 'function');
27+
var secondIsFunc = second && (typeof second === 'function');
28+
if(firstIsFunc && secondIsFunc) {
2229
return function() {
2330
first.apply(this, arguments);
2431
second.apply(this, arguments);
2532
};
33+
} else if (secondIsFunc) {
34+
return second;
2635
}
2736
return first;
2837
}
@@ -37,6 +46,93 @@ angular.module('ui.sortable', [])
3746
return null;
3847
}
3948

49+
function patchSortableOption(key, value) {
50+
if (callbacks[key]) {
51+
if( key === 'stop' ){
52+
// call apply after stop
53+
value = combineCallbacks(
54+
value, function() { scope.$apply(); });
55+
56+
value = combineCallbacks(value, afterStop);
57+
}
58+
// wrap the callback
59+
value = combineCallbacks(callbacks[key], value);
60+
} else if (wrappers[key]) {
61+
value = wrappers[key](value);
62+
}
63+
64+
if (key === 'items' && !value) {
65+
value = uiSortableConfig.items;
66+
}
67+
68+
return value;
69+
}
70+
71+
function patchUISortableOptions(newVal, oldVal, sortableWidgetInstance) {
72+
function addDummyOptionKey(value, key) {
73+
if (!(key in opts)) {
74+
// add the key in the opts object so that
75+
// the patch function detects and handles it
76+
opts[key] = null;
77+
}
78+
}
79+
// for this directive to work we have to attach some callbacks
80+
angular.forEach(callbacks, addDummyOptionKey);
81+
82+
// only initialize it in case we have to
83+
// update some options of the sortable
84+
var optsDiff = null;
85+
86+
if (oldVal) {
87+
// reset deleted options to default
88+
var defaultOptions;
89+
angular.forEach(oldVal, function(oldValue, key) {
90+
if (!newVal || !(key in newVal)) {
91+
if (key in directiveOpts) {
92+
opts[key] = 'auto';
93+
return;
94+
}
95+
96+
if (!defaultOptions) {
97+
defaultOptions = angular.element.ui.sortable().options;
98+
}
99+
var defaultValue = defaultOptions[key];
100+
defaultValue = patchSortableOption(key, defaultValue);
101+
102+
if (!optsDiff) {
103+
optsDiff = {};
104+
}
105+
optsDiff[key] = defaultValue;
106+
opts[key] = defaultValue;
107+
}
108+
});
109+
}
110+
111+
// update changed options
112+
angular.forEach(newVal, function(value, key) {
113+
// if it's a custom option of the directive,
114+
// handle it approprietly
115+
if (key in directiveOpts) {
116+
if (key === 'ui-floating' && (value === false || value === true) && sortableWidgetInstance) {
117+
sortableWidgetInstance.floating = value;
118+
}
119+
120+
opts[key] = value;
121+
return;
122+
}
123+
124+
value = patchSortableOption(key, value);
125+
126+
if (!optsDiff) {
127+
optsDiff = {};
128+
}
129+
optsDiff[key] = value;
130+
opts[key] = value;
131+
});
132+
133+
return optsDiff;
134+
}
135+
40136
function getPlaceholderElement (element) {
41137
var placeholder = element.sortable('option','placeholder');
42138

@@ -104,16 +200,11 @@ angular.module('ui.sortable', [])
104200
// we can't just do ui.item.index() because there it might have siblings
105201
// which are not items
106202
function getItemIndex(ui) {
107-
return ui.item.parent().find('> [ng-repeat],> [data-ng-repeat],> [x-ng-repeat]')
203+
return ui.item.parent().find(uiSortableConfig.items)
108204
.index(ui.item);
109205
}
110206

111-
var opts = {
112-
// the default for jquery-ui sortable is "> *", we need to restrict this to
113-
// ng-repeat items
114-
// if the user uses
115-
items: '> [ng-repeat],> [data-ng-repeat],> [x-ng-repeat]'
116-
};
207+
var opts = {};
117208

118209
// directive specific options
119210
var directiveOpts = {
@@ -338,53 +429,20 @@ angular.module('ui.sortable', [])
338429
return inner;
339430
};
340431

341-
scope.$watchCollection('uiSortable', function(newVal /*, oldVal*/) {
432+
scope.$watchCollection('uiSortable', function(newVal, oldVal) {
342433
// ensure that the jquery-ui-sortable widget instance
343434
// is still bound to the directive's element
344435
var sortableWidgetInstance = getSortableWidgetInstance(element);
345436
if (!!sortableWidgetInstance) {
346-
angular.forEach(newVal, function(value, key) {
347-
// if it's a custom option of the directive,
348-
// handle it approprietly
349-
if (key in directiveOpts) {
350-
if (key === 'ui-floating' && (value === false || value === true)) {
351-
sortableWidgetInstance.floating = value;
352-
}
353-
354-
opts[key] = value;
355-
return;
356-
}
357-
358-
if (callbacks[key]) {
359-
if( key === 'stop' ){
360-
// call apply after stop
361-
value = combineCallbacks(
362-
value, function() { scope.$apply(); });
363-
364-
value = combineCallbacks(value, afterStop);
365-
}
366-
// wrap the callback
367-
value = combineCallbacks(callbacks[key], value);
368-
} else if (wrappers[key]) {
369-
value = wrappers[key](value);
370-
}
371-
372-
if (key === 'items' && !value) {
373-
value = '> [ng-repeat],> [data-ng-repeat],> [x-ng-repeat]';
374-
}
375-
376-
opts[key] = value;
377-
element.sortable('option', key, value);
378-
});
437+
var optsDiff = patchUISortableOptions(newVal, oldVal, sortableWidgetInstance);
438+
439+
if (optsDiff) {
440+
element.sortable('option', optsDiff);
441+
}
379442
}
380443
}, true);
381444

382-
angular.forEach(callbacks, function(value, key) {
383-
opts[key] = combineCallbacks(value, opts[key]);
384-
if( key === 'stop' ){
385-
opts[key] = combineCallbacks(opts[key], afterStop);
386-
}
387-
});
445+
patchUISortableOptions(opts);
388446

389447
} else {
390448
$log.info('ui.sortable: ngModel not provided!', element);

test/sortable.e2e.callbacks.spec.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,61 @@ describe('uiSortable', function() {
457457
});
458458
});
459459

460+
it('should properly reset a deleted callback option', function() {
461+
inject(function($compile, $rootScope) {
462+
var element, logsElement;
463+
element = $compile(''.concat(
464+
'<ul ui-sortable="opts" ng-model="items">',
465+
beforeLiElement,
466+
'<li ng-repeat="item in items" id="s-{{$index}}">{{ item }}</li>',
467+
afterLiElement +
468+
'</ul>'))($rootScope);
469+
logsElement = $compile(''.concat(
470+
'<ul ng-model="logs">',
471+
beforeLiElement,
472+
'<li ng-repeat="log in logs" id="l-{{$index}}">{{ log }}</li>',
473+
afterLiElement +
474+
'</ul>'))($rootScope);
475+
$rootScope.$apply(function() {
476+
$rootScope.opts = {
477+
stop: function(e, ui) {
478+
$rootScope.logs.push('Moved element ' + ui.item.sortable.model);
479+
}
480+
};
481+
$rootScope.items = ['One', 'Two', 'Three'];
482+
$rootScope.logs = [];
483+
});
484+
485+
host.append(element).append(logsElement);
486+
487+
var li = element.find('[ng-repeat]:eq(1)');
488+
var dy = (1 + EXTRA_DY_PERCENTAGE) * li.outerHeight();
489+
li.simulate('drag', { dy: dy });
490+
expect($rootScope.items).toEqual(['One', 'Three', 'Two']);
491+
expect($rootScope.logs).toEqual(['Moved element Two']);
492+
expect($rootScope.items).toEqual(listContent(element));
493+
expect($rootScope.logs).toEqual(listContent(logsElement));
494+
495+
$rootScope.$digest();
496+
497+
$rootScope.$apply(function() {
498+
$rootScope.opts = {};
499+
});
500+
501+
li = element.find('[ng-repeat]:eq(0)');
502+
dy = (1 + EXTRA_DY_PERCENTAGE) * li.outerHeight();
503+
li.simulate('drag', { dy: dy });
504+
expect($rootScope.items).toEqual(['Three', 'One', 'Two']);
505+
expect($rootScope.items).toEqual(listContent(element));
506+
// the log should be the same
507+
expect($rootScope.logs).toEqual(['Moved element Two']);
508+
expect($rootScope.logs).toEqual(listContent(logsElement));
509+
510+
$(element).remove();
511+
$(logsElement).remove();
512+
});
513+
});
514+
460515
}
461516

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

test/sortable.e2e.directiveoptions.spec.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,46 @@ describe('uiSortable', function() {
200200
});
201201
});
202202

203+
it('should properly reset deleted directive options', function() {
204+
inject(function($compile, $rootScope) {
205+
var element, logsElement;
206+
element = $compile(''.concat(
207+
'<ul ui-sortable="opts" ng-model="items">',
208+
beforeLiElement,
209+
'<li ng-repeat="item in items" id="s-{{$index}}">{{ item }}</li>',
210+
afterLiElement +
211+
'</ul>'))($rootScope);
212+
$rootScope.$apply(function() {
213+
$rootScope.opts = {
214+
'ui-floating': true
215+
};
216+
$rootScope.items = ['One', 'Two', 'Three'];
217+
});
218+
219+
host.append(element).append(logsElement);
220+
221+
var sortableWidgetInstance = element.data('ui-sortable');
222+
223+
expect(sortableWidgetInstance.floating).toBe(true);
224+
225+
$rootScope.$digest();
226+
227+
$rootScope.$apply(function() {
228+
$rootScope.opts = {};
229+
});
230+
231+
var li = element.find('[ng-repeat]:eq(1)');
232+
var dy = (1 + EXTRA_DY_PERCENTAGE) * li.outerHeight();
233+
li.simulate('drag', { dy: dy });
234+
expect($rootScope.items).toEqual(['One', 'Three', 'Two']);
235+
expect($rootScope.items).toEqual(listContent(element));
236+
expect(sortableWidgetInstance.floating).toBe(false);
237+
238+
$(element).remove();
239+
$(logsElement).remove();
240+
});
241+
});
242+
203243
}
204244

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

test/sortable.spec.js

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,16 +256,55 @@ describe('uiSortable', function() {
256256

257257
});
258258

259+
it('should properly reset the value of a deleted option', function() {
259260

261+
inject(function($compile, $rootScope, $timeout) {
262+
var element;
263+
var childScope = $rootScope.$new();
264+
childScope.opts = {
265+
opacity: 0.7,
266+
placeholder: 'phClass',
267+
update: function() { }
268+
};
260269

270+
element = $compile('<div><ul data-ui-sortable="opts" data-ng-model="items"></ul></div>')(childScope);
271+
var $sortableElement = element.find('[data-ui-sortable]');
261272

273+
expect($sortableElement.sortable('option', 'opacity')).toBe(0.7);
274+
expect($sortableElement.sortable('option', 'placeholder')).toBe('phClass');
275+
expect(typeof $sortableElement.sortable('option', 'update')).toBe('function');
262276

277+
$rootScope.$digest();
263278

279+
$rootScope.$apply(function() {
280+
delete childScope.opts.opacity;
281+
});
264282

265-
});
283+
expect($sortableElement.sortable('option', 'opacity')).toBe(false);
284+
expect($sortableElement.sortable('option', 'placeholder')).toBe('phClass');
285+
expect(typeof $sortableElement.sortable('option', 'update')).toBe('function');
266286

287+
$rootScope.$digest();
267288

289+
$rootScope.$apply(function() {
290+
childScope.opts = {};
291+
});
268292

293+
expect($sortableElement.sortable('option', 'opacity')).toBe(false);
294+
expect($sortableElement.sortable('option', 'placeholder')).toBe(false);
295+
expect(typeof $sortableElement.sortable('option', 'update')).toBe('function');
296+
297+
element.remove(element.firstChild);
298+
299+
expect(function() {
300+
$timeout.flush();
301+
}).not.toThrow();
302+
303+
});
304+
305+
});
306+
307+
});
269308

270309

271310
});

0 commit comments

Comments
 (0)