feat(ngOptions): add support for disabling an option #11017

Closed
wants to merge 1 commit into
from

Projects

None yet

5 participants

@sjbarker
Contributor
sjbarker commented Feb 9, 2015

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

@googlebot googlebot added the cla: yes label Feb 9, 2015
@sjbarker
Contributor
sjbarker commented Feb 9, 2015

Explanation

The changes in this pull request reflect a long-time reoccurring and widely popular request for the ability to disable options dynamically in ngOptions. This solution accomplishes this feature by adding the disable when syntax to the ng-options directive. All current functionality is retained in select controls.

The solution for model changes reflecting disabled options is to keep the current functionality of "unknown" or "empty" options. Tests have been written to reflect this feature. Consider the following:

scope.selected = '';
scope.options = [ 'a', 'b', 'c', 'd'];
scope.disableOption = function(option) {
  return option === 'b';
}
<select ng-model="selected"
             ng-options="o disable when disableOption(o) for o in options"></select>

-OR-

scope.selected = '';
scope.options = [ 
  {
    name: 'a',
    disabled: false
  },
  {
    name: 'b',
    disabled: false
  },
  {
    name: 'c',
    disabled: false
  },
  {
    name: 'd',
    disabled: false
  }
];
<select ng-model="selected"
             ng-options="o.name disable when o.disabled for o in options"></select>

In either case, if selected becomes a disabled option, then select will render the empty or unknown option. This concurrent with any unknown or invalid option.

Current Issues

Issue #638 has been open for over three years now and is currently on backlog.

Current PR's

There is one current PR (#9854) by @cades that has been rendered obsolete by @petebacondarwin's select.js refactor (408f89d).

@sjbarker
Contributor
sjbarker commented Feb 9, 2015

@petebacondarwin - Since you recently did the select directive refactor, will you take a quick look at this and pass it around? We have been sitting on this for a really long time and this would provide the functionality without breaking anything; and with room to grow in the future. Thanks!

@cades
cades commented Feb 10, 2015

Great job, thanks to your effort and time!
Hope to merge this PR as soon as possible.

@sjbarker
Contributor

Thanks @cades. You were there first until they refactored the select directive. And thank you for your time and effort as well.

@sjbarker
Contributor

@pkozlowski-opensource - I figured I would tag you in this since you were attending to the #9854 PR. When you have a moment, will you take a look as well? Thanks.

@petebacondarwin
Member

OK, I like this idea, although I think that the syntax sound better with disable when rather than disable by.

Also I am concerned about add to the getWatchables array, which is already a bit of a pain point.

I realised that we can actually optimize this by only including things that are being specified. So for instance, if no disable when clause was provided we would not need to watch this expression. The same for the displayFn which computes the label.

@petebacondarwin petebacondarwin added this to the 1.4.0-beta.6 / 1.3.15 milestone Feb 12, 2015
@petebacondarwin petebacondarwin self-assigned this Feb 12, 2015
@sjbarker
Contributor

No problem, @petebacondarwin. Let me get those changes in real quick. I will open an issue for optimizing the getWatchables array and cover it in this commit as well.

@sjbarker sjbarker commented on the diff Feb 13, 2015
src/ng/directive/ngOptions.js
@@ -186,16 +200,17 @@ var ngOptionsMinErr = minErr('ngOptions');
*/
// jshint maxlen: false
- //000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888
-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]+?))?$/;
+// //00001111111111000000000002222222222000000000000000000000333333333300000000000000000000000004444444444400000000000005555555555555550000000006666666666666660000000777777777777777000000000000000888888888800000000000000000009999999999
+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]+?))?$/;
@sjbarker
sjbarker Feb 13, 2015 Contributor

@petebacondarwin Here is the syntax change.

@sjbarker sjbarker and 1 other commented on an outdated diff Feb 13, 2015
src/ng/directive/ngOptions.js
watchedArray.push(selectValue);
- watchedArray.push(label);
+ // for optimization, do not watch label or disableWhen if they weren't defined
+ if (match[2]) watchedArray.push(label);
+ if (match[4]) watchedArray.push(disableWhen);
@sjbarker
sjbarker Feb 13, 2015 Contributor

