diff --git a/modules/angular2/src/core/compiler/pipeline/compile_element.js b/modules/angular2/src/core/compiler/pipeline/compile_element.js index 4f7ab4744d..0b07ba0b9d 100644 --- a/modules/angular2/src/core/compiler/pipeline/compile_element.js +++ b/modules/angular2/src/core/compiler/pipeline/compile_element.js @@ -6,6 +6,7 @@ import {Decorator, Component, Viewport} from '../../annotations/annotations'; import {ElementBinder} from '../element_binder'; import {ProtoElementInjector} from '../element_injector'; import {ProtoView} from '../view'; +import {dashCaseToCamelCase} from './util'; import {AST} from 'angular2/change_detection'; @@ -114,7 +115,7 @@ export class CompileElement { if (isBlank(this.propertyBindings)) { this.propertyBindings = MapWrapper.create(); } - MapWrapper.set(this.propertyBindings, property, expression); + MapWrapper.set(this.propertyBindings, dashCaseToCamelCase(property), expression); } addVariableBinding(variableName:string, variableValue:string) { diff --git a/modules/angular2/src/core/compiler/pipeline/directive_parser.js b/modules/angular2/src/core/compiler/pipeline/directive_parser.js index 6117eae2e8..3b324b35e7 100644 --- a/modules/angular2/src/core/compiler/pipeline/directive_parser.js +++ b/modules/angular2/src/core/compiler/pipeline/directive_parser.js @@ -10,7 +10,8 @@ import {CompileStep} from './compile_step'; import {CompileElement} from './compile_element'; import {CompileControl} from './compile_control'; -import {isSpecialProperty} from './element_binder_builder';; +import {isSpecialProperty} from './element_binder_builder'; +import {dashCaseToCamelCase, camelCaseToDashCase} from './util'; var PROPERTY_BINDING_REGEXP = RegExpWrapper.create('^ *([^\\s\\|]+)'); @@ -84,7 +85,7 @@ function updateMatchedProperties(matchedProperties, selector, directive) { if (isPresent(attrs)) { for (var idx = 0; idx { if (!DOM.hasProperty(current.element, prop) && !isSpecialProperty(prop)) { if (!isPresent(matchedProperties) || !isPresent(StringMapWrapper.get(matchedProperties, prop))) { - throw new BaseException(`Missing directive to handle '${prop}' in ${current.elementDescription}`); + throw new BaseException(`Missing directive to handle '${camelCaseToDashCase(prop)}' in ${current.elementDescription}`); } } }); diff --git a/modules/angular2/src/core/compiler/pipeline/element_binder_builder.js b/modules/angular2/src/core/compiler/pipeline/element_binder_builder.js index ec1e8ff939..2df75ec375 100644 --- a/modules/angular2/src/core/compiler/pipeline/element_binder_builder.js +++ b/modules/angular2/src/core/compiler/pipeline/element_binder_builder.js @@ -11,21 +11,24 @@ import {DirectiveMetadata} from '../directive_metadata'; import {CompileStep} from './compile_step'; import {CompileElement} from './compile_element'; import {CompileControl} from './compile_control'; +import {dashCaseToCamelCase, camelCaseToDashCase} from './util'; var DOT_REGEXP = RegExpWrapper.create('\\.'); -const ARIA_PREFIX = 'aria-'; +const ARIA_PREFIX = 'aria'; var ariaSettersCache = StringMapWrapper.create(); function ariaSetterFactory(attrName:string) { var setterFn = StringMapWrapper.get(ariaSettersCache, attrName); + var ariaAttrName; if (isBlank(setterFn)) { + ariaAttrName = camelCaseToDashCase(attrName); setterFn = function(element, value) { if (isPresent(value)) { - DOM.setAttribute(element, attrName, stringify(value)); + DOM.setAttribute(element, ariaAttrName, stringify(value)); } else { - DOM.removeAttribute(element, attrName); + DOM.removeAttribute(element, ariaAttrName); } }; StringMapWrapper.set(ariaSettersCache, attrName, setterFn); @@ -34,7 +37,6 @@ function ariaSetterFactory(attrName:string) { return setterFn; } -const CLASS_ATTR = 'class'; const CLASS_PREFIX = 'class.'; var classSettersCache = StringMapWrapper.create(); @@ -55,22 +57,23 @@ function classSetterFactory(className:string) { return setterFn; } -const STYLE_ATTR = 'style'; const STYLE_PREFIX = 'style.'; var styleSettersCache = StringMapWrapper.create(); function styleSetterFactory(styleName:string, stylesuffix:string) { var cacheKey = styleName + stylesuffix; var setterFn = StringMapWrapper.get(styleSettersCache, cacheKey); + var dashCasedStyleName; if (isBlank(setterFn)) { + dashCasedStyleName = camelCaseToDashCase(styleName); setterFn = function(element, value) { var valAsStr; if (isPresent(value)) { valAsStr = stringify(value); - DOM.setStyle(element, styleName, valAsStr + stylesuffix); + DOM.setStyle(element, dashCasedStyleName, valAsStr + stylesuffix); } else { - DOM.removeStyle(element, styleName); + DOM.removeStyle(element, dashCasedStyleName); } }; StringMapWrapper.set(classSettersCache, cacheKey, setterFn); @@ -229,7 +232,7 @@ export class ElementBinderBuilder extends CompileStep { var elProp = ListWrapper.removeAt(pipes, 0); var bindingAst = isPresent(compileElement.propertyBindings) ? - MapWrapper.get(compileElement.propertyBindings, elProp) : + MapWrapper.get(compileElement.propertyBindings, dashCaseToCamelCase(elProp)) : null; if (isBlank(bindingAst)) { @@ -246,7 +249,7 @@ export class ElementBinderBuilder extends CompileStep { directiveIndex, fullExpAstWithBindPipes, dirProp, - reflector.setter(dirProp) + reflector.setter(dashCaseToCamelCase(dirProp)) ); } }); diff --git a/modules/angular2/src/core/compiler/pipeline/util.js b/modules/angular2/src/core/compiler/pipeline/util.js new file mode 100644 index 0000000000..7ef4f4c50b --- /dev/null +++ b/modules/angular2/src/core/compiler/pipeline/util.js @@ -0,0 +1,16 @@ +import {StringWrapper, RegExpWrapper} from 'angular2/src/facade/lang'; + +var DASH_CASE_REGEXP = RegExpWrapper.create('-([a-z])'); +var CAMEL_CASE_REGEXP = RegExpWrapper.create('([A-Z])'); + +export function dashCaseToCamelCase(input:string) { + return StringWrapper.replaceAllMapped(input, DASH_CASE_REGEXP, (m) => { + return m[1].toUpperCase(); + }); +} + +export function camelCaseToDashCase(input:string) { + return StringWrapper.replaceAllMapped(input, CAMEL_CASE_REGEXP, (m) => { + return '-' + m[1].toLowerCase(); + }); +} diff --git a/modules/angular2/src/dom/browser_adapter.dart b/modules/angular2/src/dom/browser_adapter.dart index 37bf545962..6ae6d58765 100644 --- a/modules/angular2/src/dom/browser_adapter.dart +++ b/modules/angular2/src/dom/browser_adapter.dart @@ -21,7 +21,7 @@ class BrowserDomAdapter extends GenericBrowserDomAdapter { @override final attrToPropMap = const { - 'inner-html': 'innerHtml', + 'innerHtml': 'innerHtml', 'readonly': 'readOnly', 'tabindex': 'tabIndex', }; diff --git a/modules/angular2/src/dom/browser_adapter.es6 b/modules/angular2/src/dom/browser_adapter.es6 index 29bc14b145..27edca7481 100644 --- a/modules/angular2/src/dom/browser_adapter.es6 +++ b/modules/angular2/src/dom/browser_adapter.es6 @@ -4,9 +4,9 @@ import {setRootDomAdapter} from './dom_adapter'; import {GenericBrowserDomAdapter} from './generic_browser_adapter'; var _attrToPropMap = { - 'inner-html': 'innerHTML', + 'innerHtml': 'innerHTML', 'readonly': 'readOnly', - 'tabindex': 'tabIndex', + 'tabindex': 'tabIndex' }; export class BrowserDomAdapter extends GenericBrowserDomAdapter { diff --git a/modules/angular2/src/dom/parse5_adapter.cjs b/modules/angular2/src/dom/parse5_adapter.cjs index 6b3b0c6b02..382ef484a2 100644 --- a/modules/angular2/src/dom/parse5_adapter.cjs +++ b/modules/angular2/src/dom/parse5_adapter.cjs @@ -13,7 +13,7 @@ import {BaseException, isPresent, isBlank} from 'angular2/src/facade/lang'; import {SelectorMatcher, CssSelector} from 'angular2/src/core/compiler/selector'; var _attrToPropMap = { - 'inner-html': 'innerHTML', + 'innerHtml': 'innerHTML', 'readonly': 'readOnly', 'tabindex': 'tabIndex', }; @@ -206,7 +206,7 @@ export class Parse5DomAdapter extends DomAdapter { } setText(el, value:string) { if (this.isTextNode(el)) { - el.data = value; + el.data = value; } else { this.clearNodes(el); treeAdapter.insertText(el, value); @@ -315,7 +315,7 @@ export class Parse5DomAdapter extends DomAdapter { for (var key in styleMap) { var newValue = styleMap[key]; if (newValue && newValue.length > 0) { - styleAttrValue += key + ":" + styleMap[key] + ";"; + styleAttrValue += key + ":" + styleMap[key] + ";"; } } element.attribs["style"] = styleAttrValue; @@ -427,7 +427,7 @@ export class Parse5DomAdapter extends DomAdapter { var declaration = parsedRule.declarations[j]; rule.style[declaration.property] = declaration.value; rule.style.cssText += declaration.property + ": " + declaration.value + ";"; - } + } } else if (parsedRule.type == "media") { rule.type = 4; rule.media = {mediaText: parsedRule.media}; diff --git a/modules/angular2/test/core/compiler/integration_spec.js b/modules/angular2/test/core/compiler/integration_spec.js index 34331fa9b9..9b3cb76f6a 100644 --- a/modules/angular2/test/core/compiler/integration_spec.js +++ b/modules/angular2/test/core/compiler/integration_spec.js @@ -133,6 +133,23 @@ export function main() { }); })); + it('should consume binding to camel-cased properties using dash-cased syntax in templates', inject([AsyncTestCompleter], (async) => { + tplResolver.setTemplate(MyComp, new Template({inline: ''})); + + compiler.compile(MyComp).then((pv) => { + createView(pv); + + cd.detectChanges(); + expect(view.nodes[0].readOnly).toBeFalsy(); + + ctx.ctxBoolProp = true; + cd.detectChanges(); + expect(view.nodes[0].readOnly).toBeTruthy(); + + async.done(); + }); + })); + it('should consume binding to inner-html', inject([AsyncTestCompleter], (async) => { tplResolver.setTemplate(MyComp, new Template({inline: '
'})); @@ -592,9 +609,11 @@ class PushBasedComp { class MyComp { ctxProp:string; ctxNumProp; + ctxBoolProp; constructor() { this.ctxProp = 'initial value'; this.ctxNumProp = 0; + this.ctxBoolProp = false; } }