From cb91f7315f6b44e7af272ba2f6926041437592f0 Mon Sep 17 00:00:00 2001 From: chrisrhoden Date: Wed, 2 Oct 2013 19:14:29 -0400 Subject: [PATCH] feature(ngEnabled): define ngEnabled directive as negation of ngDisabled Provide a negated form of the `ngDisabled` directive as `ngEnabled` in the same vein as the `ngHide`/`ngShow` pair. Allow for slightly clearer templates by avoiding double negatives, as in: Closes #1252 --- src/ng/directive/booleanAttrs.js | 67 ++++++++++++++++++++++----- test/ng/directive/booleanAttrsSpec.js | 10 ++++ test/ngTouch/directive/ngClickSpec.js | 36 ++++++++++++++ 3 files changed, 102 insertions(+), 11 deletions(-) diff --git a/src/ng/directive/booleanAttrs.js b/src/ng/directive/booleanAttrs.js index b290d52c0eac..c364604bbcdb 100644 --- a/src/ng/directive/booleanAttrs.js +++ b/src/ng/directive/booleanAttrs.js @@ -172,6 +172,44 @@ * then special attribute "disabled" will be set on the element */ + /** + * @ngdoc directive + * @name ng.directive:ngEnabled + * @restrict A + * + * @description + * + * The following markup will make the button enabled on Chrome/Firefox but not on IE8 and older IEs: + *
+ * 
+ * + *
+ *
+ * + * The HTML specs do not require browsers to preserve the values of special attributes + * such as disabled. (The presence of them means true and absence means false) + * This prevents the Angular compiler from correctly retrieving the binding expression. + * To solve this problem, we introduce the `ngDisabled` directive and its corollary `ngEnabled`. + * + * @example + + + Click me to toggle:
+ +
+ + it('should toggle button', function() { + expect(element('.doc-example-live :button').prop('disabled')).toBeTruthy(); + input('checked').check(); + expect(element('.doc-example-live :button').prop('disabled')).toBeFalsy(); + }); + +
+ * + * @element INPUT + * @param {expression} ngEnabled If the {@link guide/expression expression} is falsy, + * then special attribute "disabled" will be set on the element + */ /** * @ngdoc directive @@ -199,7 +237,7 @@ * * @element INPUT - * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, + * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, * then special attribute "checked" will be set on the element */ @@ -230,7 +268,7 @@ * * @element INPUT - * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, + * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, * then special attribute "readonly" will be set on the element */ @@ -303,27 +341,34 @@ var ngAttributeAliasDirectives = {}; +function ngAttributeAliasDirective(attrName, directiveName, inverted) { + inverted = !!inverted; -// boolean attrs are evaluated -forEach(BOOLEAN_ATTR, function(propName, attrName) { - // binding to multiple is not supported - if (propName == "multiple") return; - - var normalized = directiveNormalize('ng-' + attrName); - ngAttributeAliasDirectives[normalized] = function() { + return function() { return { priority: 100, compile: function() { return function(scope, element, attr) { - scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { - attr.$set(attrName, !!value); + scope.$watch(attr[directiveName], function ngBooleanAttrWatchAction(value) { + attr.$set(attrName, !!(inverted ^ !!value)); }); }; } }; }; +} + + +// boolean attrs are evaluated +forEach(BOOLEAN_ATTR, function(propName, attrName) { + // binding to multiple is not supported + if (propName == "multiple") return; + + var normalized = directiveNormalize('ng-' + attrName); + ngAttributeAliasDirectives[normalized] = ngAttributeAliasDirective(attrName, normalized); }); +ngAttributeAliasDirectives['ngEnabled'] = ngAttributeAliasDirective('disabled', 'ngEnabled', true); // ng-src, ng-srcset, ng-href are interpolated forEach(['src', 'srcset', 'href'], function(attrName) { diff --git a/test/ng/directive/booleanAttrsSpec.js b/test/ng/directive/booleanAttrsSpec.js index 83bc75e55188..d657c345e5d3 100644 --- a/test/ng/directive/booleanAttrsSpec.js +++ b/test/ng/directive/booleanAttrsSpec.js @@ -30,6 +30,16 @@ describe('boolean attr directives', function() { expect(element.attr('disabled')).toBeTruthy(); })); + it('should bind disabled to the negation of ng-enabled', inject(function($rootScope, $compile) { + element = $compile('')($rootScope); + $rootScope.isEnabled = false; + $rootScope.$digest(); + expect(element.attr('disabled')).toBeTruthy(); + $rootScope.isEnabled = true; + $rootScope.$digest(); + expect(element.attr('disabled')).toBeFalsy(); + })); + it('should bind checked', inject(function($rootScope, $compile) { element = $compile('')($rootScope) diff --git a/test/ngTouch/directive/ngClickSpec.js b/test/ngTouch/directive/ngClickSpec.js index 0eccc8f3d04b..7f2a5e857da9 100644 --- a/test/ngTouch/directive/ngClickSpec.js +++ b/test/ngTouch/directive/ngClickSpec.js @@ -466,6 +466,42 @@ describe('ngClick (touch)', function() { expect($rootScope.event).toBeDefined(); })); + it('should trigger click if ngEnabled is true', inject(function($rootScope, $compile) { + element = $compile('
')($rootScope); + $rootScope.enabled = true; + $rootScope.$digest(); + + browserTrigger(element, 'touchstart',{ + keys: [], + x: 10, + y: 10 + }); + browserTrigger(element, 'touchend',{ + keys: [], + x: 10, + y: 10 + }); + + expect($rootScope.event).toBeDefined(); + })); + it('should not trigger click if ngEnabled is false', inject(function($rootScope, $compile) { + element = $compile('
')($rootScope); + $rootScope.enabled = false; + $rootScope.$digest(); + + browserTrigger(element, 'touchstart',{ + keys: [], + x: 10, + y: 10 + }); + browserTrigger(element, 'touchend',{ + keys: [], + x: 10, + y: 10 + }); + + expect($rootScope.event).toBeUndefined(); + })); it('should not trigger click if regular disabled is true', inject(function($rootScope, $compile) { element = $compile('
')($rootScope);