Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit ba6f987

Browse files
committedDec 1, 2016
fix adding / removing 'selected' on dynamic empty options
1 parent 41d4f74 commit ba6f987

File tree

3 files changed

+112
-13
lines changed

3 files changed

+112
-13
lines changed
 

‎src/ng/directive/ngOptions.js

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,7 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile,
420420
// option when the viewValue does not match any of the option values.
421421
for (var i = 0, children = selectElement.children(), ii = children.length; i < ii; i++) {
422422
if (children[i].value === '') {
423+
selectCtrl.hasEmptyOption = true;
423424
selectCtrl.emptyOption = children.eq(i);
424425
break;
425426
}
@@ -556,9 +557,35 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile,
556557
// compile the element since there might be bindings in it
557558
$compile(selectCtrl.emptyOption)(scope);
558559

559-
// remove the class, which is added automatically because we recompile the element and it
560-
// becomes the compilation root
561-
selectCtrl.emptyOption.removeClass('ng-scope');
560+
if (selectCtrl.emptyOption[0].nodeType === NODE_TYPE_COMMENT) {
561+
// This means the empty option has currently no actual DOM node, probably because
562+
// it has been modified by a transclusion directive.
563+
selectCtrl.hasEmptyOption = false;
564+
565+
// Redefine the registerOption function, which will catch
566+
// options that are added by ngIf etc. (rendering of the node is async because of
567+
// lazy transclusion)
568+
selectCtrl.registerOption = function(optionScope, optionEl) {
569+
if (optionEl.val() === '') {
570+
selectCtrl.hasEmptyOption = true;
571+
selectCtrl.emptyOption = optionEl;
572+
selectCtrl.emptyOption.removeClass('ng-scope');
573+
// This ensures the new empty option is selected if previously no option was selected
574+
ngModelCtrl.$render();
575+
576+
optionEl.on('$destroy', function() {
577+
selectCtrl.hasEmptyOption = false;
578+
selectCtrl.emptyOption = undefined;
579+
});
580+
}
581+
}
582+
583+
} else {
584+
// remove the class, which is added automatically because we recompile the element and it
585+
// becomes the compilation root
586+
selectCtrl.emptyOption.removeClass('ng-scope');
587+
}
588+
562589
}
563590

564591
selectElement.empty();

‎src/ng/directive/select.js

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ var SelectController =
3232
// to create it in <select> and IE barfs otherwise.
3333
self.unknownOption = jqLite(window.document.createElement('option'));
3434

35+
// The empty option is an option with the value '' that te application developer can
36+
// provide inside the select. When the model changes to a value that doesn't match an option,
37+
// it is selected - so if an empty option is provided, no unknown option is generated.
38+
// However, the empty option is not removed when the model matches an option. It is always selectable
39+
// and indicates that a "null" selection has been made.
40+
self.hasEmptyOption = false;
41+
self.emptyOption = undefined;
42+
3543
self.renderUnknownOption = function(val) {
3644
var unknownVal = self.generateUnknownOptionValue(val);
3745
self.unknownOption.val(unknownVal);
@@ -55,13 +63,6 @@ var SelectController =
5563
if (self.unknownOption.parent()) self.unknownOption.remove();
5664
};
5765