@petebacondarwin and here is the check for expression definition prior to adding watchers

@petebacondarwin
petebacondarwin Feb 13, 2015 Member

We can do more here. We don't need to even compute the label or displayWhen values if they are not specified. See https://github.com/angular/angular.js/pull/11052/files

@sjbarker
sjbarker Feb 13, 2015 Contributor

Agreed. Let me push that.
On Fri, Feb 13, 2015 at 6:17 AM Pete Bacon Darwin notifications@github.com
wrote:

In src/ng/directive/ngOptions.js
#11017 (comment):

       watchedArray.push(selectValue);
  •      watchedArray.push(label);
    
  •      // for optimization, do not watch label or disableWhen if they weren't defined
    
  •      if (match[2]) watchedArray.push(label);
    
  •      if (match[4]) watchedArray.push(disableWhen);
    

We can do more here. We don't need to even compute the label or
displayWhen values if they are not specified. See
https://github.com/angular/angular.js/pull/11052/files


Reply to this email directly or view it on GitHub
https://github.com/angular/angular.js/pull/11017/files#r24657856.

@sjbarker sjbarker commented on the diff Feb 13, 2015
src/ng/directive/ngOptions.js
@@ -275,6 +292,12 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
var label = displayFn(scope, locals);
watchedArray.push(label);
}
+
+ // Only need to watch the disableWhenFn if there is a specific disable expression
+ if (match[4]) {
+ var disableWhen = disableWhenFn(scope, locals);
+ watchedArray.push(disableWhen);
+ }
@sjbarker
sjbarker Feb 13, 2015 Contributor

@petebacondarwin - I rebased your perf and mimicked it with the disableWhenFn

@petebacondarwin petebacondarwin commented on an outdated diff Feb 18, 2015
src/ng/directive/ngOptions.js
@@ -326,7 +350,7 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
return {
restrict: 'A',
terminal: true,
- require: ['select', '?ngModel'],
+ require: ['select', 'ngModel'],
@petebacondarwin
petebacondarwin Feb 18, 2015 Member

ngModel is still optional here.

@sjbarker sjbarker 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
518bf01
@sjbarker
Contributor

@petebacondarwin I returned the ngModel requirement to optional.

@petebacondarwin petebacondarwin added a commit that closed this pull request Feb 18, 2015
@sjbarker @petebacondarwin sjbarker + petebacondarwin 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
da9eac8
@petebacondarwin
Member

I added a few extra tests and merged into master - thanks @sjbarker and @cades !

@sjbarker
Contributor

Thanks @petebacondarwin! I didn't see that ngModel optional requirement make it in though. Is that ok?

@petebacondarwin
Member

Oops

On 18 February 2015 at 13:21, Stephen Barker notifications@github.com
wrote:

Thanks @petebacondarwin https://github.com/petebacondarwin! I didn't
see that ngModel optional requirement make it in though. Is that ok?


Reply to this email directly or view it on GitHub
#11017 (comment).

@sjbarker sjbarker added a commit to sjbarker/angular.js that referenced this pull request Feb 18, 2015
@sjbarker sjbarker 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
f3ae91d
@sjbarker
Contributor

@petebacondarwin - f3ae91d here has the commit you just merged with the ngModel require change.

@petebacondarwin
Member

Thanks - I fixed that here: ef894c8

@sjbarker
Contributor

Ok perfect

@IngoVals

If the disabling requirements are dynamic and something that was selected becomes disable the value is set to null, could we intercept this event and change it to something else?

@sjbarker
Contributor

@IngoVals when you say change it to something else, what do you mean?

@netman92 netman92 added a commit to netman92/angular.js that referenced this pull request Aug 8, 2015
@sjbarker @netman92 sjbarker + netman92 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
8bc638f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment