Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit da9eac8

Browse files
sjbarkerpetebacondarwin
authored andcommitted
feat(ngOptions): add support for disabling an option
This patch adds support for disabling options based on model values. The "disable when" syntax allows for listening to changes on those model values, in order to dynamically enable and disable the options. The changes prevent disabled options from being written to the selectCtrl from the model. If a disabled selection is present on the model, normal unknown or empty functionality kicks in. Closes #638 Closes #11017
1 parent 5b52286 commit da9eac8

File tree

2 files changed

+302
-24
lines changed

2 files changed

+302
-24
lines changed

src/ng/directive/ngOptions.js

+53-24
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,20 @@ var ngOptionsMinErr = minErr('ngOptions');
9494
* * `label` **`for`** `value` **`in`** `array`
9595
* * `select` **`as`** `label` **`for`** `value` **`in`** `array`
9696
* * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
97+
* * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array`
9798
* * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
99+
* * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
98100
* * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr`
99101
* (for including a filter with `track by`)
100102
* * for object data sources:
101103
* * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
102104
* * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
103105
* * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
106+
* * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object`
104107
* * `select` **`as`** `label` **`group by`** `group`
105108
* **`for` `(`**`key`**`,`** `value`**`) in`** `object`
109+
* * `select` **`as`** `label` **`disable when`** `disable`
110+
* **`for` `(`**`key`**`,`** `value`**`) in`** `object`
106111
*
107112
* Where:
108113
*
@@ -116,6 +121,8 @@ var ngOptionsMinErr = minErr('ngOptions');
116121
* element. If not specified, `select` expression will default to `value`.
117122
* * `group`: The result of this expression will be used to group options using the `<optgroup>`
118123
* DOM element.
124+
* * `disable`: The result of this expression will be used to disable the rendered `<option>`
125+
* element. Return `true` to disable.
119126
* * `trackexpr`: Used when working with an array of objects. The result of this expression will be
120127
* used to identify the objects in the array. The `trackexpr` will most likely refer to the
121128
* `value` variable (e.g. `value.propertyName`). With this the selection is preserved
@@ -129,10 +136,10 @@ var ngOptionsMinErr = minErr('ngOptions');
129136
.controller('ExampleController', ['$scope', function($scope) {
130137
$scope.colors = [
131138
{name:'black', shade:'dark'},
132-
{name:'white', shade:'light'},
139+
{name:'white', shade:'light', notAnOption: true},
133140
{name:'red', shade:'dark'},
134-
{name:'blue', shade:'dark'},
135-
{name:'yellow', shade:'light'}
141+
{name:'blue', shade:'dark', notAnOption: true},
142+
{name:'yellow', shade:'light', notAnOption: false}
136143
];
137144
$scope.myColor = $scope.colors[2]; // red
138145
}]);
@@ -141,6 +148,7 @@ var ngOptionsMinErr = minErr('ngOptions');
141148
<ul>
142149
<li ng-repeat="color in colors">
143150
Name: <input ng-model="color.name">
151+
<input type="checkbox" ng-model="color.notAnOption"> Disabled?
144152
[<a href ng-click="colors.splice($index, 1)">X</a>]
145153
</li>
146154
<li>
@@ -162,6 +170,12 @@ var ngOptionsMinErr = minErr('ngOptions');
162170
<select ng-model="myColor" ng-options="color.name group by color.shade for color in colors">
163171
</select><br/>
164172
173+
Color grouped by shade, with some disabled:
174+
<select ng-model="myColor"
175+
ng-options="color.name group by color.shade disable when color.notAnOption for color in colors">
176+
</select><br/>
177+
178+
165179
166180
Select <a href ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</a>.<br>
167181
<hr/>
@@ -186,16 +200,17 @@ var ngOptionsMinErr = minErr('ngOptions');
186200
*/
187201

188202
// jshint maxlen: false
189-
//000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888
190-
var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/;
203+
// //00001111111111000000000002222222222000000000000000000000333333333300000000000000000000000004444444444400000000000005555555555555550000000006666666666666660000000777777777777777000000000000000888888888800000000000000000009999999999
204+
var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?(?:\s+disable\s+when\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/;
191205
// 1: value expression (valueFn)
192206
// 2: label expression (displayFn)
193207
// 3: group by expression (groupByFn)
194-
// 4: array item variable name
195-
// 5: object item key variable name
196-
// 6: object item value variable name
197-
// 7: collection expression
198-
// 8: track by expression
208+
// 4: disable when expression (disableWhenFn)
209+
// 5: array item variable name
210+
// 6: object item key variable name
211+
// 7: object item value variable name
212+
// 8: collection expression
213+
// 9: track by expression
199214
// jshint maxlen: 100
200215

201216

@@ -215,14 +230,14 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
215230
// Extract the parts from the ngOptions expression
216231

217232
// The variable name for the value of the item in the collection
218-
var valueName = match[4] || match[6];
233+
var valueName = match[5] || match[7];
219234
// The variable name for the key of the item in the collection
220-
var keyName = match[5];
235+
var keyName = match[6];
221236

222237
// An expression that generates the viewValue for an option if there is a label expression
223238
var selectAs = / as /.test(match[0]) && match[1];
224239
// An expression that is used to track the id of each object in the options collection
225-
var trackBy = match[8];
240+
var trackBy = match[9];
226241
// An expression that generates the viewValue for an option if there is no label expression
227242
var valueFn = $parse(match[2] ? match[1] : valueName);
228243
var selectAsFn = selectAs && $parse(selectAs);
@@ -237,7 +252,8 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
237252
function getHashOfValue(viewValue) { return hashKey(viewValue); };
238253
var displayFn = $parse(match[2] || match[1]);
239254
var groupByFn = $parse(match[3] || '');
240-
var valuesFn = $parse(match[7]);
255+
var disableWhenFn = $parse(match[4] || '');
256+
var valuesFn = $parse(match[8]);
241257

242258
var locals = {};
243259
var getLocals = keyName ? function(value, key) {
@@ -250,11 +266,12 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
250266
};
251267

252268

253-
function Option(selectValue, viewValue, label, group) {
269+
function Option(selectValue, viewValue, label, group, disabled) {
254270
this.selectValue = selectValue;
255271
this.viewValue = viewValue;
256272
this.label = label;
257273
this.group = group;
274+
this.disabled = disabled;
258275
}
259276

260277
return {
@@ -275,6 +292,12 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
275292
var label = displayFn(scope, locals);
276293
watchedArray.push(label);
277294
}
295+
296+
// Only need to watch the disableWhenFn if there is a specific disable expression
297+
if (match[4]) {
298+
var disableWhen = disableWhenFn(scope, locals);
299+
watchedArray.push(disableWhen);
300+
}
278301
});
279302
return watchedArray;
280303
}),
@@ -300,7 +323,8 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
300323
var selectValue = getTrackByValue(viewValue, locals);
301324
var label = displayFn(scope, locals);
302325
var group = groupByFn(scope, locals);
303-
var optionItem = new Option(selectValue, viewValue, label, group);
326+
var disabled = disableWhenFn(scope, locals);
327+
var optionItem = new Option(selectValue, viewValue, label, group, disabled);
304328

305329
optionItems.push(optionItem);
306330
selectValueMap[selectValue] = optionItem;
@@ -326,7 +350,7 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
326350
return {
327351
restrict: 'A',
328352
terminal: true,
329-
require: ['select', '?ngModel'],
353+
require: ['select', 'ngModel'],
330354
link: function(scope, selectElement, attr, ctrls) {
331355

332356
// if ngModel is not defined, we don't need to do anything
@@ -377,7 +401,7 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
377401
selectCtrl.writeValue = function writeNgOptionsValue(value) {
378402
var option = options.getOptionFromViewValue(value);
379403

380-
if (option) {
404+
if (option && !option.disabled) {
381405
if (selectElement[0].value !== option.selectValue) {
382406
removeUnknownOption();
383407
removeEmptyOption();
@@ -401,7 +425,7 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
401425

402426
var selectedOption = options.selectValueMap[selectElement.val()];
403427

404-
if (selectedOption) {
428+
if (selectedOption && !selectedOption.disabled) {
405429
removeEmptyOption();
406430
removeUnknownOption();
407431
return selectedOption.viewValue;
@@ -426,18 +450,22 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
426450
if (value) {
427451
value.forEach(function(item) {
428452
var option = options.getOptionFromViewValue(item);
429-
if (option) option.element.selected = true;
453+
if (option && !option.disabled) option.element.selected = true;
430454
});
431455
}
432456
};
433457

434458

435459
selectCtrl.readValue = function readNgOptionsMultiple() {
436-
var selectedValues = selectElement.val() || [];
437-
return selectedValues.map(function(selectedKey) {
438-
var option = options.selectValueMap[selectedKey];
439-
return option.viewValue;
460+
var selectedValues = selectElement.val() || [],
461+
selections = [];
462+
463+
forEach(selectedValues, function(value) {
464+
var option = options.selectValueMap[value];
465+
if (!option.disabled) selections.push(option.viewValue);
440466
});
467+
468+
return selections;
441469
};
442470
}
443471

@@ -470,6 +498,7 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
470498

471499
function updateOptionElement(option, element) {
472500
option.element = element;
501+
element.disabled = option.disabled;
473502
if (option.value !== element.value) element.value = option.selectValue;
474503
if (option.label !== element.label) {
475504
element.label = option.label;

0 commit comments

Comments
 (0)