mirror of
https://github.com/zhigang1992/angular.js.git
synced 2026-04-29 21:37:00 +08:00
fix(select): assign result of track exp to element value
Fixes a regression where the option/select values would always be set to the key or index of a value within the corresponding collection. Prior to some 1.3.0 refactoring, the result of the track expression would be bound to the value, but this behavior was not documented or explicitly tested. A cache was added in order to improve performance getting the associated value for a given track expression. This commit adds one explicit test for this behavior, and changes several other trackBy tests to reflect the desired behavior as well. Closes #9718 Fixes #9592
This commit is contained in:
committed by
Jeff Cross
parent
c3b3b90bc9
commit
4b4098bfca
@@ -355,6 +355,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
|||||||
valuesFn = $parse(match[7]),
|
valuesFn = $parse(match[7]),
|
||||||
track = match[8],
|
track = match[8],
|
||||||
trackFn = track ? $parse(match[8]) : null,
|
trackFn = track ? $parse(match[8]) : null,
|
||||||
|
trackKeysCache = {},
|
||||||
// This is an array of array of existing option groups in DOM.
|
// This is an array of array of existing option groups in DOM.
|
||||||
// We try to reuse these if possible
|
// We try to reuse these if possible
|
||||||
// - optionGroupsCache[0] is the options with no option group
|
// - optionGroupsCache[0] is the options with no option group
|
||||||
@@ -405,10 +406,11 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
|||||||
if (multiple) {
|
if (multiple) {
|
||||||
viewValue = [];
|
viewValue = [];
|
||||||
forEach(selectElement.val(), function(selectedKey) {
|
forEach(selectElement.val(), function(selectedKey) {
|
||||||
|
selectedKey = trackFn ? trackKeysCache[selectedKey] : selectedKey;
|
||||||
viewValue.push(getViewValue(selectedKey, collection[selectedKey]));
|
viewValue.push(getViewValue(selectedKey, collection[selectedKey]));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
var selectedKey = selectElement.val();
|
var selectedKey = trackFn ? trackKeysCache[selectElement.val()] : selectElement.val();
|
||||||
viewValue = getViewValue(selectedKey, collection[selectedKey]);
|
viewValue = getViewValue(selectedKey, collection[selectedKey]);
|
||||||
}
|
}
|
||||||
ctrl.$setViewValue(viewValue);
|
ctrl.$setViewValue(viewValue);
|
||||||
@@ -530,7 +532,10 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
|||||||
anySelected = false,
|
anySelected = false,
|
||||||
lastElement,
|
lastElement,
|
||||||
element,
|
element,
|
||||||
label;
|
label,
|
||||||
|
optionId;
|
||||||
|
|
||||||
|
trackKeysCache = {};
|
||||||
|
|
||||||
// We now build up the list of options we need (we merge later)
|
// We now build up the list of options we need (we merge later)
|
||||||
for (index = 0; length = keys.length, index < length; index++) {
|
for (index = 0; length = keys.length, index < length; index++) {
|
||||||
@@ -554,9 +559,14 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
|||||||
|
|
||||||
// doing displayFn(scope, locals) || '' overwrites zero values
|
// doing displayFn(scope, locals) || '' overwrites zero values
|
||||||
label = isDefined(label) ? label : '';
|
label = isDefined(label) ? label : '';
|
||||||
|
optionId = trackFn ? trackFn(scope, locals) : (keyName ? keys[index] : index);
|
||||||
|
if (trackFn) {
|
||||||
|
trackKeysCache[optionId] = key;
|
||||||
|
}
|
||||||
|
|
||||||
optionGroup.push({
|
optionGroup.push({
|
||||||
// either the index into array or key from object
|
// either the index into array or key from object
|
||||||
id: (keyName ? keys[index] : index),
|
id: optionId,
|
||||||
label: label,
|
label: label,
|
||||||
selected: selected // determine if we should be selected
|
selected: selected // determine if we should be selected
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -722,10 +722,45 @@ describe('select', function() {
|
|||||||
describe('trackBy expression', function() {
|
describe('trackBy expression', function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
scope.arr = [{id: 10, label: 'ten'}, {id:20, label: 'twenty'}];
|
scope.arr = [{id: 10, label: 'ten'}, {id:20, label: 'twenty'}];
|
||||||
scope.obj = {'10': {score: 10, label: 'ten'}, '20': {score: 20, label: 'twenty'}};
|
scope.obj = {'1': {score: 10, label: 'ten'}, '2': {score: 20, label: 'twenty'}};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should set the result of track by expression to element value', function() {
|
||||||
|
createSelect({
|
||||||
|
'ng-model': 'selected',
|
||||||
|
'ng-options': 'item.label for item in arr track by item.id'
|
||||||
|
});
|
||||||
|
|
||||||
|
scope.$apply(function() {
|
||||||
|
scope.selected = scope.arr[0];
|
||||||
|
});
|
||||||
|
expect(element.val()).toBe('10');
|
||||||
|
|
||||||
|
scope.$apply(function() {
|
||||||
|
scope.arr[0] = {id: 10, label: 'new ten'};
|
||||||
|
});
|
||||||
|
expect(element.val()).toBe('10');
|
||||||
|
|
||||||
|
element.children()[1].selected = 'selected';
|
||||||
|
browserTrigger(element, 'change');
|
||||||
|
expect(scope.selected).toEqual(scope.arr[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should use the tracked expression as option value', function() {
|
||||||
|
createSelect({
|
||||||
|
'ng-model': 'selected',
|
||||||
|
'ng-options': 'item.label for item in arr track by item.id'
|
||||||
|
});
|
||||||
|
|
||||||
|
var options = element.find('option');
|
||||||
|
expect(options.length).toEqual(3);
|
||||||
|
expect(sortedHtml(options[0])).toEqual('<option value="?"></option>');
|
||||||
|
expect(sortedHtml(options[1])).toEqual('<option value="10">ten</option>');
|
||||||
|
expect(sortedHtml(options[2])).toEqual('<option value="20">twenty</option>');
|
||||||
|
});
|
||||||
|
|
||||||
it('should preserve value even when reference has changed (single&array)', function() {
|
it('should preserve value even when reference has changed (single&array)', function() {
|
||||||
createSelect({
|
createSelect({
|
||||||
'ng-model': 'selected',
|
'ng-model': 'selected',
|
||||||
@@ -735,12 +770,12 @@ describe('select', function() {
|
|||||||
scope.$apply(function() {
|
scope.$apply(function() {
|
||||||
scope.selected = scope.arr[0];
|
scope.selected = scope.arr[0];
|
||||||
});
|
});
|
||||||
expect(element.val()).toBe('0');
|
expect(element.val()).toBe('10');
|
||||||
|
|
||||||
scope.$apply(function() {
|
scope.$apply(function() {
|
||||||
scope.arr[0] = {id: 10, label: 'new ten'};
|
scope.arr[0] = {id: 10, label: 'new ten'};
|
||||||
});
|
});
|
||||||
expect(element.val()).toBe('0');
|
expect(element.val()).toBe('10');
|
||||||
|
|
||||||
element.children()[1].selected = 1;
|
element.children()[1].selected = 1;
|
||||||
browserTrigger(element, 'change');
|
browserTrigger(element, 'change');
|
||||||
@@ -758,12 +793,12 @@ describe('select', function() {
|
|||||||
scope.$apply(function() {
|
scope.$apply(function() {
|
||||||
scope.selected = scope.arr;
|
scope.selected = scope.arr;
|
||||||
});
|
});
|
||||||
expect(element.val()).toEqual(['0','1']);
|
expect(element.val()).toEqual(['10','20']);
|
||||||
|
|
||||||
scope.$apply(function() {
|
scope.$apply(function() {
|
||||||
scope.arr[0] = {id: 10, label: 'new ten'};
|
scope.arr[0] = {id: 10, label: 'new ten'};
|
||||||
});
|
});
|
||||||
expect(element.val()).toEqual(['0','1']);
|
expect(element.val()).toEqual(['10','20']);
|
||||||
|
|
||||||
element.children()[0].selected = false;
|
element.children()[0].selected = false;
|
||||||
browserTrigger(element, 'change');
|
browserTrigger(element, 'change');
|
||||||
@@ -778,18 +813,18 @@ describe('select', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
scope.$apply(function() {
|
scope.$apply(function() {
|
||||||
scope.selected = scope.obj['10'];
|
scope.selected = scope.obj['1'];
|
||||||
});
|
});
|
||||||
expect(element.val()).toBe('10');
|
expect(element.val()).toBe('10');
|
||||||
|
|
||||||
scope.$apply(function() {
|
scope.$apply(function() {
|
||||||
scope.obj['10'] = {score: 10, label: 'ten'};
|
scope.obj['1'] = {score: 10, label: 'ten'};
|
||||||
});
|
});
|
||||||
expect(element.val()).toBe('10');
|
expect(element.val()).toBe('10');
|
||||||
|
|
||||||
element.val('20');
|
element.val('20');
|
||||||
browserTrigger(element, 'change');
|
browserTrigger(element, 'change');
|
||||||
expect(scope.selected).toBe(scope.obj[20]);
|
expect(scope.selected).toBe(scope.obj['2']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -801,18 +836,18 @@ describe('select', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
scope.$apply(function() {
|
scope.$apply(function() {
|
||||||
scope.selected = [scope.obj['10']];
|
scope.selected = [scope.obj['1']];
|
||||||
});
|
});
|
||||||
expect(element.val()).toEqual(['10']);
|
expect(element.val()).toEqual(['10']);
|
||||||
|
|
||||||
scope.$apply(function() {
|
scope.$apply(function() {
|
||||||
scope.obj['10'] = {score: 10, label: 'ten'};
|
scope.obj['1'] = {score: 10, label: 'ten'};
|
||||||
});
|
});
|
||||||
expect(element.val()).toEqual(['10']);
|
expect(element.val()).toEqual(['10']);
|
||||||
|
|
||||||
element.children()[1].selected = 'selected';
|
element.children()[1].selected = 'selected';
|
||||||
browserTrigger(element, 'change');
|
browserTrigger(element, 'change');
|
||||||
expect(scope.selected).toEqual([scope.obj[10], scope.obj[20]]);
|
expect(scope.selected).toEqual([scope.obj['1'], scope.obj['2']]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -840,16 +875,16 @@ describe('select', function() {
|
|||||||
scope.$apply(function() {
|
scope.$apply(function() {
|
||||||
scope.selected = scope.arr[0].subItem;
|
scope.selected = scope.arr[0].subItem;
|
||||||
});
|
});
|
||||||
expect(element.val()).toEqual('0');
|
expect(element.val()).toEqual('10');
|
||||||
|
|
||||||
scope.$apply(function() {
|
scope.$apply(function() {
|
||||||
scope.selected = scope.arr[1].subItem;
|
scope.selected = scope.arr[1].subItem;
|
||||||
});
|
});
|
||||||
expect(element.val()).toEqual('1');
|
expect(element.val()).toEqual('20');
|
||||||
|
|
||||||
// Now test view -> model
|
// Now test view -> model
|
||||||
|
|
||||||
element.val('0');
|
element.val('10');
|
||||||
browserTrigger(element, 'change');
|
browserTrigger(element, 'change');
|
||||||
expect(scope.selected).toBe(scope.arr[0].subItem);
|
expect(scope.selected).toBe(scope.arr[0].subItem);
|
||||||
|
|
||||||
@@ -861,7 +896,7 @@ describe('select', function() {
|
|||||||
subItem: {label: 'new twenty', id: 20}
|
subItem: {label: 'new twenty', id: 20}
|
||||||
}];
|
}];
|
||||||
});
|
});
|
||||||
expect(element.val()).toBe('0');
|
expect(element.val()).toBe('10');
|
||||||
expect(scope.selected.id).toBe(10);
|
expect(scope.selected.id).toBe(10);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -879,12 +914,12 @@ describe('select', function() {
|
|||||||
scope.$apply(function() {
|
scope.$apply(function() {
|
||||||
scope.selected = [scope.arr[0].subItem];
|
scope.selected = [scope.arr[0].subItem];
|
||||||
});
|
});
|
||||||
expect(element.val()).toEqual(['0']);
|
expect(element.val()).toEqual(['10']);
|
||||||
|
|
||||||
scope.$apply(function() {
|
scope.$apply(function() {
|
||||||
scope.selected = [scope.arr[1].subItem];
|
scope.selected = [scope.arr[1].subItem];
|
||||||
});
|
});
|
||||||
expect(element.val()).toEqual(['1']);
|
expect(element.val()).toEqual(['20']);
|
||||||
|
|
||||||
// Now test view -> model
|
// Now test view -> model
|
||||||
|
|
||||||
@@ -901,7 +936,7 @@ describe('select', function() {
|
|||||||
subItem: {label: 'new twenty', id: 20}
|
subItem: {label: 'new twenty', id: 20}
|
||||||
}];
|
}];
|
||||||
});
|
});
|
||||||
expect(element.val()).toEqual(['0']);
|
expect(element.val()).toEqual(['10']);
|
||||||
expect(scope.selected[0].id).toEqual(10);
|
expect(scope.selected[0].id).toEqual(10);
|
||||||
expect(scope.selected.length).toBe(1);
|
expect(scope.selected.length).toBe(1);
|
||||||
});
|
});
|
||||||
@@ -1338,20 +1373,20 @@ describe('select', function() {
|
|||||||
scope.selected = scope.values[1];
|
scope.selected = scope.values[1];
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(element.val()).toEqual('1');
|
expect(element.val()).toEqual('2');
|
||||||
|
|
||||||
var first = jqLite(element.find('option')[0]);
|
var first = jqLite(element.find('option')[0]);
|
||||||
expect(first.text()).toEqual('first');
|
expect(first.text()).toEqual('first');
|
||||||
expect(first.attr('value')).toEqual('0');
|
expect(first.attr('value')).toEqual('1');
|
||||||
var forth = jqLite(element.find('option')[3]);
|
var forth = jqLite(element.find('option')[3]);
|
||||||
expect(forth.text()).toEqual('forth');
|
expect(forth.text()).toEqual('forth');
|
||||||
expect(forth.attr('value')).toEqual('3');
|
expect(forth.attr('value')).toEqual('4');
|
||||||
|
|
||||||
scope.$apply(function() {
|
scope.$apply(function() {
|
||||||
scope.selected = scope.values[3];
|
scope.selected = scope.values[3];
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(element.val()).toEqual('3');
|
expect(element.val()).toEqual('4');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user