parent
ee523efcb4
commit
02aa8e7945
@ -15,23 +15,34 @@ import {dashCaseToCamelCase, camelCaseToDashCase} from './util';
|
|||||||
|
|
||||||
var DOT_REGEXP = RegExpWrapper.create('\\.');
|
var DOT_REGEXP = RegExpWrapper.create('\\.');
|
||||||
|
|
||||||
const ARIA_PREFIX = 'aria';
|
const ATTRIBUTE_PREFIX = 'attr.';
|
||||||
var ariaSettersCache = StringMapWrapper.create();
|
var attributeSettersCache = StringMapWrapper.create();
|
||||||
|
|
||||||
function ariaSetterFactory(attrName:string) {
|
function _isValidAttributeValue(attrName:string, value: any) {
|
||||||
var setterFn = StringMapWrapper.get(ariaSettersCache, attrName);
|
if (attrName == "role") {
|
||||||
var ariaAttrName;
|
return isString(value);
|
||||||
|
} else {
|
||||||
|
return isPresent(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function attributeSetterFactory(attrName:string) {
|
||||||
|
var setterFn = StringMapWrapper.get(attributeSettersCache, attrName);
|
||||||
|
var dashCasedAttributeName;
|
||||||
|
|
||||||
if (isBlank(setterFn)) {
|
if (isBlank(setterFn)) {
|
||||||
ariaAttrName = camelCaseToDashCase(attrName);
|
dashCasedAttributeName = camelCaseToDashCase(attrName);
|
||||||
setterFn = function(element, value) {
|
setterFn = function(element, value) {
|
||||||
if (isPresent(value)) {
|
if (_isValidAttributeValue(dashCasedAttributeName, value)) {
|
||||||
DOM.setAttribute(element, ariaAttrName, stringify(value));
|
DOM.setAttribute(element, dashCasedAttributeName, stringify(value));
|
||||||
} else {
|
} else {
|
||||||
DOM.removeAttribute(element, ariaAttrName);
|
DOM.removeAttribute(element, dashCasedAttributeName);
|
||||||
|
if (isPresent(value)) {
|
||||||
|
throw new BaseException("Invalid " + dashCasedAttributeName + " attribute, only string values are allowed, got '" + stringify(value) + "'");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
StringMapWrapper.set(ariaSettersCache, attrName, setterFn);
|
StringMapWrapper.set(attributeSettersCache, attrName, setterFn);
|
||||||
}
|
}
|
||||||
|
|
||||||
return setterFn;
|
return setterFn;
|
||||||
@ -82,21 +93,9 @@ function styleSetterFactory(styleName:string, stylesuffix:string) {
|
|||||||
return setterFn;
|
return setterFn;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ROLE_ATTR = 'role';
|
|
||||||
function roleSetter(element, value) {
|
|
||||||
if (isString(value)) {
|
|
||||||
DOM.setAttribute(element, ROLE_ATTR, value);
|
|
||||||
} else {
|
|
||||||
DOM.removeAttribute(element, ROLE_ATTR);
|
|
||||||
if (isPresent(value)) {
|
|
||||||
throw new BaseException("Invalid role attribute, only string values are allowed, got '" + stringify(value) + "'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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, ATTRIBUTE_PREFIX)
|
||||||
|| StringWrapper.startsWith(propName, CLASS_PREFIX)
|
|| StringWrapper.startsWith(propName, CLASS_PREFIX)
|
||||||
|| StringWrapper.startsWith(propName, STYLE_PREFIX)
|
|| StringWrapper.startsWith(propName, STYLE_PREFIX)
|
||||||
|| StringMapWrapper.contains(DOM.attrToPropMap, propName);
|
|| StringMapWrapper.contains(DOM.attrToPropMap, propName);
|
||||||
@ -188,10 +187,8 @@ export class ElementBinderBuilder extends CompileStep {
|
|||||||
MapWrapper.forEach(compileElement.propertyBindings, (expression, property) => {
|
MapWrapper.forEach(compileElement.propertyBindings, (expression, property) => {
|
||||||
var setterFn, styleParts, styleSuffix;
|
var setterFn, styleParts, styleSuffix;
|
||||||
|
|
||||||
if (StringWrapper.startsWith(property, ARIA_PREFIX)) {
|
if (StringWrapper.startsWith(property, ATTRIBUTE_PREFIX)) {
|
||||||
setterFn = ariaSetterFactory(property);
|
setterFn = attributeSetterFactory(StringWrapper.substring(property, ATTRIBUTE_PREFIX.length));
|
||||||
} else if (StringWrapper.equals(property, ROLE_ATTR)) {
|
|
||||||
setterFn = roleSetter;
|
|
||||||
} else if (StringWrapper.startsWith(property, CLASS_PREFIX)) {
|
} else if (StringWrapper.startsWith(property, CLASS_PREFIX)) {
|
||||||
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)) {
|
||||||
|
@ -116,7 +116,7 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should consume binding to aria-* attributes', inject([AsyncTestCompleter], (async) => {
|
it('should consume binding to aria-* attributes', inject([AsyncTestCompleter], (async) => {
|
||||||
tplResolver.setTemplate(MyComp, new Template({inline: '<div [aria-label]="ctxProp"></div>'}));
|
tplResolver.setTemplate(MyComp, new Template({inline: '<div [attr.aria-label]="ctxProp"></div>'}));
|
||||||
|
|
||||||
compiler.compile(MyComp).then((pv) => {
|
compiler.compile(MyComp).then((pv) => {
|
||||||
createView(pv);
|
createView(pv);
|
||||||
|
@ -218,7 +218,7 @@ export function main() {
|
|||||||
|
|
||||||
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'
|
'attr.aria-label': 'prop1'
|
||||||
});
|
});
|
||||||
var pipeline = createPipeline({propertyBindings: propertyBindings});
|
var pipeline = createPipeline({propertyBindings: propertyBindings});
|
||||||
var results = pipeline.process(el('<div viewroot prop-binding></div>'));
|
var results = pipeline.process(el('<div viewroot prop-binding></div>'));
|
||||||
@ -243,7 +243,7 @@ export function main() {
|
|||||||
|
|
||||||
it('should bind to aria-* attributes when exp evaluates to booleans', () => {
|
it('should bind to aria-* attributes when exp evaluates to booleans', () => {
|
||||||
var propertyBindings = MapWrapper.createFromStringMap({
|
var propertyBindings = MapWrapper.createFromStringMap({
|
||||||
'aria-busy': 'prop1'
|
'attr.aria-busy': 'prop1'
|
||||||
});
|
});
|
||||||
var pipeline = createPipeline({propertyBindings: propertyBindings});
|
var pipeline = createPipeline({propertyBindings: propertyBindings});
|
||||||
var results = pipeline.process(el('<div viewroot prop-binding></div>'));
|
var results = pipeline.process(el('<div viewroot prop-binding></div>'));
|
||||||
@ -264,7 +264,7 @@ export function main() {
|
|||||||
|
|
||||||
it('should bind to ARIA role attribute', () => {
|
it('should bind to ARIA role attribute', () => {
|
||||||
var propertyBindings = MapWrapper.createFromStringMap({
|
var propertyBindings = MapWrapper.createFromStringMap({
|
||||||
'role': 'prop1'
|
'attr.role': 'prop1'
|
||||||
});
|
});
|
||||||
var pipeline = createPipeline({propertyBindings: propertyBindings});
|
var pipeline = createPipeline({propertyBindings: propertyBindings});
|
||||||
var results = pipeline.process(el('<div viewroot prop-binding></div>'));
|
var results = pipeline.process(el('<div viewroot prop-binding></div>'));
|
||||||
@ -289,7 +289,7 @@ export function main() {
|
|||||||
|
|
||||||
it('should throw for a non-string ARIA role', () => {
|
it('should throw for a non-string ARIA role', () => {
|
||||||
var propertyBindings = MapWrapper.createFromStringMap({
|
var propertyBindings = MapWrapper.createFromStringMap({
|
||||||
'role': 'prop1'
|
'attr.role': 'prop1'
|
||||||
});
|
});
|
||||||
var pipeline = createPipeline({propertyBindings: propertyBindings});
|
var pipeline = createPipeline({propertyBindings: propertyBindings});
|
||||||
var results = pipeline.process(el('<div viewroot prop-binding></div>'));
|
var results = pipeline.process(el('<div viewroot prop-binding></div>'));
|
||||||
@ -303,6 +303,31 @@ export function main() {
|
|||||||
}).toThrowError("Invalid role attribute, only string values are allowed, got '1'");
|
}).toThrowError("Invalid role attribute, only string values are allowed, got '1'");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should bind to any attribute', () => {
|
||||||
|
var propertyBindings = MapWrapper.createFromStringMap({
|
||||||
|
'attr.foo-bar': '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 = 'baz';
|
||||||
|
changeDetector.detectChanges();
|
||||||
|
expect(DOM.getAttribute(view.nodes[0], 'foo-bar')).toEqual('baz');
|
||||||
|
|
||||||
|
evalContext.prop1 = 123;
|
||||||
|
changeDetector.detectChanges();
|
||||||
|
expect(DOM.getAttribute(view.nodes[0], 'foo-bar')).toEqual('123');
|
||||||
|
|
||||||
|
evalContext.prop1 = null;
|
||||||
|
changeDetector.detectChanges();
|
||||||
|
expect(DOM.getAttribute(view.nodes[0], 'foo-bar')).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
it('should bind class with a dot', () => {
|
it('should bind class with a dot', () => {
|
||||||
var propertyBindings = MapWrapper.createFromStringMap({
|
var propertyBindings = MapWrapper.createFromStringMap({
|
||||||
'class.bar': 'prop1',
|
'class.bar': 'prop1',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user