diff options
| author | quazzie | 2013-05-14 19:56:11 +0100 | 
|---|---|---|
| committer | Pete Bacon Darwin | 2013-05-14 19:58:05 +0100 | 
| commit | c32a859bdb93699cc080f9affed4bcff63005a64 (patch) | |
| tree | 19dbc20c08ac85d0c9878eaab8ac479d57affe37 /src | |
| parent | 4acc28a310d006c62afe0de8ec82fed21c98c2d6 (diff) | |
| download | angular.js-c32a859bdb93699cc080f9affed4bcff63005a64.tar.bz2 | |
feat(select): match options by expression other than object identity
Extend ng-options with a new clause, "track by [trackByExpression]", which can be used when
working with objects.  The `trackByExpression` should uniquely identify select options objects.
This solves the problem of previously having to match ng-options objects by identity.
You can now write: `ng-options="obj as obj.name for obj in objects track by obj.id"`
The "track by" expression will be used when checking for equality of objects.
Examples:
<select
    ng-model="user.favMovieStub"
    ng-options="movie as movie.name for movie in movies track by movie.id">
</select>
scope: {
  user: { name: 'Test user', favMovieStub: { id: 1, name: 'Starwars' } }
  movies: [{ id: 1, name: 'Starwars', rating: 5, ... }, { id: 13, ... }]
}
The select input will match user favMovieStub to the first movie in the movies array, and show
"Star Wars" as the selected item.
Diffstat (limited to 'src')
| -rw-r--r-- | src/ng/directive/select.js | 60 | 
1 files changed, 48 insertions, 12 deletions
| diff --git a/src/ng/directive/select.js b/src/ng/directive/select.js index 4f4aace2..7a1cab53 100644 --- a/src/ng/directive/select.js +++ b/src/ng/directive/select.js @@ -39,7 +39,7 @@   *     * `label` **`for`** `value` **`in`** `array`   *     * `select` **`as`** `label` **`for`** `value` **`in`** `array`   *     * `label`  **`group by`** `group` **`for`** `value` **`in`** `array` - *     * `select` **`as`** `label` **`group by`** `group` **`for`** `value` **`in`** `array` + *     * `select` **`as`** `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`   *   * for object data sources:   *     * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`   *     * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object` @@ -59,6 +59,9 @@   *      element. If not specified, `select` expression will default to `value`.   *   * `group`: The result of this expression will be used to group options using the `<optgroup>`   *      DOM element. + *   * `trackexpr`: Used when working with an array of objects. The result of this expression will be + *      used to identify the objects in the array. The `trackexpr` will most likely refer to the + *     `value` variable (e.g. `value.propertyName`).   *   * @example      <doc:example> @@ -123,8 +126,8 @@  var ngOptionsDirective = valueFn({ terminal: true });  var selectDirective = ['$compile', '$parse', function($compile,   $parse) { -                         //0000111110000000000022220000000000000000000000333300000000000000444444444444444440000000005555555555555555500000006666666666666666600000000000000077770 -  var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w\d]*)|(?:\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*)$/, +                         //0000111110000000000022220000000000000000000000333300000000000000444444444444444440000000005555555555555555500000006666666666666666600000000000000007777000000000000000000088888 +  var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w\d]*)|(?:\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*?)(?:\s+track\s+by\s+(.*?))?$/,        nullModelCtrl = {$setViewValue: noop};    return { @@ -298,7 +301,7 @@ var selectDirective = ['$compile', '$parse', function($compile,   $parse) {          if (! (match = optionsExp.match(NG_OPTIONS_REGEXP))) {            throw Error( -            "Expected ngOptions in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" + +            "Expected ngOptions in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_ (track by _expr_)?'" +              " but got '" + optionsExp + "'.");          } @@ -308,6 +311,8 @@ var selectDirective = ['$compile', '$parse', function($compile,   $parse) {              groupByFn = $parse(match[3] || ''),              valueFn = $parse(match[2] ? match[1] : valueName),              valuesFn = $parse(match[7]), +            track = match[8], +            trackFn = track ? $parse(match[8]) : null,              // This is an array of array of existing option groups in DOM. We try to reuse these if possible              // optionGroupsCache[0] is the options with no option group              // optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element @@ -348,7 +353,14 @@ var selectDirective = ['$compile', '$parse', function($compile,   $parse) {                    if ((optionElement = optionGroup[index].element)[0].selected) {                      key = optionElement.val();                      if (keyName) locals[keyName] = key; -                    locals[valueName] = collection[key]; +                    if (trackFn) { +                      for (var trackIndex = 0; trackIndex < collection.length; trackIndex++) { +                        locals[valueName] = collection[trackIndex]; +                        if (trackFn(scope, locals) == key) break; +                      }  +                    } else { +                      locals[valueName] = collection[key]; +                    }                      value.push(valueFn(scope, locals));                    }                  } @@ -360,9 +372,19 @@ var selectDirective = ['$compile', '$parse', function($compile,   $parse) {                } else if (key == ''){                  value = null;                } else { -                locals[valueName] = collection[key]; -                if (keyName) locals[keyName] = key; -                value = valueFn(scope, locals); +                if (trackFn) { +                  for (var trackIndex = 0; trackIndex < collection.length; trackIndex++) { +                    locals[valueName] = collection[trackIndex]; +                    if (trackFn(scope, locals) == key) { +                      value = valueFn(scope, locals); +                      break; +                    } +                  } +                } else { +                  locals[valueName] = collection[key]; +                  if (keyName) locals[keyName] = key; +                  value = valueFn(scope, locals); +                }                }              }              ctrl.$setViewValue(value); @@ -394,7 +416,15 @@ var selectDirective = ['$compile', '$parse', function($compile,   $parse) {                label;            if (multiple) { -            selectedSet = new HashMap(modelValue); +            if (trackFn && isArray(modelValue)) { +              selectedSet = new HashMap([]); +              for (var trackIndex = 0; trackIndex < modelValue.length; trackIndex++) { +                locals[valueName] = modelValue[trackIndex]; +                selectedSet.put(trackFn(scope, locals), modelValue[trackIndex]); +              } +            } else { +              selectedSet = new HashMap(modelValue); +            }            }            // We now build up the list of options we need (we merge later) @@ -406,15 +436,21 @@ var selectDirective = ['$compile', '$parse', function($compile,   $parse) {                optionGroupNames.push(optionGroupName);              }              if (multiple) { -              selected = selectedSet.remove(valueFn(scope, locals)) != undefined; +              selected = selectedSet.remove(trackFn ? trackFn(scope, locals) : valueFn(scope, locals)) != undefined;              } else { -              selected = modelValue === valueFn(scope, locals); +              if (trackFn) { +                var modelCast = {}; +                modelCast[valueName] = modelValue; +                selected = trackFn(scope, modelCast) === trackFn(scope, locals); +              } else { +                selected = modelValue === valueFn(scope, locals); +              }                selectedSet = selectedSet || selected; // see if at least one item is selected              }              label = displayFn(scope, locals); // what will be seen by the user              label = label === undefined ? '' : label; // doing displayFn(scope, locals) || '' overwrites zero values              optionGroup.push({ -              id: keyName ? keys[index] : index,   // either the index into array or key from object +              id: trackFn ? trackFn(scope, locals) : (keyName ? keys[index] : index),   // either the index into array or key from object                label: label,                selected: selected                   // determine if we should be selected              }); | 
