fix(compiler): properly bind to properties that don't have matching attr name
Fixes #619 Closes #783
This commit is contained in:
parent
e490861ba2
commit
7e6f536cf5
@ -1,5 +1,5 @@
|
|||||||
import {int, isPresent, isBlank, Type, BaseException, StringWrapper, RegExpWrapper, isString, stringify} from 'angular2/src/facade/lang';
|
import {int, isPresent, isBlank, Type, BaseException, StringWrapper, RegExpWrapper, isString, stringify} from 'angular2/src/facade/lang';
|
||||||
import {Element, DOM} from 'angular2/src/facade/dom';
|
import {Element, DOM, attrToPropMap} from 'angular2/src/facade/dom';
|
||||||
import {ListWrapper, List, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
import {ListWrapper, List, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||||
|
|
||||||
import {reflector} from 'angular2/src/reflection/reflection';
|
import {reflector} from 'angular2/src/reflection/reflection';
|
||||||
@ -91,11 +91,19 @@ function roleSetter(element:Element, value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// special mapping for cases where attribute name doesn't match property name
|
||||||
|
var attrToProp = StringMapWrapper.merge({
|
||||||
|
"inner-html": "innerHTML",
|
||||||
|
"readonly": "readOnly",
|
||||||
|
"tabindex": "tabIndex",
|
||||||
|
}, attrToPropMap);
|
||||||
|
|
||||||
// tells if an attribute is handled by the ElementBinderBuilder step
|
// tells if an attribute is handled by the ElementBinderBuilder step
|
||||||
export function isSpecialProperty(propName:string) {
|
export function isSpecialProperty(propName:string) {
|
||||||
return StringWrapper.startsWith(propName, ARIA_PREFIX)
|
return StringWrapper.startsWith(propName, ARIA_PREFIX)
|
||||||
|| StringWrapper.startsWith(propName, CLASS_PREFIX)
|
|| StringWrapper.startsWith(propName, CLASS_PREFIX)
|
||||||
|| StringWrapper.startsWith(propName, STYLE_PREFIX);
|
|| StringWrapper.startsWith(propName, STYLE_PREFIX)
|
||||||
|
|| StringMapWrapper.contains(attrToProp, propName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -176,10 +184,14 @@ export class ElementBinderBuilder extends CompileStep {
|
|||||||
setterFn = classSetterFactory(StringWrapper.substring(property, CLASS_PREFIX.length));
|
setterFn = classSetterFactory(StringWrapper.substring(property, CLASS_PREFIX.length));
|
||||||
} else if (StringWrapper.startsWith(property, STYLE_PREFIX)) {
|
} else if (StringWrapper.startsWith(property, STYLE_PREFIX)) {
|
||||||
styleParts = StringWrapper.split(property, DOT_REGEXP);
|
styleParts = StringWrapper.split(property, DOT_REGEXP);
|
||||||
styleSuffix = styleParts.length > 2 ? ListWrapper.get(styleParts, 2) : '';
|
styleSuffix = styleParts.length > 2 ? ListWrapper.get(styleParts, 2) : '';
|
||||||
setterFn = styleSetterFactory(ListWrapper.get(styleParts, 1), styleSuffix);
|
setterFn = styleSetterFactory(ListWrapper.get(styleParts, 1), styleSuffix);
|
||||||
} else if (DOM.hasProperty(compileElement.element, property)) {
|
} else {
|
||||||
setterFn = reflector.setter(property);
|
property = this._resolvePropertyName(property);
|
||||||
|
//TODO(pk): special casing innerHtml, see: https://github.com/angular/angular/issues/789
|
||||||
|
if (DOM.hasProperty(compileElement.element, property) || StringWrapper.equals(property, 'innerHtml')) {
|
||||||
|
setterFn = reflector.setter(property);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPresent(setterFn)) {
|
if (isPresent(setterFn)) {
|
||||||
@ -236,4 +248,9 @@ export class ElementBinderBuilder extends CompileStep {
|
|||||||
var parts = StringWrapper.split(bindConfig, RegExpWrapper.create("\\|"));
|
var parts = StringWrapper.split(bindConfig, RegExpWrapper.create("\\|"));
|
||||||
return ListWrapper.map(parts, (s) => s.trim());
|
return ListWrapper.map(parts, (s) => s.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_resolvePropertyName(attrName:string) {
|
||||||
|
var mappedPropName = StringMapWrapper.get(attrToProp, attrName);
|
||||||
|
return isPresent(mappedPropName) ? mappedPropName : attrName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,8 @@ export 'dart:html' show
|
|||||||
InputElement,
|
InputElement,
|
||||||
AnchorElement,
|
AnchorElement,
|
||||||
Text,
|
Text,
|
||||||
window;
|
window,
|
||||||
|
attrToPropMap;
|
||||||
|
|
||||||
// TODO(tbosch): Is there a builtin one? Why is Dart
|
// TODO(tbosch): Is there a builtin one? Why is Dart
|
||||||
// removing unknown elements by default?
|
// removing unknown elements by default?
|
||||||
@ -36,6 +37,11 @@ void gc() {
|
|||||||
|
|
||||||
final identitySanitizer = new IdentitySanitizer();
|
final identitySanitizer = new IdentitySanitizer();
|
||||||
|
|
||||||
|
// override JS logic of attribute to property mapping
|
||||||
|
var attrToPropMap = {
|
||||||
|
"inner-html": "innerHtml"
|
||||||
|
};
|
||||||
|
|
||||||
class DOM {
|
class DOM {
|
||||||
static query(String selector) => document.querySelector(selector);
|
static query(String selector) => document.querySelector(selector);
|
||||||
|
|
||||||
|
@ -16,6 +16,8 @@ export var gc = window.gc ? () => window.gc() : () => null;
|
|||||||
export var CssRule = window.CSSRule;
|
export var CssRule = window.CSSRule;
|
||||||
export var CssKeyframesRule = window.CSSKeyframesRule;
|
export var CssKeyframesRule = window.CSSKeyframesRule;
|
||||||
|
|
||||||
|
export var attrToPropMap = {};
|
||||||
|
|
||||||
export class DOM {
|
export class DOM {
|
||||||
static query(selector) {
|
static query(selector) {
|
||||||
return document.querySelector(selector);
|
return document.querySelector(selector);
|
||||||
|
@ -102,6 +102,41 @@ export function main() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should consume binding to propery names where attr name and property name do not match', (done) => {
|
||||||
|
tplResolver.setTemplate(MyComp, new Template({inline: '<div [tabindex]="ctxNumProp"></div>'}));
|
||||||
|
|
||||||
|
compiler.compile(MyComp).then((pv) => {
|
||||||
|
createView(pv);
|
||||||
|
|
||||||
|
cd.detectChanges();
|
||||||
|
expect(view.nodes[0].tabIndex).toEqual(0);
|
||||||
|
|
||||||
|
ctx.ctxNumProp = 5;
|
||||||
|
cd.detectChanges();
|
||||||
|
expect(view.nodes[0].tabIndex).toEqual(5);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should consume binding to inner-html', (done) => {
|
||||||
|
tplResolver.setTemplate(MyComp, new Template({inline: '<div inner-html="{{ctxProp}}"></div>'}));
|
||||||
|
|
||||||
|
compiler.compile(MyComp).then((pv) => {
|
||||||
|
createView(pv);
|
||||||
|
|
||||||
|
ctx.ctxProp = 'Some <span>HTML</span>';
|
||||||
|
cd.detectChanges();
|
||||||
|
expect(DOM.getInnerHTML(view.nodes[0])).toEqual('Some <span>HTML</span>');
|
||||||
|
|
||||||
|
ctx.ctxProp = 'Some other <div>HTML</div>';
|
||||||
|
cd.detectChanges();
|
||||||
|
expect(DOM.getInnerHTML(view.nodes[0])).toEqual('Some other <div>HTML</div>');
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should consume directive watch expression change.', (done) => {
|
it('should consume directive watch expression change.', (done) => {
|
||||||
var tpl =
|
var tpl =
|
||||||
'<div>' +
|
'<div>' +
|
||||||
@ -490,8 +525,10 @@ class PushBasedComp {
|
|||||||
@Component()
|
@Component()
|
||||||
class MyComp {
|
class MyComp {
|
||||||
ctxProp:string;
|
ctxProp:string;
|
||||||
|
ctxNumProp;
|
||||||
constructor() {
|
constructor() {
|
||||||
this.ctxProp = 'initial value';
|
this.ctxProp = 'initial value';
|
||||||
|
this.ctxNumProp = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,6 +196,27 @@ export function main() {
|
|||||||
expect(view.nodes[0].hidden).toEqual(false);
|
expect(view.nodes[0].hidden).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should bind element properties where attr name and prop name do not match', () => {
|
||||||
|
var propertyBindings = MapWrapper.createFromStringMap({
|
||||||
|
'tabindex': 'prop1'
|
||||||
|
});
|
||||||
|
var pipeline = createPipeline({propertyBindings: propertyBindings});
|
||||||
|
var results = pipeline.process(el('<div viewroot prop-binding></div>'));
|
||||||
|
var pv = results[0].inheritedProtoView;
|
||||||
|
|
||||||
|
expect(pv.elementBinders[0].hasElementPropertyBindings).toBe(true);
|
||||||
|
|
||||||
|
instantiateView(pv);
|
||||||
|
|
||||||
|
evalContext.prop1 = 1;
|
||||||
|
changeDetector.detectChanges();
|
||||||
|
expect(view.nodes[0].tabIndex).toEqual(1);
|
||||||
|
|
||||||
|
evalContext.prop1 = 0;
|
||||||
|
changeDetector.detectChanges();
|
||||||
|
expect(view.nodes[0].tabIndex).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
it('should bind to aria-* attributes when exp evaluates to strings', () => {
|
it('should bind to aria-* attributes when exp evaluates to strings', () => {
|
||||||
var propertyBindings = MapWrapper.createFromStringMap({
|
var propertyBindings = MapWrapper.createFromStringMap({
|
||||||
'aria-label': 'prop1'
|
'aria-label': 'prop1'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user