diff --git a/CHANGELOG.md b/CHANGELOG.md index f1363105689b..670ed6729cf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Bug Fixes - 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 #448: [ng:options] should support iterating over objects diff --git a/src/widgets.js b/src/widgets.js index f8ada6676209..bdd68804f65b 100644 --- a/src/widgets.js +++ b/src/widgets.js @@ -596,14 +596,23 @@ angularWidget('button', inputWidgetSelector); * * binding to a value not in list confuses most browsers. * * @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. - * * _item_: local variable which will refer to the item in the _array_ during the iteration + * * _select_ `for` _value_ `in` _array_ + * * _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. * 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 - * `expression` most likely reffers to the _item_ variable. (optional) + * `expression` most likely refers to the _item_ variable. (optional) * * @example @@ -658,8 +667,8 @@ angularWidget('button', inputWidgetSelector); */ - -var NG_OPTIONS_REGEXP = /^\s*((.*)\s+as\s+)?(.*)\s+for\s+([\$\w][\$\w\d]*)\s+in\s+(.*)$/; +// 000012222111111111133330000000004555555555555555554666666777777777777777776666666888888888888888888888864000000009999 +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){ this.descend(true); this.directives(true); @@ -671,13 +680,14 @@ angularWidget('select', function(element){ } if (! (match = expression.match(NG_OPTIONS_REGEXP))) { 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 + "'."); } var displayFn = expressionCompile(match[3]).fnSelf; - var itemName = match[4]; - var itemFn = expressionCompile(match[2] || itemName).fnSelf; - var collectionFn = expressionCompile(match[5]).fnSelf; + var valueName = match[5] || match[8]; + var keyName = match[7]; + var valueFn = expressionCompile(match[2] || valueName).fnSelf; + var valuesFn = expressionCompile(match[9]).fnSelf; // we can't just jqLite(''); }); + 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(''); + expect(sortedHtml(options[1])).toEqual(''); + expect(sortedHtml(options[2])).toEqual(''); + 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(){ createSingleSelect(); scope.values = []; @@ -751,6 +772,34 @@ describe("widget", function(){ 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(){ createSingleSelect(); scope.values = [{name:'A'}];