Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 6d29b21

Browse files
committed
fix(select): handle model updates when options are manipulated
When an option that is currently selected, is removed or its value changes, the model is set to null. Likewise if an option is added or its value changes to the currently selected model, this option is selected. If an option is disabled, the model is set to null. If the model value changes to a value that matches a disabled option, this option is selected (analogue to ngOptions)
1 parent 4a3de1a commit 6d29b21

File tree

2 files changed

+505
-16
lines changed

2 files changed

+505
-16
lines changed

src/ng/directive/select.js

Lines changed: 109 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,19 +48,43 @@ var SelectController =
4848
// Read the value of the select control, the implementation of this changes depending
4949
// upon whether the select can have multiple values and whether ngOptions is at work.
5050
self.readValue = function readSingleValue() {
51-
self.removeUnknownOption();
5251
var val = $element.val();
5352
// ngValue added option values are stored in the selectValueMap, normal interpolations are not
54-
return val in self.selectValueMap ? self.selectValueMap[val] : val;
53+
var realVal = val in self.selectValueMap ? self.selectValueMap[val] : val;
54+
55+
console.log('read', 'elval', val, 'possiblyhashed', realVal)
56+
if (self.hasOption(realVal)) {
57+
console.log('has selected val', realVal)
58+
// self.removeUnknownOption();
59+
return realVal;
60+
}
61+
62+
return null;
5563
};
5664

5765

