diff options
| author | Vojta Jina | 2012-03-23 15:53:04 -0700 |
|---|---|---|
| committer | Vojta Jina | 2012-03-26 21:14:09 -0700 |
| commit | a08cbc02e78e789a66e9af771c410e8ad1646e25 (patch) | |
| tree | bc7081b11d6d1ed4cd5bde3a9e059e5c964e75a8 | |
| parent | 55027132f3d57e5dcf94683e6e6bd7b0aae0087d (diff) | |
| download | angular.js-a08cbc02e78e789a66e9af771c410e8ad1646e25.tar.bz2 | |
feat($compile): do not interpolate boolean attributes, rather evaluate them
So that we can have non string values, e.g. ng-value="true" for radio inputs
Breaks boolean attrs are evaluated rather than interpolated
To migrate your code, change: <input ng-disabled="{{someBooleanVariable}}">
to: <input ng-disabled="someBooleanVariabla">
Affected directives:
* ng-multiple
* ng-selected
* ng-checked
* ng-disabled
* ng-readonly
* ng-required
| -rw-r--r-- | docs/content/cookbook/advancedform.ngdoc | 4 | ||||
| -rw-r--r-- | src/directive/booleanAttrDirs.js | 73 | ||||
| -rw-r--r-- | src/service/compiler.js | 66 | ||||
| -rw-r--r-- | test/directive/booleanAttrDirSpecs.js | 46 | ||||
| -rw-r--r-- | test/directive/inputSpec.js | 4 | ||||
| -rw-r--r-- | test/directive/selectSpec.js | 2 | ||||
| -rw-r--r-- | test/service/compilerSpec.js | 16 |
7 files changed, 105 insertions, 106 deletions
diff --git a/docs/content/cookbook/advancedform.ngdoc b/docs/content/cookbook/advancedform.ngdoc index 37c5da0d..1501f955 100644 --- a/docs/content/cookbook/advancedform.ngdoc +++ b/docs/content/cookbook/advancedform.ngdoc @@ -85,8 +85,8 @@ detection, and preventing invalid form submission. <input type="text" ng-model="contact.value" required/> [ <a href="" ng-click="removeContact(contact)">X</a> ] </div> - <button ng-click="cancel()" ng-disabled="{{isCancelDisabled()}}">Cancel</button> - <button ng-click="save()" ng-disabled="{{isSaveDisabled()}}">Save</button> + <button ng-click="cancel()" ng-disabled="isCancelDisabled()">Cancel</button> + <button ng-click="save()" ng-disabled="isSaveDisabled()">Save</button> </form> <hr/> diff --git a/src/directive/booleanAttrDirs.js b/src/directive/booleanAttrDirs.js index 10c6eee8..7da52db0 100644 --- a/src/directive/booleanAttrDirs.js +++ b/src/directive/booleanAttrDirs.js @@ -130,7 +130,7 @@ <doc:example> <doc:source> Click me to toggle: <input type="checkbox" ng-model="checked"><br/> - <button ng-model="button" ng-disabled="{{checked}}">Button</button> + <button ng-model="button" ng-disabled="checked">Button</button> </doc:source> <doc:scenario> it('should toggle button', function() { @@ -142,7 +142,7 @@ </doc:example> * * @element INPUT - * @param {template} ng-disabled any string which can contain '{{}}' markup. + * @param {string} expression Angular expression that will be evaluated. */ @@ -160,7 +160,7 @@ <doc:example> <doc:source> Check me to check both: <input type="checkbox" ng-model="master"><br/> - <input id="checkSlave" type="checkbox" ng-checked="{{master}}"> + <input id="checkSlave" type="checkbox" ng-checked="master"> </doc:source> <doc:scenario> it('should check both checkBoxes', function() { @@ -172,7 +172,7 @@ </doc:example> * * @element INPUT - * @param {template} ng-checked any string which can contain '{{}}' markup. + * @param {string} expression Angular expression that will be evaluated. */ @@ -191,7 +191,7 @@ <doc:example> <doc:source> Check me check multiple: <input type="checkbox" ng-model="checked"><br/> - <select id="select" ng-multiple="{{checked}}"> + <select id="select" ng-multiple="checked"> <option>Misko</option> <option>Igor</option> <option>Vojta</option> @@ -208,7 +208,7 @@ </doc:example> * * @element SELECT - * @param {template} ng-multiple any string which can contain '{{}}' markup. + * @param {string} expression Angular expression that will be evaluated. */ @@ -226,7 +226,7 @@ <doc:example> <doc:source> Check me to make text readonly: <input type="checkbox" ng-model="checked"><br/> - <input type="text" ng-readonly="{{checked}}" value="I'm Angular"/> + <input type="text" ng-readonly="checked" value="I'm Angular"/> </doc:source> <doc:scenario> it('should toggle readonly attr', function() { @@ -238,7 +238,7 @@ </doc:example> * * @element INPUT - * @param {template} ng-readonly any string which can contain '{{}}' markup. + * @param {string} expression Angular expression that will be evaluated. */ @@ -255,35 +255,60 @@ * @example <doc:example> <doc:source> - Check me to select: <input type="checkbox" ng-model="checked"><br/> + Check me to select: <input type="checkbox" ng-model="selected"><br/> <select> <option>Hello!</option> - <option id="greet" ng-selected="{{checked}}">Greetings!</option> + <option id="greet" ng-selected="selected">Greetings!</option> </select> </doc:source> <doc:scenario> it('should select Greetings!', function() { expect(element('.doc-example-live #greet').prop('selected')).toBeFalsy(); - input('checked').check(); + input('selected').check(); expect(element('.doc-example-live #greet').prop('selected')).toBeTruthy(); }); </doc:scenario> </doc:example> + * * @element OPTION - * @param {template} ng-selected any string which can contain '{{}}' markup. + * @param {string} expression Angular expression that will be evaluated. */ - -function ngAttributeAliasDirective(propName, attrName) { - ngAttributeAliasDirectives[directiveNormalize('ng-' + attrName)] = valueFn( - function(scope, element, attr) { - attr.$observe(directiveNormalize('ng-' + attrName), function(value) { - attr.$set(attrName, value); - }); - } - ); -} var ngAttributeAliasDirectives = {}; -forEach(BOOLEAN_ATTR, ngAttributeAliasDirective); -ngAttributeAliasDirective(null, 'src'); + + +// boolean attrs are evaluated +forEach(BOOLEAN_ATTR, function(propName, attrName) { + var normalized = directiveNormalize('ng-' + attrName); + ngAttributeAliasDirectives[normalized] = function() { + return { + compile: function(tpl, attr) { + attr.$observers[attrName] = []; + return function(scope, element, attr) { + scope.$watch(attr[normalized], function(value) { + attr.$set(attrName, value); + }); + }; + } + }; + }; +}); + + +// ng-src, ng-href are interpolated +forEach(['src', 'href'], function(attrName) { + var normalized = directiveNormalize('ng-' + attrName); + ngAttributeAliasDirectives[normalized] = function() { + return { + compile: function(tpl, attr) { + attr.$observers[attrName] = []; + return function(scope, element, attr) { + attr.$observe(normalized, function(value) { + attr.$set(attrName, value); + }); + }; + } + }; + }; +}); diff --git a/src/service/compiler.js b/src/service/compiler.js index 8ddf77ae..a22c5d66 100644 --- a/src/service/compiler.js +++ b/src/service/compiler.js @@ -128,13 +128,7 @@ function $CompileProvider($provide) { COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/, CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/, CONTENT_REGEXP = /\<\<content\>\>/i, - HAS_ROOT_ELEMENT = /^\<[\s\S]*\>$/, - SIDE_EFFECT_ATTRS = {}; - - forEach('src,href,multiple,selected,checked,disabled,readonly,required'.split(','), function(name) { - SIDE_EFFECT_ATTRS[name] = name; - SIDE_EFFECT_ATTRS[directiveNormalize('ng_' + name)] = name; - }); + HAS_ROOT_ELEMENT = /^\<[\s\S]*\>$/; this.directive = function registerDirective(name, directiveFactory) { @@ -861,44 +855,29 @@ function $CompileProvider($provide) { function addAttrInterpolateDirective(node, directives, value, name) { - var interpolateFn = $interpolate(value, true), - realName = SIDE_EFFECT_ATTRS[name], - specialAttrDir = (realName && (realName !== name)); - - realName = realName || name; + var interpolateFn = $interpolate(value, true); - if (specialAttrDir && isBooleanAttr(node, name)) { - value = true; - } - // no interpolation found and we are not a side-effect attr -> ignore - if (!interpolateFn && !specialAttrDir) { - return; - } + // no interpolation found -> ignore + if (!interpolateFn) return; directives.push({ priority: 100, - compile: function(element, attr) { - if (interpolateFn) { - return function(scope, element, attr) { - if (name === 'class') { - // we need to interpolate classes again, in the case the element was replaced - // and therefore the two class attrs got merged - we want to interpolate the result - interpolateFn = $interpolate(attr[name], true); - } - - // we define observers array only for interpolated attrs - // and ignore observers for non interpolated attrs to save some memory - attr.$observers[realName] = []; - attr[realName] = undefined; - scope.$watch(interpolateFn, function(value) { - attr.$set(realName, value); - }); - }; - } else { - attr.$set(realName, value); + compile: valueFn(function(scope, element, attr) { + if (name === 'class') { + // we need to interpolate classes again, in the case the element was replaced + // and therefore the two class attrs got merged - we want to interpolate the result + interpolateFn = $interpolate(attr[name], true); } - } + + // we define observers array only for interpolated attrs + // and ignore observers for non interpolated attrs to save some memory + attr.$observers[name] = []; + attr[name] = undefined; + scope.$watch(interpolateFn, function(value) { + attr.$set(name, value); + }); + }) }); } @@ -945,15 +924,12 @@ function $CompileProvider($provide) { var booleanKey = isBooleanAttr(this.$element[0], key.toLowerCase()); if (booleanKey) { - value = toBoolean(value); this.$element.prop(key, value); - this[key] = value; - attrName = key = booleanKey; - value = value ? booleanKey : undefined; - } else { - this[key] = value; + attrName = booleanKey; } + this[key] = value; + // translate normalized key to actual key if (attrName) { this.$attr[key] = attrName; diff --git a/test/directive/booleanAttrDirSpecs.js b/test/directive/booleanAttrDirSpecs.js index 8d71c2d8..7a4244a8 100644 --- a/test/directive/booleanAttrDirSpecs.js +++ b/test/directive/booleanAttrDirSpecs.js @@ -17,7 +17,7 @@ describe('boolean attr directives', function() { it('should bind disabled', inject(function($rootScope, $compile) { - element = $compile('<button ng-disabled="{{isDisabled}}">Button</button>')($rootScope) + element = $compile('<button ng-disabled="isDisabled">Button</button>')($rootScope) $rootScope.isDisabled = false; $rootScope.$digest(); expect(element.attr('disabled')).toBeFalsy(); @@ -28,7 +28,7 @@ describe('boolean attr directives', function() { it('should bind checked', inject(function($rootScope, $compile) { - element = $compile('<input type="checkbox" ng-checked="{{isChecked}}" />')($rootScope) + element = $compile('<input type="checkbox" ng-checked="isChecked" />')($rootScope) $rootScope.isChecked = false; $rootScope.$digest(); expect(element.attr('checked')).toBeFalsy(); @@ -39,7 +39,7 @@ describe('boolean attr directives', function() { it('should bind selected', inject(function($rootScope, $compile) { - element = $compile('<select><option value=""></option><option ng-selected="{{isSelected}}">Greetings!</option></select>')($rootScope) + element = $compile('<select><option value=""></option><option ng-selected="isSelected">Greetings!</option></select>')($rootScope) jqLite(document.body).append(element) $rootScope.isSelected=false; $rootScope.$digest(); @@ -51,7 +51,7 @@ describe('boolean attr directives', function() { it('should bind readonly', inject(function($rootScope, $compile) { - element = $compile('<input type="text" ng-readonly="{{isReadonly}}" />')($rootScope) + element = $compile('<input type="text" ng-readonly="isReadonly" />')($rootScope) $rootScope.isReadonly=false; $rootScope.$digest(); expect(element.attr('readOnly')).toBeFalsy(); @@ -62,7 +62,7 @@ describe('boolean attr directives', function() { it('should bind multiple', inject(function($rootScope, $compile) { - element = $compile('<select ng-multiple="{{isMultiple}}"></select>')($rootScope) + element = $compile('<select ng-multiple="isMultiple"></select>')($rootScope) $rootScope.isMultiple=false; $rootScope.$digest(); expect(element.attr('multiple')).toBeFalsy(); @@ -88,24 +88,38 @@ describe('boolean attr directives', function() { expect(element.attr('href')).toEqual('http://server'); expect(element.attr('rel')).toEqual('REL'); })); +}); - it('should bind Text with no Bindings', inject(function($compile, $rootScope) { - forEach(['checked', 'disabled', 'multiple', 'readonly', 'selected'], function(name) { - element = $compile('<div ng-' + name + '="some"></div>')($rootScope) - $rootScope.$digest(); - expect(element.attr(name)).toBe(name); - dealoc(element); - }); +describe('ng-src', function() { - element = $compile('<div ng-src="some"></div>')($rootScope) + it('should interpolate the expression and bind to src', inject(function($compile, $rootScope) { + var element = $compile('<div ng-src="some/{{id}}"></div>')($rootScope) $rootScope.$digest(); - expect(element.attr('src')).toEqual('some'); + expect(element.attr('src')).toEqual('some/'); + + $rootScope.$apply(function() { + $rootScope.id = 1; + }); + expect(element.attr('src')).toEqual('some/1'); + dealoc(element); + })); +}); + + +describe('ng-href', function() { - element = $compile('<div ng-href="some"></div>')($rootScope) + it('should interpolate the expression and bind to href', inject(function($compile, $rootScope) { + var element = $compile('<div ng-href="some/{{id}}"></div>')($rootScope) $rootScope.$digest(); - expect(element.attr('href')).toEqual('some'); + expect(element.attr('href')).toEqual('some/'); + + $rootScope.$apply(function() { + $rootScope.id = 1; + }); + expect(element.attr('href')).toEqual('some/1'); + dealoc(element); })); }); diff --git a/test/directive/inputSpec.js b/test/directive/inputSpec.js index 8d0e44b3..e5f083b3 100644 --- a/test/directive/inputSpec.js +++ b/test/directive/inputSpec.js @@ -941,8 +941,8 @@ describe('input', function() { describe('required', function() { - it('should allow bindings on required', function() { - compileInput('<input type="text" ng-model="value" required="{{required}}" />'); + it('should allow bindings on ng-required', function() { + compileInput('<input type="text" ng-model="value" ng-required="required" />'); scope.$apply(function() { scope.required = false; diff --git a/test/directive/selectSpec.js b/test/directive/selectSpec.js index 3f8fb56e..2e3cfaaf 100644 --- a/test/directive/selectSpec.js +++ b/test/directive/selectSpec.js @@ -780,7 +780,7 @@ describe('select', function() { createSelect({ 'ng-model': 'value', 'ng-options': 'item.name for item in values', - 'ng-required': '{{required}}' + 'ng-required': 'required' }, true); diff --git a/test/service/compilerSpec.js b/test/service/compilerSpec.js index 698fc23e..dc2e20cf 100644 --- a/test/service/compilerSpec.js +++ b/test/service/compilerSpec.js @@ -1411,22 +1411,6 @@ describe('$compile', function() { }); - it('should set boolean attributes', function() { - attr.$set('disabled', 'true'); - attr.$set('readOnly', 'true'); - expect(element.attr('disabled')).toEqual('disabled'); - expect(element.attr('readonly')).toEqual('readonly'); - - attr.$set('disabled', 'false'); - expect(element.attr('disabled')).toEqual(undefined); - - attr.$set('disabled', false); - attr.$set('readOnly', false); - expect(element.attr('disabled')).toEqual(undefined); - expect(element.attr('readonly')).toEqual(undefined); - }); - - it('should remove attribute', function() { attr.$set('ngMyAttr', 'value'); expect(element.attr('ng-my-attr')).toEqual('value'); |
