21
21
* elements.
22
22
* * {@link angular.widget.@ng:required ng:required} - Verifies presence of user input.
23
23
* * {@link angular.widget.@ng:validate ng:validate} - Validates content of user input.
24
- * * {@link angular.widget.HTML HTML} - Standard HTML processed by angular.
24
+ * * {@link angular.widget.HTML HTML input elements} - Standard HTML input elements data-bound by
25
+ * angular.
25
26
* * {@link angular.widget.ng:view ng:view} - Works with $route to "include" partial templates
26
27
* * {@link angular.widget.ng:switch ng:switch} - Conditionally changes DOM structure
27
28
* * {@link angular.widget.ng:include ng:include} - Includes an external HTML fragment
@@ -574,11 +575,12 @@ angularWidget('button', inputWidgetSelector);
574
575
* @name angular.directive.ng:options
575
576
*
576
577
* @description
577
- * Dynamically generate a list of `<option>` elements for a `<select>` element using the array
578
- * obtained by evaluating the `ng:options` expression.
578
+ * Dynamically generate a list of `<option>` elements for a `<select>` element using an array or
579
+ * an object obtained by evaluating the `ng:options` expression.
579
580
*
580
- * When an item in the select menu is select, the array element represented by the selected option
581
- * will be bound to the model identified by the `name` attribute of the parent select element.
581
+ * When an item in the select menu is select, the value of array element or object property
582
+ * represented by the selected option will be bound to the model identified by the `name` attribute
583
+ * of the parent select element.
582
584
*
583
585
* Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
584
586
* be nested into the `<select>` element. This element will then represent `null` or "not selected"
@@ -596,69 +598,79 @@ angularWidget('button', inputWidgetSelector);
596
598
* * binding to a value not in list confuses most browsers.
597
599
*
598
600
* @element select
599
- * @param {comprehension_expression } comprehension in following form
600
- *
601
- * * _label_ `for` _value_ `in` _array_
602
- * * _select_ `as` _label_ `for` _value_ `in` _array_
603
- * * _select_ `as` _label_ `group by` _group_ `for` _value_ `in` _array_
604
- * * _select_ `group by` _group_ `for` _value_ `in` _array_
605
- * * _label_ `for` `(`_key_`,` _value_`)` `in` _object_
606
- * * _select_ `as` _label_ `for` `(`_key_`,` _value_`)` `in` _object_
607
- * * _select_ `as` _label_ `group by` _group_ `for` `(`_key_`,` _value_`)` `in` _object_
608
- * * _select_ `group by` _group_ `for` `(`_key_`,` _value_`)` `in` _object_
601
+ * @param {comprehension_expression } comprehension in one of the following forms:
602
+ *
603
+ * * for array data sources:
604
+ * * `label` **`for`** `value` **`in`** `array`
605
+ * * `select` **`as`** `label` **`for`** `value` **`in`** `array`
606
+ * * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
607
+ * * `select` **`as`** `label` **`group by`** `group` **`for`** `value` **`in`** `array`
608
+ * * for object data sources:
609
+ * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
610
+ * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
611
+ * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
612
+ * * `select` **`as`** `label` **`group by`** `group`
613
+ * **`for` `(`**`key`**`,`** `value`**`) in`** `object`
609
614
*
610
615
* Where:
611
616
*
612
- * * _array_ / _object_ : an expression which evaluates to an array / object to iterate over.
613
- * * _value_ : local variable which will refer to each item in the _array_ or each value of
614
- * _object_ during itteration .
615
- * * _key_ : local variable which will refer to the key in the _object_ during the iteration.
616
- * * _label_ : The result of this expression will be the ` option` label . The
617
- * `expression` will most likely refer to the _value_ variable.
618
- * * _select_ : The result of this expression will be bound to the scope. If not specified,
619
- * _select_ expression will default to _value_ .
620
- * * _group_ : The result of this expression will be used to group options using the `optgroup`
621
- * DOM element.
617
+ * * `array` / `object` : an expression which evaluates to an array / object to iterate over.
618
+ * * `value` : local variable which will refer to each item in the `array` or each property value
619
+ * of `object` during iteration .
620
+ * * `key` : local variable which will refer to a property name in `object` during iteration.
621
+ * * `label` : The result of this expression will be the label for `< option>` element . The
622
+ * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`) .
623
+ * * `select` : The result of this expression will be bound to the model of the parent `<select>`
624
+ * element. If not specified, `select` expression will default to `value` .
625
+ * * `group` : The result of this expression will be used to group options using the `< optgroup> `
626
+ * DOM element.
622
627
*
623
628
* @example
624
629
<doc:example>
625
630
<doc:source>
626
631
<script>
627
632
function MyCntrl(){
628
633
this.colors = [
629
- {name:'black'},
630
- {name:'white'},
631
- {name:'red'},
632
- {name:'blue'},
633
- {name:'green '}
634
+ {name:'black', shade:'dark' },
635
+ {name:'white', shade:'light' },
636
+ {name:'red', shade:'dark' },
637
+ {name:'blue', shade:'dark' },
638
+ {name:'yellow', shade:'light '}
634
639
];
635
640
this.color = this.colors[2]; // red
636
641
}
637
642
</script>
638
643
<div ng:controller="MyCntrl">
639
644
<ul>
640
645
<li ng:repeat="color in colors">
641
- Name: <input name="color.name"/> [<a href ng:click="colors.$remove(color)">X</a>]
646
+ Name: <input name="color.name">
647
+ [<a href ng:click="colors.$remove(color)">X</a>]
642
648
</li>
643
649
<li>
644
650
[<a href ng:click="colors.push({})">add</a>]
645
651
</li>
646
652
</ul>
647
653
<hr/>
648
654
Color (null not allowed):
649
- <select name="color" ng:options="c.name for c in colors"></select><br/ >
655
+ <select name="color" ng:options="c.name for c in colors"></select><br>
650
656
651
657
Color (null allowed):
652
- <select name="color" ng:options="c.name for c in colors">
653
- <option value="">-- chose color --</option>
658
+ <div class="nullable">
659
+ <select name="color" ng:options="c.name for c in colors">
660
+ <option value="">-- chose color --</option>
661
+ </select>
662
+ </div><br/>
663
+
664
+ Color grouped by shade:
665
+ <select name="color" ng:options="c.name group by c.shade for c in colors">
654
666
</select><br/>
655
667
656
- Select <a href ng:click="color={name:'not in list'}">bogus</a>. <br/>
668
+
669
+ Select <a href ng:click="color={name:'not in list'}">bogus</a>.<br>
657
670
<hr/>
658
671
Currently selected: {{ {selected_color:color} }}
659
- <div style="border:solid 1px black;"
672
+ <div style="border:solid 1px black; height:20px "
660
673
ng:style="{'background-color':color.name}">
661
-
662
674
</div>
663
675
</div>
664
676
</doc:source>
@@ -667,7 +679,7 @@ angularWidget('button', inputWidgetSelector);
667
679
expect(binding('color')).toMatch('red');
668
680
select('color').option('0');
669
681
expect(binding('color')).toMatch('black');
670
- select('color').option('');
682
+ using('.nullable'). select('color').option('');
671
683
expect(binding('color')).toMatch('null');
672
684
});
673
685
</doc:scenario>
@@ -678,37 +690,41 @@ var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+
678
690
angularWidget ( 'select' , function ( element ) {
679
691
this . descend ( true ) ;
680
692
this . directives ( true ) ;
681
- var isMultiselect = element . attr ( 'multiple' ) ;
682
- var expression = element . attr ( 'ng:options' ) ;
683
- var onChange = expressionCompile ( element . attr ( 'ng:change' ) || "" ) . fnSelf ;
684
- var match ;
693
+
694
+ var isMultiselect = element . attr ( 'multiple' ) ,
695
+ expression = element . attr ( 'ng:options' ) ,
696
+ onChange = expressionCompile ( element . attr ( 'ng:change' ) || "" ) . fnSelf ,
697
+ match ;
698
+
685
699
if ( ! expression ) {
686
700
return inputWidgetSelector . call ( this , element ) ;
687
701
}
688
702
if ( ! ( match = expression . match ( NG_OPTIONS_REGEXP ) ) ) {
689
703
throw Error (
690
- "Expected ng:options in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_' but got '" +
691
- expression + "'." ) ;
704
+ "Expected ng:options in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
705
+ " but got '" + expression + "'." ) ;
692
706
}
693
- var displayFn = expressionCompile ( match [ 2 ] || match [ 1 ] ) . fnSelf ;
694
- var valueName = match [ 4 ] || match [ 6 ] ;
695
- var keyName = match [ 5 ] ;
696
- var groupByFn = expressionCompile ( match [ 3 ] || '' ) . fnSelf ;
697
- var valueFn = expressionCompile ( match [ 2 ] ? match [ 1 ] : valueName ) . fnSelf ;
698
- var valuesFn = expressionCompile ( match [ 7 ] ) . fnSelf ;
699
- // we can't just jqLite('<option>') since jqLite is not smart enough
700
- // to create it in <select> and IE barfs otherwise.
701
- var optionTemplate = jqLite ( document . createElement ( 'option' ) ) ;
702
- var optGroupTemplate = jqLite ( document . createElement ( 'optgroup' ) ) ;
703
- var nullOption = false ; // if false then user will not be able to select it
707
+
708
+ var displayFn = expressionCompile ( match [ 2 ] || match [ 1 ] ) . fnSelf ,
709
+ valueName = match [ 4 ] || match [ 6 ] ,
710
+ keyName = match [ 5 ] ,
711
+ groupByFn = expressionCompile ( match [ 3 ] || '' ) . fnSelf ,
712
+ valueFn = expressionCompile ( match [ 2 ] ? match [ 1 ] : valueName ) . fnSelf ,
713
+ valuesFn = expressionCompile ( match [ 7 ] ) . fnSelf ,
714
+ // we can't just jqLite('<option>') since jqLite is not smart enough
715
+ // to create it in <select> and IE barfs otherwise.
716
+ optionTemplate = jqLite ( document . createElement ( 'option' ) ) ,
717
+ optGroupTemplate = jqLite ( document . createElement ( 'optgroup' ) ) ,
718
+ nullOption = false ; // if false then user will not be able to select it
719
+
704
720
return function ( selectElement ) {
705
- var scope = this ;
706
721
707
722
// This is an array of array of existing option groups in DOM. We try to reuse these if possible
708
723
// optionGroupsCache[0] is the options with no option group
709
724
// optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element
710
- var optionGroupsCache = [ [ { element : selectElement , label :'' } ] ] ;
711
- var model = modelAccessor ( scope , element ) ;
725
+ var optionGroupsCache = [ [ { element : selectElement , label :'' } ] ] ,
726
+ scope = this ,
727
+ model = modelAccessor ( scope , element ) ;
712
728
713
729
// find existing special options
714
730
forEach ( selectElement . children ( ) , function ( option ) {
@@ -719,13 +735,12 @@ angularWidget('select', function(element){
719
735
selectElement . html ( '' ) ; // clear contents
720
736
721
737
selectElement . bind ( 'change' , function ( ) {
722
- var optionGroup ;
723
- var collection = valuesFn ( scope ) || [ ] ;
724
- var key = selectElement . val ( ) ;
725
- var value ;
726
- var optionElement ;
727
- var index , groupIndex , length , groupLength ;
728
- var tempScope = scope . $new ( ) ;
738
+ var optionGroup ,
739
+ collection = valuesFn ( scope ) || [ ] ,
740
+ key = selectElement . val ( ) ,
741
+ tempScope = scope . $new ( ) ,
742
+ value , optionElement , index , groupIndex , length , groupLength ;
743
+
729
744
try {
730
745
if ( isMultiselect ) {
731
746
value = [ ] ;
@@ -767,31 +782,27 @@ angularWidget('select', function(element){
767
782
} ) ;
768
783
769
784
scope . $onEval ( function ( ) {
770
- var scope = this ;
771
-
772
- // Temporary location for the option groups before we render them
773
- var optionGroups = {
774
- '' :[ ]
775
- } ;
776
- var optionGroupNames = [ '' ] ;
777
- var optionGroupName ;
778
- var optionGroup ;
779
- var option ;
780
- var existingParent , existingOptions , existingOption ;
781
- var values = valuesFn ( scope ) || [ ] ;
782
- var keys = values ;
783
- var key ;
784
- var groupLength , length ;
785
- var fragment ;
786
- var groupIndex , index ;
787
- var optionElement ;
788
- var optionScope = scope . $new ( ) ;
789
- var modelValue = model . get ( ) ;
790
- var selected ;
791
- var selectedSet = false ; // nothing is selected yet
792
- var isMulti = isMultiselect ;
793
- var lastElement ;
794
- var element ;
785
+ var scope = this ,
786
+ optionGroups = { '' :[ ] } , // Temporary location for the option groups before we render them
787
+ optionGroupNames = [ '' ] ,
788
+ optionGroupName ,
789
+ optionGroup ,
790
+ option ,
791
+ existingParent , existingOptions , existingOption ,
792
+ values = valuesFn ( scope ) || [ ] ,
793
+ keys = values ,
794
+ key ,
795
+ groupLength , length ,
796
+ fragment ,
797
+ groupIndex , index ,
798
+ optionElement ,
799
+ optionScope = scope . $new ( ) ,
800
+ modelValue = model . get ( ) ,
801
+ selected ,
802
+ selectedSet = false , // nothing is selected yet
803
+ isMulti = isMultiselect ,
804
+ lastElement ,
805
+ element ;
795
806
796
807
try {
797
808
if ( isMulti ) {
@@ -807,8 +818,7 @@ angularWidget('select', function(element){
807
818
selectedSet = true ;
808
819
}
809
820
810
- // If we have a keyName then we are iterating over on object. We
811
- // grab the keys and sort them.
821
+ // If we have a keyName then we are iterating over on object. Grab the keys and sort them.
812
822
if ( keyName ) {
813
823
keys = [ ] ;
814
824
for ( key in values ) {
@@ -930,6 +940,7 @@ angularWidget('select', function(element){
930
940
} ;
931
941
} ) ;
932
942
943
+
933
944
/**
934
945
* @workInProgress
935
946
* @ngdoc widget
0 commit comments