5866
// Write the value to the select control, the implementation of this changes depending
5967
// upon whether the select can have multiple values and whether ngOptions is at work.
6068
self.writeValue = function writeSingleValue(value) {
69+
console.log('write', value);
6170
if (self.hasOption(value)) {
71+
console.log('hasOption', value);
6272
self.removeUnknownOption();
63-
$element.val(value);
73+
var hashedVal = hashKey(value);
74+
if (hashedVal in self.selectValueMap) {
75+
$element.val(hashedVal);
76+
} else {
77+
$element.val(value);
78+
}
79+
console.log('after set', $element.val())
80+
// console.log('selectValueMap', self.selectValueMap)
81+
// var items = new HashMap();
82+
// items.put(value, value);
83+
// console.log(items, hashKey(value));
84+
85+
// if (hashKey(value) in self.selectValueMap) {
86+
// console.log('hashed')
87+
// }
6488
if (value === '') self.emptyOption.prop('selected', true); // to make IE9 happy
6589
} else {
6690
if (value == null && self.emptyOption) {
@@ -110,50 +134,124 @@ var SelectController =
110134

111135
self.registerOption = function(optionScope, optionElement, optionAttrs, interpolateValueFn, interpolateTextFn) {
112136

113-
if (interpolateValueFn === true) {
137+
// console.log('attr', optionAttrs)
138+
if (optionAttrs.$attr.ngValue) {
114139
// The value attribute is set by ngValue
115140
var oldVal, hashedVal = NaN;
116141
optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) {
142+
console.log('ngValue change', 'n', newVal, 'o', oldVal, 'hashed', hashedVal)
143+
144+
var removal;
145+
console.log('val', $element.val());
146+
var previouslySelected = optionElement.prop('selected');
147+
117148
if (isDefined(hashedVal)) {
118149
self.removeOption(oldVal);
119150
delete self.selectValueMap[hashedVal];
151+
removal = true;
120152
}
121153

122154
hashedVal = hashKey(newVal);
123155
oldVal = newVal;
124-
self.addOption(newVal, optionElement);
125156
self.selectValueMap[hashedVal] = newVal;
157+
self.addOption(newVal, optionElement);
158+
console.log('val', $element.val());
126159
// Set the attribute directly instead of using optionAttrs.$set - this stops the observer
127160
// from firing a second time. Other $observers on value will also get the result of the
128161
// ngValue expression, not the hashed value
129162
optionElement.attr('value', hashedVal);
163+
164+
console.log('previouslySelected', previouslySelected, 'removal', removal)
165+
166+
if (removal && previouslySelected) {
167+
console.log('removed val is currently selected', $element.val())
168+
self.ngModelCtrl.$setViewValue(self.readValue());
169+
}
170+
130171
});
131172
} else if (interpolateValueFn) {
132173
// The value attribute is interpolated
133174
optionAttrs.$observe('value', function valueAttributeObserveAction(newVal) {
175+
// console.log('value attribute changed', 'viewVal', self.ngModelCtrl.$viewValue, 'index', optionElement[0].index, 'indices', $element[0].selectedIndex, $element[0].selectedOptions)
176+
var currentVal = self.readValue();
177+
var removal;
178+
var previouslySelected = optionElement.prop('selected');
179+
var removedVal;
180+
134181
if (isDefined(oldVal)) {
135182
self.removeOption(oldVal);
183+
removal = true;
184+
removedVal = oldVal;
136185
}
137186
oldVal = newVal;
138187
self.addOption(newVal, optionElement);
188+
189+
console.log('updated interpolated value', 'new', newVal, 'removed', removedVal, 'current', currentVal);
190+
if (removal && previouslySelected) {
191+
console.log('removed val is currently selected', $element.val())
192+
self.ngModelCtrl.$setViewValue(self.readValue());
193+
}
139194
});
140195
} else if (interpolateTextFn) {
141196
// The text content is interpolated
142197
optionScope.$watch(interpolateTextFn, function interpolateWatchAction(newVal, oldVal) {
143198
optionAttrs.$set('value', newVal);
199+
var previouslySelected = optionElement.prop('selected');
144200
if (oldVal !== newVal) {
145201
self.removeOption(oldVal);
146202
}
147203
self.addOption(newVal, optionElement);
204+
205+
if (oldVal && previouslySelected) {
206+
self.ngModelCtrl.$setViewValue(self.readValue());
207+
}
148208
});
149209
} else {
150210
// The value attribute is static
151211
self.addOption(optionAttrs.value, optionElement);
152212
}
153213

214+
215+
var oldDisabled;
216+
optionAttrs.$observe('disabled', function(newVal) {
217+
218+
// Since model updates will also select disabled options (like ngOptions),
219+
// we only have to handle options becomeing disabled, not enabled
220+
221+
if (newVal === 'true' || newVal && optionElement.prop('selected')) {
222+
console.log('disabled')
223+
self.ngModelCtrl.$setViewValue(null);
224+
self.ngModelCtrl.$render();
225+
oldDisabled = newVal;
226+
} else if (isDefined(oldDisabled) && !newVal || newVal === 'false') {
227+
// var val = optionAttrs.value;
228+
// console.log('OA', optionAttrs.value);
229+
// var realVal = val in self.selectValueMap ? self.selectValueMap[val] : val;
230+
// console.log('back from disabled', val, realVal, self.ngModelCtrl.$viewValue);
231+
232+
// if (realVal === self.ngModelCtrl.$viewValue) {
233+
// self.writeValue(realVal);
234+
// self.ngModelCtrl.$setViewValue(self.readValue());
235+
// }
236+
}
237+
});
238+
154239
optionElement.on('$destroy', function() {
155-
self.removeOption(optionAttrs.value);
240+
var currentValue = self.readValue();
241+
var removeValue = optionAttrs.value;
242+
243+
console.log('destroy', removeValue, 'elval', $element.val())
244+
// console.log('viewValue', self.ngModelCtrl.$viewValue)
245+
self.removeOption(removeValue);
156246
self.ngModelCtrl.$render();
247+
248+
if (currentValue === removeValue) {
249+
// console.log('removed val is currently selected', $element.val())
250+
// console.log('self.readValue()', self.readValue());
251+
self.ngModelCtrl.$setViewValue(self.readValue());
252+
253+
}
254+
// console.log('read after render', self.readValue())
157255
});
158256
};
159257
}];
@@ -389,6 +487,8 @@ var selectDirective = function() {
389487
// to the `readValue` method, which can be changed if the select can have multiple
390488
// selected values or if the options are being generated by `ngOptions`
391489
element.on('change', function() {
490+
console.log('on change', element.val())
491+
selectCtrl.removeUnknownOption();
392492
scope.$apply(function() {
393493
ngModelCtrl.$setViewValue(selectCtrl.readValue());
394494
});
@@ -467,17 +567,17 @@ var optionDirective = ['$interpolate', function($interpolate) {
467567
restrict: 'E',
468568
priority: 100,
469569
compile: function(element, attr) {
470-
var interpolateValueFn;
570+
var interpolateValueFn, interpolateTextFn;
471571

472572
if (isDefined(attr.ngValue)) {
473-
interpolateValueFn = true;
573+
// Will be handled by registerOption
474574
} else if (isDefined(attr.value)) {
475575
// If the value attribute is defined, check if it contains an interpolation
476576
interpolateValueFn = $interpolate(attr.value, true);
477577
} else {
478578
// If the value attribute is not defined then we fall back to the
479579
// text content of the option element, which may be interpolated
480-
var interpolateTextFn = $interpolate(element.text(), true);
580+
interpolateTextFn = $interpolate(element.text(), true);
481581
if (!interpolateTextFn) {
482582
attr.$set('value', element.text());
483583
}

0 commit comments

Comments
 (0)