58-
// The empty option is an option with the value '' that te application developer can
59-
// provide inside the select. When the model changes to a value that doesn't match an option,
60-
// it is selected - so if an empty option is provided, no unknown option is generated.
61-
// However, the empty option is not removed when the model matches an option. It is always selectable
62-
// and indicates that a "null" selection has been made.
63-
self.emptyOption = undefined;
64-
6566
self.selectEmptyOption = function() {
6667
if (self.emptyOption) {
6768
$element.val('');
@@ -70,9 +71,7 @@ var SelectController =
7071
};
7172

7273
self.unselectEmptyOption = function() {
73-
// Empty options may be comment nodes if they have been removed by
74-
// transclude directives such as `ngIf`
75-
if (self.emptyOption && self.emptyOption[0].nodeType === NODE_TYPE_ELEMENT) {
74+
if (self.hasEmptyOption) {
7675
self.emptyOption.removeAttr('selected');
7776
}
7877
};
@@ -134,6 +133,7 @@ var SelectController =
134133

135134
assertNotHasOwnProperty(value, '"option value"');
136135
if (value === '') {
136+
self.hasEmptyOption = true;
137137
self.emptyOption = element;
138138
}
139139
var count = optionsMap.get(value) || 0;
@@ -150,6 +150,7 @@ var SelectController =
150150
if (count === 1) {
151151
optionsMap.remove(value);
152152
if (value === '') {
153+
self.hasEmptyOption = false;
153154
self.emptyOption = undefined;
154155
}
155156
} else {

‎test/ng/directive/ngOptionsSpec.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2543,6 +2543,77 @@ describe('ngOptions', function() {
25432543
});
25442544

25452545

2546+
it('should add / remove the "selected" attribute on empty option which has an initially falsy ngIf expression', function() {
2547+
scope.values = [
2548+
{name:'black'},
2549+
{name:'white'},
2550+
{name:'red'}
2551+
];
2552+
scope.selected = scope.values[2];
2553+
2554+
createSingleSelect('<option ng-if="isBlank" value="">blank</option>');
2555+
scope.$apply();
2556+
2557+
expect(element.find('option')[2]).toBeMarkedAsSelected();
2558+
2559+
scope.$apply('isBlank = true');
2560+
expect(element.find('option')[0].value).toBe('');
2561+
expect(element.find('option')[0]).not.toBeMarkedAsSelected();
2562+
2563+
scope.$apply('selected = null');
2564+
expect(element.find('option')[0].value).toBe('');
2565+
expect(element.find('option')[0]).toBeMarkedAsSelected();
2566+
2567+
scope.selected = scope.values[1];
2568+
scope.$apply();
2569+
expect(element.find('option')[0].value).toBe('');
2570+
expect(element.find('option')[0]).not.toBeMarkedAsSelected();
2571+
expect(element.find('option')[2]).toBeMarkedAsSelected();
2572+
});
2573+
2574+
2575+
it('should add / remove the "selected" attribute on empty option which has an initially truthy ngIf expression when no option is selected', function() {
2576+
scope.values = [
2577+
{name:'black'},
2578+
{name:'white'},
2579+
{name:'red'}
2580+
];
2581+
scope.isBlank = true;
2582+
2583+
createSingleSelect('<option ng-if="isBlank" value="">blank</option>');
2584+
scope.$apply();
2585+
2586+
expect(element.find('option')[0].value).toBe('');
2587+
expect(element.find('option')[0]).toBeMarkedAsSelected();
2588+
2589+
scope.selected = scope.values[2];
2590+
scope.$apply();
2591+
expect(element.find('option')[0]).not.toBeMarkedAsSelected();
2592+
expect(element.find('option')[3]).toBeMarkedAsSelected();
2593+
});
2594+
2595+
2596+
it('should add the "selected" attribute on empty option which has an initially falsy ngIf expression when no option is selected', function() {
2597+
scope.values = [
2598+
{name:'black'},
2599+
{name:'white'},
2600+
{name:'red'}
2601+
];
2602+
2603+
createSingleSelect('<option ng-if="isBlank" value="">blank</option>');
2604+
scope.$apply();
2605+
2606+
expect(element.find('option')[0]).not.toBeMarkedAsSelected();
2607+
2608+
scope.isBlank = true;
2609+
scope.$apply();
2610+
2611+
expect(element.find('option')[0].value).toBe('');
2612+
expect(element.find('option')[0]).toBeMarkedAsSelected();
2613+
expect(element.find('option')[1]).not.toBeMarkedAsSelected();
2614+
});
2615+
2616+
25462617
it('should not throw when a directive compiles the blank option before ngOptions is linked', function() {
25472618
expect(function() {
25482619
createSelect({

0 commit comments

Comments
 (0)
This repository has been archived.