mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-25 02:40:24 +00:00
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
This commit is contained in:
parent
55027132f3
commit
a08cbc02e7
7 changed files with 108 additions and 109 deletions
|
|
@ -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/>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
var interpolateFn = $interpolate(value, true);
|
||||
|
||||
realName = realName || name;
|
||||
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
||||
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/');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.id = 1;
|
||||
});
|
||||
expect(element.attr('src')).toEqual('some/1');
|
||||
|
||||
dealoc(element);
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
describe('ng-href', function() {
|
||||
|
||||
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/');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.id = 1;
|
||||
});
|
||||
expect(element.attr('href')).toEqual('some/1');
|
||||
|
||||
element = $compile('<div ng-src="some"></div>')($rootScope)
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('src')).toEqual('some');
|
||||
dealoc(element);
|
||||
|
||||
element = $compile('<div ng-href="some"></div>')($rootScope)
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('href')).toEqual('some');
|
||||
dealoc(element);
|
||||
}));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
Loading…
Reference in a new issue