@@ -35,6 +35,12 @@ var ngOptionsMinErr = minErr('ngOptions');
35
35
* be bound to string values at present.
36
36
* </div>
37
37
*
38
+ * <div class="alert alert-info">
39
+ * **Note:** Using `selectAs` will bind the result of the `selectAs` expression to the model, but
40
+ * the value of the `select` and `option` elements will be either the index (for array data sources)
41
+ * or property name (for object data sources) of the value within the collection.
42
+ * </div>
43
+ *
38
44
* @param {string } ngModel Assignable angular expression to data-bind to.
39
45
* @param {string= } name Property name of the form under which the control is published.
40
46
* @param {string= } required The control is considered valid only if value is entered.
@@ -69,7 +75,9 @@ var ngOptionsMinErr = minErr('ngOptions');
69
75
* DOM element.
70
76
* * `trackexpr`: Used when working with an array of objects. The result of this expression will be
71
77
* used to identify the objects in the array. The `trackexpr` will most likely refer to the
72
- * `value` variable (e.g. `value.propertyName`).
78
+ * `value` variable (e.g. `value.propertyName`). With this the selection is preserved
79
+ * even when the options are recreated (e.g. reloaded from the server).
80
+ * Note that this does not work when there is a `select` expression that is different from the `trackexpr`.
73
81
*
74
82
* @example
75
83
<example module="selectExample">
@@ -147,6 +155,28 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
147
155
nullModelCtrl = { $setViewValue : noop } ;
148
156
// jshint maxlen: 100
149
157
158
+ function isSelected ( selectAsFn , trackFn , valueName , valueFn , scope , viewValue , locals , selectedSet , mult ) {
159
+ var compareValueFn ;
160
+ if ( selectAsFn ) {
161
+ compareValueFn = selectAsFn ;
162
+ } else if ( trackFn ) {
163
+ var withValueName = { } ;
164
+ withValueName [ valueName ] = viewValue ;
165
+ compareValueFn = trackFn ;
166
+
167
+ viewValue = trackFn ( withValueName ) ;
168
+ } else {
169
+ compareValueFn = valueFn ;
170
+ }
171
+
172
+ var optionValue = compareValueFn ( scope , locals ) ;
173
+
174
+ if ( mult ) {
175
+ return isDefined ( selectedSet . remove ( optionValue ) ) ;
176
+ }
177
+ return viewValue === optionValue ;
178
+ } ;
179
+
150
180
return {
151
181
restrict : 'E' ,
152
182
require : [ 'select' , '?ngModel' ] ,
@@ -326,6 +356,8 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
326
356
327
357
var displayFn = $parse ( match [ 2 ] || match [ 1 ] ) ,
328
358
valueName = match [ 4 ] || match [ 6 ] ,
359
+ selectAs = / a s / . test ( match [ 0 ] ) && match [ 1 ] ,
360
+ selectAsFn = selectAs ? $parse ( selectAs ) : null ,
329
361
keyName = match [ 5 ] ,
330
362
groupByFn = $parse ( match [ 3 ] || '' ) ,
331
363
valueFn = $parse ( match [ 2 ] ? match [ 1 ] : valueName ) ,
@@ -371,41 +403,12 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
371
403
372
404
for ( index = 1 , length = optionGroup . length ; index < length ; index ++ ) {
373
405
if ( ( optionElement = optionGroup [ index ] . element ) [ 0 ] . selected ) {
374
- key = optionElement . val ( ) ;
375
- if ( keyName ) locals [ keyName ] = key ;
376
- if ( trackFn ) {
377
- for ( trackIndex = 0 ; trackIndex < collection . length ; trackIndex ++ ) {
378
- locals [ valueName ] = collection [ trackIndex ] ;
379
- if ( trackFn ( scope , locals ) == key ) break ;
380
- }
381
- } else {
382
- locals [ valueName ] = collection [ key ] ;
383
- }
384
- value . push ( valueFn ( scope , locals ) ) ;
406
+ value . push ( getViewValue ( optionElement , scope , locals , collection , true ) )
385
407
}
386
408
}
387
409
}
388
410
} else {
389
- key = selectElement . val ( ) ;
390
- if ( key == '?' ) {
391
- value = undefined ;
392
- } else if ( key === '' ) {
393
- value = null ;
394
- } else {
395
- if ( trackFn ) {
396
- for ( trackIndex = 0 ; trackIndex < collection . length ; trackIndex ++ ) {
397
- locals [ valueName ] = collection [ trackIndex ] ;
398
- if ( trackFn ( scope , locals ) == key ) {
399
- value = valueFn ( scope , locals ) ;
400
- break ;
401
- }
402
- }
403
- } else {
404
- locals [ valueName ] = collection [ key ] ;
405
- if ( keyName ) locals [ keyName ] = key ;
406
- value = valueFn ( scope , locals ) ;
407
- }
408
- }
411
+ value = getViewValue ( selectElement , scope , locals , collection ) ;
409
412
}
410
413
ctrl . $setViewValue ( value ) ;
411
414
render ( ) ;
@@ -437,7 +440,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
437
440
var selectedSet = false ;
438
441
if ( multiple ) {
439
442
var viewValue = ctrl . $viewValue ;
440
- if ( trackFn && isArray ( viewValue ) ) {
443
+ if ( trackFn && isArray ( viewValue ) && ! selectAs ) {
441
444
selectedSet = new HashMap ( [ ] ) ;
442
445
var locals = { } ;
443
446
for ( var trackIndex = 0 ; trackIndex < viewValue . length ; trackIndex ++ ) {
@@ -451,6 +454,58 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
451
454
return selectedSet ;
452
455
}
453
456
457
+ function getViewValue ( el , scope , locals , collection , multiple ) {
458
+ var key = el . val ( ) ;
459
+ var value ;
460
+ var calculateViewValue ;
461
+
462
+ if ( selectAsFn || trackFn ) {
463
+ calculateViewValue = function ( ) {
464
+ var getterFn = selectAsFn || trackFn ;
465
+ var wrappedCollectionValue = { } ;
466
+ wrappedCollectionValue [ valueName ] = collection [ key ] ;
467
+ wrappedCollectionValue [ keyName ] = key ;
468
+
469
+ for ( var i in collection ) {
470
+ if ( collection . hasOwnProperty ( i ) ) {
471
+ locals [ valueName ] = collection [ i ] ;
472
+ if ( keyName ) locals [ keyName ] = i ;
473
+ if ( getterFn ( scope , locals ) ==
474
+ getterFn ( scope , wrappedCollectionValue ) ) {
475
+ /*
476
+ * trackBy should not be used for final calculation, because it doesn't
477
+ * necessarily return the expected value.
478
+ */
479
+ return ( selectAsFn || valueFn ) ( scope , locals ) ;
480
+ }
481
+ }
482
+ }
483
+ } ;
484
+ } else {
485
+ calculateViewValue = function ( ) {
486
+ locals [ valueName ] = collection [ key ] ;
487
+ if ( keyName ) locals [ keyName ] = key ;
488
+ return valueFn ( scope , locals ) ;
489
+ } ;
490
+ }
491
+
492
+ if ( multiple ) {
493
+ if ( keyName ) locals [ keyName ] = key ;
494
+ calculateViewValue ( ) ;
495
+ return ( selectAsFn || valueFn ) ( scope , locals ) ;
496
+ }
497
+
498
+ if ( key == '?' ) {
499
+ value = undefined ;
500
+ } else if ( key === '' ) {
501
+ value = null ;
502
+ } else {
503
+ value = calculateViewValue ( ) ;
504
+ }
505
+
506
+ return value ;
507
+ }
508
+
454
509
455
510
function scheduleRendering ( ) {
456
511
if ( ! renderScheduled ) {
@@ -483,7 +538,6 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
483
538
element ,
484
539
label ;
485
540
486
-
487
541
// We now build up the list of options we need (we merge later)
488
542
for ( index = 0 ; length = keys . length , index < length ; index ++ ) {
489
543
@@ -501,27 +555,26 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
501
555
optionGroup = optionGroups [ optionGroupName ] = [ ] ;
502
556
optionGroupNames . push ( optionGroupName ) ;
503
557
}
504
- if ( multiple ) {
505
- selected = isDefined (
506
- selectedSet . remove ( trackFn ? trackFn ( scope , locals ) : valueFn ( scope , locals ) )
507
- ) ;
508
- } else {
509
- if ( trackFn ) {
510
- var modelCast = { } ;
511
- modelCast [ valueName ] = viewValue ;
512
- selected = trackFn ( scope , modelCast ) === trackFn ( scope , locals ) ;
513
- } else {
514
- selected = viewValue === valueFn ( scope , locals ) ;
515
- }
516
- selectedSet = selectedSet || selected ; // see if at least one item is selected
517
- }
558
+
559
+ selected = isSelected (
560
+ selectAsFn ,
561
+ trackFn ,
562
+ valueName ,
563
+ valueFn ,
564
+ scope ,
565
+ viewValue ,
566
+ locals ,
567
+ selectedSet ,
568
+ multiple ) ;
569
+ selectedSet = selectedSet || selected ;
570
+
518
571
label = displayFn ( scope , locals ) ; // what will be seen by the user
519
572
520
573
// doing displayFn(scope, locals) || '' overwrites zero values
521
574
label = isDefined ( label ) ? label : '' ;
522
575
optionGroup . push ( {
523
576
// either the index into array or key from object
524
- id : trackFn ? trackFn ( scope , locals ) : ( keyName ? keys [ index ] : index ) ,
577
+ id : ( keyName ? keys [ index ] : index ) ,
525
578
label : label ,
526
579
selected : selected // determine if we should be selected
527
580
} ) ;
0 commit comments