mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-05-12 17:03:09 +00:00
fix(directive): ng:options to support iterating over objects
Closes #448
This commit is contained in:
parent
c348f2cad6
commit
7802c90e13
3 changed files with 93 additions and 20 deletions
|
|
@ -4,6 +4,7 @@
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
- Issue #449: [ng:options] should support binding to a property of an item.
|
- Issue #449: [ng:options] should support binding to a property of an item.
|
||||||
- Issue #464: [ng:options] incorrectly re-grew options on datasource change
|
- Issue #464: [ng:options] incorrectly re-grew options on datasource change
|
||||||
|
- Issue #448: [ng:options] should support iterating over objects
|
||||||
|
|
||||||
### Breaking changes
|
### Breaking changes
|
||||||
- no longer support MMMMM in filter.date as we need to follow UNICODE LOCALE DATA formats.
|
- no longer support MMMMM in filter.date as we need to follow UNICODE LOCALE DATA formats.
|
||||||
|
|
|
||||||
|
|
@ -596,14 +596,23 @@ angularWidget('button', inputWidgetSelector);
|
||||||
* * binding to a value not in list confuses most browsers.
|
* * binding to a value not in list confuses most browsers.
|
||||||
*
|
*
|
||||||
* @element select
|
* @element select
|
||||||
* @param {comprehension_expression} comprehension _select_ `as` _label_ `for` _item_ `in` _array_.
|
* @param {comprehension_expression} comprehension in following form
|
||||||
*
|
*
|
||||||
* * _array_: an expression which evaluates to an array of objects to bind.
|
* * _select_ `for` _value_ `in` _array_
|
||||||
* * _item_: local variable which will refer to the item in the _array_ during the iteration
|
* * _select_ `as` _label_ `for` _value_ `in` _array_
|
||||||
|
* * _select_ `for` `(`_key_`,` _value_`)` `in` _object_
|
||||||
|
* * _select_ `as` _label_ `for` `(`_key_`,` _value_`)` `in` _object_
|
||||||
|
*
|
||||||
|
* Where:
|
||||||
|
*
|
||||||
|
* * _array_ / _object_: an expression which evaluates to an array / object to iterate over.
|
||||||
|
* * _value_: local variable which will reffer to the item in the _array_ or _object_ during
|
||||||
|
* iteration
|
||||||
|
* * _key_: local variable which will refer to the key in the _object_ during the iteration
|
||||||
* * _select_: The result of this expression will be assigned to the scope.
|
* * _select_: The result of this expression will be assigned to the scope.
|
||||||
* The _select_ can be ommited, in which case the _item_ itself will be assigned.
|
* The _select_ can be ommited, in which case the _item_ itself will be assigned.
|
||||||
* * _label_: The result of this expression will be the `option` label. The
|
* * _label_: The result of this expression will be the `option` label. The
|
||||||
* `expression` most likely reffers to the _item_ variable. (optional)
|
* `expression` most likely refers to the _item_ variable. (optional)
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
<doc:example>
|
<doc:example>
|
||||||
|
|
@ -658,8 +667,8 @@ angularWidget('button', inputWidgetSelector);
|
||||||
</doc:scenario>
|
</doc:scenario>
|
||||||
</doc:example>
|
</doc:example>
|
||||||
*/
|
*/
|
||||||
|
// 000012222111111111133330000000004555555555555555554666666777777777777777776666666888888888888888888888864000000009999
|
||||||
var NG_OPTIONS_REGEXP = /^\s*((.*)\s+as\s+)?(.*)\s+for\s+([\$\w][\$\w\d]*)\s+in\s+(.*)$/;
|
var NG_OPTIONS_REGEXP = /^\s*((.*)\s+as\s+)?(.*)\s+for\s+(([\$\w][\$\w\d]*)|(\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*)$/;
|
||||||
angularWidget('select', function(element){
|
angularWidget('select', function(element){
|
||||||
this.descend(true);
|
this.descend(true);
|
||||||
this.directives(true);
|
this.directives(true);
|
||||||
|
|
@ -671,13 +680,14 @@ angularWidget('select', function(element){
|
||||||
}
|
}
|
||||||
if (! (match = expression.match(NG_OPTIONS_REGEXP))) {
|
if (! (match = expression.match(NG_OPTIONS_REGEXP))) {
|
||||||
throw Error(
|
throw Error(
|
||||||
"Expected ng:options in form of '(_expression_ as)? _expresion_ for _item_ in _collection_' but got '" +
|
"Expected ng:options in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_' but got '" +
|
||||||
expression + "'.");
|
expression + "'.");
|
||||||
}
|
}
|
||||||
var displayFn = expressionCompile(match[3]).fnSelf;
|
var displayFn = expressionCompile(match[3]).fnSelf;
|
||||||
var itemName = match[4];
|
var valueName = match[5] || match[8];
|
||||||
var itemFn = expressionCompile(match[2] || itemName).fnSelf;
|
var keyName = match[7];
|
||||||
var collectionFn = expressionCompile(match[5]).fnSelf;
|
var valueFn = expressionCompile(match[2] || valueName).fnSelf;
|
||||||
|
var valuesFn = expressionCompile(match[9]).fnSelf;
|
||||||
// we can't just jqLite('<option>') since jqLite is not smart enough
|
// we can't just jqLite('<option>') since jqLite is not smart enough
|
||||||
// to create it in <select> and IE barfs otherwise.
|
// to create it in <select> and IE barfs otherwise.
|
||||||
var option = jqLite(document.createElement('option'));
|
var option = jqLite(document.createElement('option'));
|
||||||
|
|
@ -696,7 +706,7 @@ angularWidget('select', function(element){
|
||||||
});
|
});
|
||||||
|
|
||||||
select.bind('change', function(){
|
select.bind('change', function(){
|
||||||
var collection = collectionFn(scope) || [];
|
var collection = valuesFn(scope) || [];
|
||||||
var value = select.val();
|
var value = select.val();
|
||||||
var index, length;
|
var index, length;
|
||||||
var tempScope = scope.$new();
|
var tempScope = scope.$new();
|
||||||
|
|
@ -705,8 +715,8 @@ angularWidget('select', function(element){
|
||||||
value = [];
|
value = [];
|
||||||
for (index = 0, length = optionElements.length; index < length; index++) {
|
for (index = 0, length = optionElements.length; index < length; index++) {
|
||||||
if (optionElements[index][0].selected) {
|
if (optionElements[index][0].selected) {
|
||||||
tempScope[itemName] = collection[index];
|
tempScope[valueName] = collection[index];
|
||||||
value.push(itemFn(tempScope));
|
value.push(valueFn(tempScope));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -715,8 +725,8 @@ angularWidget('select', function(element){
|
||||||
} else if (value == ''){
|
} else if (value == ''){
|
||||||
value = null;
|
value = null;
|
||||||
} else {
|
} else {
|
||||||
tempScope[itemName] = collection[value];
|
tempScope[valueName] = collection[value];
|
||||||
value = itemFn(tempScope);
|
value = valueFn(tempScope);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isUndefined(value)) model.set(value);
|
if (!isUndefined(value)) model.set(value);
|
||||||
|
|
@ -730,7 +740,9 @@ angularWidget('select', function(element){
|
||||||
|
|
||||||
scope.$onEval(function(){
|
scope.$onEval(function(){
|
||||||
var scope = this;
|
var scope = this;
|
||||||
var collection = collectionFn(scope) || [];
|
var values = valuesFn(scope) || [];
|
||||||
|
var keys = values;
|
||||||
|
var key;
|
||||||
var value;
|
var value;
|
||||||
var length;
|
var length;
|
||||||
var fragment;
|
var fragment;
|
||||||
|
|
@ -753,9 +765,20 @@ angularWidget('select', function(element){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (index = 0, length = collection.length; index < length; index++) {
|
// If we have a keyName then we are itterating over on object. We
|
||||||
optionScope[itemName] = collection[index];
|
// grab the keys and sort them.
|
||||||
currentItem = itemFn(optionScope);
|
if(keyName) {
|
||||||
|
keys = [];
|
||||||
|
for (key in values) {
|
||||||
|
if (values.hasOwnProperty(key))
|
||||||
|
keys.push(key);
|
||||||
|
}
|
||||||
|
keys.sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (index = 0; length = keys.length, index < length; index++) {
|
||||||
|
optionScope[valueName] = values[keyName ? optionScope[keyName]=keys[index]:index];
|
||||||
|
currentItem = valueFn(optionScope);
|
||||||
optionText = displayFn(optionScope);
|
optionText = displayFn(optionScope);
|
||||||
if (optionTexts.length > index) {
|
if (optionTexts.length > index) {
|
||||||
// reuse
|
// reuse
|
||||||
|
|
|
||||||
|
|
@ -611,7 +611,7 @@ describe("widget", function(){
|
||||||
it('should throw when not formated "? for ? in ?"', function(){
|
it('should throw when not formated "? for ? in ?"', function(){
|
||||||
expect(function(){
|
expect(function(){
|
||||||
compile('<select name="selected" ng:options="i dont parse"></select>');
|
compile('<select name="selected" ng:options="i dont parse"></select>');
|
||||||
}).toThrow("Expected ng:options in form of '(_expression_ as)? _expresion_ for _item_ in _collection_' but got 'i dont parse'.");
|
}).toThrow("Expected ng:options in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_' but got 'i dont parse'.");
|
||||||
|
|
||||||
$logMock.error.logs.shift();
|
$logMock.error.logs.shift();
|
||||||
});
|
});
|
||||||
|
|
@ -628,6 +628,27 @@ describe("widget", function(){
|
||||||
expect(sortedHtml(options[2])).toEqual('<option value="2">C</option>');
|
expect(sortedHtml(options[2])).toEqual('<option value="2">C</option>');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should render an object', function(){
|
||||||
|
createSelect({
|
||||||
|
name:'selected',
|
||||||
|
'ng:options': 'value as key for (key, value) in object'
|
||||||
|
});
|
||||||
|
scope.object = {'red':'FF0000', 'green':'00FF00', 'blue':'0000FF'};
|
||||||
|
scope.selected = scope.object.red;
|
||||||
|
scope.$eval();
|
||||||
|
var options = select.find('option');
|
||||||
|
expect(options.length).toEqual(3);
|
||||||
|
expect(sortedHtml(options[0])).toEqual('<option value="0">blue</option>');
|
||||||
|
expect(sortedHtml(options[1])).toEqual('<option value="1">green</option>');
|
||||||
|
expect(sortedHtml(options[2])).toEqual('<option value="2">red</option>');
|
||||||
|
expect(options[2].selected).toEqual(true);
|
||||||
|
|
||||||
|
scope.object.azur = '8888FF';
|
||||||
|
scope.$eval();
|
||||||
|
options = select.find('option');
|
||||||
|
expect(options[3].selected).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
it('should grow list', function(){
|
it('should grow list', function(){
|
||||||
createSingleSelect();
|
createSingleSelect();
|
||||||
scope.values = [];
|
scope.values = [];
|
||||||
|
|
@ -751,6 +772,34 @@ describe("widget", function(){
|
||||||
expect(select.val()).toEqual('1');
|
expect(select.val()).toEqual('1');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should bind to object key', function(){
|
||||||
|
createSelect({
|
||||||
|
name:'selected',
|
||||||
|
'ng:options':'key as value for (key, value) in object'});
|
||||||
|
scope.object = {'red':'FF0000', 'green':'00FF00', 'blue':'0000FF'};
|
||||||
|
scope.selected = 'green';
|
||||||
|
scope.$eval();
|
||||||
|
expect(select.val()).toEqual('1');
|
||||||
|
|
||||||
|
scope.selected = 'blue';
|
||||||
|
scope.$eval();
|
||||||
|
expect(select.val()).toEqual('0');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should bind to object value', function(){
|
||||||
|
createSelect({
|
||||||
|
name:'selected',
|
||||||
|
'ng:options':'value as key for (key, value) in object'});
|
||||||
|
scope.object = {'red':'FF0000', 'green':'00FF00', 'blue':'0000FF'};
|
||||||
|
scope.selected = '00FF00';
|
||||||
|
scope.$eval();
|
||||||
|
expect(select.val()).toEqual('1');
|
||||||
|
|
||||||
|
scope.selected = '0000FF';
|
||||||
|
scope.$eval();
|
||||||
|
expect(select.val()).toEqual('0');
|
||||||
|
});
|
||||||
|
|
||||||
it('should insert a blank option if bound to null', function(){
|
it('should insert a blank option if bound to null', function(){
|
||||||
createSingleSelect();
|
createSingleSelect();
|
||||||
scope.values = [{name:'A'}];
|
scope.values = [{name:'A'}];
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue