diff --git a/modules/angular2/src/render/dom/compiler/compiler.ts b/modules/angular2/src/render/dom/compiler/compiler.ts index 9f1c224c65..d26fd4a281 100644 --- a/modules/angular2/src/render/dom/compiler/compiler.ts +++ b/modules/angular2/src/render/dom/compiler/compiler.ts @@ -16,20 +16,25 @@ import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader'; import {CompileStepFactory, DefaultStepFactory} from './compile_step_factory'; import {Parser} from 'angular2/change_detection'; import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy'; +import { + PropertySetterFactory +} from '../view/property_setter_factory' -/** - * The compiler loads and translates the html templates of components into - * nested ProtoViews. To decompose its functionality it uses - * the CompilePipeline and the CompileSteps. - */ -export class DomCompiler extends RenderCompiler { + /** + * The compiler loads and translates the html templates of components into + * nested ProtoViews. To decompose its functionality it uses + * the CompilePipeline and the CompileSteps. + */ + export class DomCompiler extends RenderCompiler { _templateLoader: TemplateLoader; _stepFactory: CompileStepFactory; + _propertySetterFactory: PropertySetterFactory; constructor(stepFactory: CompileStepFactory, templateLoader: TemplateLoader) { super(); this._templateLoader = templateLoader; this._stepFactory = stepFactory; + this._propertySetterFactory = new PropertySetterFactory(); } compile(template: ViewDefinition): Promise { @@ -58,7 +63,7 @@ export class DomCompiler extends RenderCompiler { var pipeline = new CompilePipeline(this._stepFactory.createSteps(viewDef, subTaskPromises)); var compileElements = pipeline.process(tplElement, protoViewType, viewDef.componentId); - var protoView = compileElements[0].inheritedProtoView.build(); + var protoView = compileElements[0].inheritedProtoView.build(this._propertySetterFactory); if (subTaskPromises.length > 0) { return PromiseWrapper.all(subTaskPromises).then((_) => protoView); diff --git a/modules/angular2/src/render/dom/view/property_setter_factory.ts b/modules/angular2/src/render/dom/view/property_setter_factory.ts index 9670ce4b3a..be359c3af9 100644 --- a/modules/angular2/src/render/dom/view/property_setter_factory.ts +++ b/modules/angular2/src/render/dom/view/property_setter_factory.ts @@ -13,119 +13,123 @@ import {camelCaseToDashCase, dashCaseToCamelCase} from '../util'; import {reflector} from 'angular2/src/reflection/reflection'; const STYLE_SEPARATOR = '.'; -var propertySettersCache = StringMapWrapper.create(); -var innerHTMLSetterCache; const ATTRIBUTE_PREFIX = 'attr.'; -var attributeSettersCache = StringMapWrapper.create(); const CLASS_PREFIX = 'class.'; -var classSettersCache = StringMapWrapper.create(); const STYLE_PREFIX = 'style.'; -var styleSettersCache = StringMapWrapper.create(); -export function setterFactory(property: string): Function { - var setterFn, styleParts, styleSuffix; - if (StringWrapper.startsWith(property, ATTRIBUTE_PREFIX)) { - setterFn = attributeSetterFactory(StringWrapper.substring(property, ATTRIBUTE_PREFIX.length)); - } else if (StringWrapper.startsWith(property, CLASS_PREFIX)) { - setterFn = classSetterFactory(StringWrapper.substring(property, CLASS_PREFIX.length)); - } else if (StringWrapper.startsWith(property, STYLE_PREFIX)) { - styleParts = property.split(STYLE_SEPARATOR); - styleSuffix = styleParts.length > 2 ? ListWrapper.get(styleParts, 2) : ''; - setterFn = styleSetterFactory(ListWrapper.get(styleParts, 1), styleSuffix); - } else if (StringWrapper.equals(property, 'innerHtml')) { - if (isBlank(innerHTMLSetterCache)) { - innerHTMLSetterCache = (el, value) => DOM.setInnerHTML(el, value); +export class PropertySetterFactory { + private _propertySettersCache: StringMap = StringMapWrapper.create(); + private _innerHTMLSetterCache: Function; + private _attributeSettersCache: StringMap = StringMapWrapper.create(); + private _classSettersCache: StringMap = StringMapWrapper.create(); + private _styleSettersCache: StringMap = StringMapWrapper.create(); + + createSetter(property: string): Function { + var setterFn, styleParts, styleSuffix; + if (StringWrapper.startsWith(property, ATTRIBUTE_PREFIX)) { + setterFn = + this._attributeSetterFactory(StringWrapper.substring(property, ATTRIBUTE_PREFIX.length)); + } else if (StringWrapper.startsWith(property, CLASS_PREFIX)) { + setterFn = this._classSetterFactory(StringWrapper.substring(property, CLASS_PREFIX.length)); + } else if (StringWrapper.startsWith(property, STYLE_PREFIX)) { + styleParts = property.split(STYLE_SEPARATOR); + styleSuffix = styleParts.length > 2 ? ListWrapper.get(styleParts, 2) : ''; + setterFn = this._styleSetterFactory(ListWrapper.get(styleParts, 1), styleSuffix); + } else if (StringWrapper.equals(property, 'innerHtml')) { + if (isBlank(this._innerHTMLSetterCache)) { + this._innerHTMLSetterCache = (el, value) => DOM.setInnerHTML(el, value); + } + setterFn = this._innerHTMLSetterCache; + } else { + property = this._resolvePropertyName(property); + setterFn = StringMapWrapper.get(this._propertySettersCache, property); + if (isBlank(setterFn)) { + var propertySetterFn = reflector.setter(property); + setterFn = (receiver, value) => { + if (DOM.hasProperty(receiver, property)) { + return propertySetterFn(receiver, value); + } + }; + StringMapWrapper.set(this._propertySettersCache, property, setterFn); + } } - setterFn = innerHTMLSetterCache; - } else { - property = resolvePropertyName(property); - setterFn = StringMapWrapper.get(propertySettersCache, property); + return setterFn; + } + + private _isValidAttributeValue(attrName: string, value: any): boolean { + if (attrName == "role") { + return isString(value); + } else { + return isPresent(value); + } + } + + private _attributeSetterFactory(attrName: string): Function { + var setterFn = StringMapWrapper.get(this._attributeSettersCache, attrName); + var dashCasedAttributeName; + if (isBlank(setterFn)) { - var propertySetterFn = reflector.setter(property); - setterFn = function(receiver, value) { - if (DOM.hasProperty(receiver, property)) { - return propertySetterFn(receiver, value); + dashCasedAttributeName = camelCaseToDashCase(attrName); + setterFn = (element, value) => { + if (this._isValidAttributeValue(dashCasedAttributeName, value)) { + DOM.setAttribute(element, dashCasedAttributeName, stringify(value)); + } else { + if (isPresent(value)) { + throw new BaseException("Invalid " + dashCasedAttributeName + + " attribute, only string values are allowed, got '" + + stringify(value) + "'"); + } + DOM.removeAttribute(element, dashCasedAttributeName); } }; - StringMapWrapper.set(propertySettersCache, property, setterFn); + StringMapWrapper.set(this._attributeSettersCache, attrName, setterFn); } + + return setterFn; } - return setterFn; -} -function _isValidAttributeValue(attrName: string, value: any): boolean { - if (attrName == "role") { - return isString(value); - } else { - return isPresent(value); - } -} - -function attributeSetterFactory(attrName: string): Function { - var setterFn = StringMapWrapper.get(attributeSettersCache, attrName); - var dashCasedAttributeName; - - if (isBlank(setterFn)) { - dashCasedAttributeName = camelCaseToDashCase(attrName); - setterFn = function(element, value) { - if (_isValidAttributeValue(dashCasedAttributeName, value)) { - DOM.setAttribute(element, dashCasedAttributeName, stringify(value)); - } else { - if (isPresent(value)) { - throw new BaseException("Invalid " + dashCasedAttributeName + - " attribute, only string values are allowed, got '" + - stringify(value) + "'"); + private _classSetterFactory(className: string): Function { + var setterFn = StringMapWrapper.get(this._classSettersCache, className); + var dashCasedClassName; + if (isBlank(setterFn)) { + dashCasedClassName = camelCaseToDashCase(className); + setterFn = (element, value) => { + if (value) { + DOM.addClass(element, dashCasedClassName); + } else { + DOM.removeClass(element, dashCasedClassName); } - DOM.removeAttribute(element, dashCasedAttributeName); - } - }; - StringMapWrapper.set(attributeSettersCache, attrName, setterFn); + }; + StringMapWrapper.set(this._classSettersCache, className, setterFn); + } + + return setterFn; } - return setterFn; -} + private _styleSetterFactory(styleName: string, styleSuffix: string): Function { + var cacheKey = styleName + styleSuffix; + var setterFn = StringMapWrapper.get(this._styleSettersCache, cacheKey); + var dashCasedStyleName; -function classSetterFactory(className: string): Function { - var setterFn = StringMapWrapper.get(classSettersCache, className); - var dashCasedClassName; - if (isBlank(setterFn)) { - dashCasedClassName = camelCaseToDashCase(className); - setterFn = function(element, value) { - if (value) { - DOM.addClass(element, dashCasedClassName); - } else { - DOM.removeClass(element, dashCasedClassName); - } - }; - StringMapWrapper.set(classSettersCache, className, setterFn); + if (isBlank(setterFn)) { + dashCasedStyleName = camelCaseToDashCase(styleName); + setterFn = (element, value) => { + var valAsStr; + if (isPresent(value)) { + valAsStr = stringify(value); + DOM.setStyle(element, dashCasedStyleName, valAsStr + styleSuffix); + } else { + DOM.removeStyle(element, dashCasedStyleName); + } + }; + StringMapWrapper.set(this._styleSettersCache, cacheKey, setterFn); + } + + return setterFn; } - return setterFn; -} - -function styleSetterFactory(styleName: string, styleSuffix: string): Function { - 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, dashCasedStyleName, valAsStr + styleSuffix); - } else { - DOM.removeStyle(element, dashCasedStyleName); - } - }; - StringMapWrapper.set(styleSettersCache, cacheKey, setterFn); + private _resolvePropertyName(attrName: string): string { + var mappedPropName = StringMapWrapper.get(DOM.attrToPropMap, attrName); + return isPresent(mappedPropName) ? mappedPropName : attrName; } - - return setterFn; -} - -function resolvePropertyName(attrName: string): string { - var mappedPropName = StringMapWrapper.get(DOM.attrToPropMap, attrName); - return isPresent(mappedPropName) ? mappedPropName : attrName; } diff --git a/modules/angular2/src/render/dom/view/proto_view_builder.ts b/modules/angular2/src/render/dom/view/proto_view_builder.ts index 9e0e4e4cdc..81be08538d 100644 --- a/modules/angular2/src/render/dom/view/proto_view_builder.ts +++ b/modules/angular2/src/render/dom/view/proto_view_builder.ts @@ -13,24 +13,17 @@ import { import {DomProtoView, DomProtoViewRef, resolveInternalDomProtoView} from './proto_view'; import {ElementBinder, Event, HostAction} from './element_binder'; -import {setterFactory} from './property_setter_factory'; +import {PropertySetterFactory} from './property_setter_factory'; import * as api from '../../api'; import {NG_BINDING_CLASS, EVENT_TARGET_SEPARATOR} from '../util'; export class ProtoViewBuilder { - rootElement; - variableBindings: Map; - elements: List; - type: number; + variableBindings: Map = MapWrapper.create(); + elements: List = []; - constructor(rootElement, type: number) { - this.rootElement = rootElement; - this.elements = []; - this.variableBindings = MapWrapper.create(); - this.type = type; - } + constructor(public rootElement, public type: number) {} bindElement(element, description = null): ElementBinderBuilder { var builder = new ElementBinderBuilder(this.elements.length, element, description); @@ -50,7 +43,7 @@ export class ProtoViewBuilder { MapWrapper.set(this.variableBindings, value, name); } - build(): api.ProtoViewDto { + build(setterFactory: PropertySetterFactory): api.ProtoViewDto { var renderElementBinders = []; var apiElementBinders = []; @@ -63,7 +56,8 @@ export class ProtoViewBuilder { ebb.eventBuilder.merge(dbb.eventBuilder); MapWrapper.forEach(dbb.hostPropertyBindings, (_, hostPropertyName) => { - MapWrapper.set(propertySetters, hostPropertyName, setterFactory(hostPropertyName)); + MapWrapper.set(propertySetters, hostPropertyName, + setterFactory.createSetter(hostPropertyName)); }); ListWrapper.forEach(dbb.hostActions, (hostAction) => { @@ -79,10 +73,11 @@ export class ProtoViewBuilder { }); MapWrapper.forEach(ebb.propertyBindings, (_, propertyName) => { - MapWrapper.set(propertySetters, propertyName, setterFactory(propertyName)); + MapWrapper.set(propertySetters, propertyName, setterFactory.createSetter(propertyName)); }); - var nestedProtoView = isPresent(ebb.nestedProtoView) ? ebb.nestedProtoView.build() : null; + var nestedProtoView = + isPresent(ebb.nestedProtoView) ? ebb.nestedProtoView.build(setterFactory) : null; var nestedRenderProtoView = isPresent(nestedProtoView) ? resolveInternalDomProtoView(nestedProtoView.render) : null; if (isPresent(nestedRenderProtoView)) { diff --git a/modules/angular2/test/render/dom/view/property_setter_factory_spec.ts b/modules/angular2/test/render/dom/view/property_setter_factory_spec.ts index 852b6d44c0..fa9fb9cc2b 100644 --- a/modules/angular2/test/render/dom/view/property_setter_factory_spec.ts +++ b/modules/angular2/test/render/dom/view/property_setter_factory_spec.ts @@ -9,25 +9,28 @@ import { beforeEach, el } from 'angular2/test_lib'; -import {setterFactory} from 'angular2/src/render/dom/view/property_setter_factory'; +import {PropertySetterFactory} from 'angular2/src/render/dom/view/property_setter_factory'; import {DOM} from 'angular2/src/dom/dom_adapter'; export function main() { - var div; - beforeEach(() => { div = el('
'); }); + var div, setterFactory; + beforeEach(() => { + div = el('
'); + setterFactory = new PropertySetterFactory(); + }); describe('property setter factory', () => { it('should return a setter for a property', () => { - var setterFn = setterFactory('title'); + var setterFn = setterFactory.createSetter('title'); setterFn(div, 'Hello'); expect(div.title).toEqual('Hello'); - var otherSetterFn = setterFactory('title'); + var otherSetterFn = setterFactory.createSetter('title'); expect(setterFn).toBe(otherSetterFn); }); it('should return a setter for an attribute', () => { - var setterFn = setterFactory('attr.role'); + var setterFn = setterFactory.createSetter('attr.role'); setterFn(div, 'button'); expect(DOM.getAttribute(div, 'role')).toEqual('button'); setterFn(div, null); @@ -35,49 +38,49 @@ export function main() { expect(() => { setterFn(div, 4); }) .toThrowError("Invalid role attribute, only string values are allowed, got '4'"); - var otherSetterFn = setterFactory('attr.role'); + var otherSetterFn = setterFactory.createSetter('attr.role'); expect(setterFn).toBe(otherSetterFn); }); it('should return a setter for a class', () => { - var setterFn = setterFactory('class.active'); + var setterFn = setterFactory.createSetter('class.active'); setterFn(div, true); expect(DOM.hasClass(div, 'active')).toEqual(true); setterFn(div, false); expect(DOM.hasClass(div, 'active')).toEqual(false); - var otherSetterFn = setterFactory('class.active'); + var otherSetterFn = setterFactory.createSetter('class.active'); expect(setterFn).toBe(otherSetterFn); }); it('should return a setter for a style', () => { - var setterFn = setterFactory('style.width'); + var setterFn = setterFactory.createSetter('style.width'); setterFn(div, '40px'); expect(DOM.getStyle(div, 'width')).toEqual('40px'); setterFn(div, null); expect(DOM.getStyle(div, 'width')).toEqual(''); - var otherSetterFn = setterFactory('style.width'); + var otherSetterFn = setterFactory.createSetter('style.width'); expect(setterFn).toBe(otherSetterFn); }); it('should return a setter for a style with a unit', () => { - var setterFn = setterFactory('style.height.px'); + var setterFn = setterFactory.createSetter('style.height.px'); setterFn(div, 40); expect(DOM.getStyle(div, 'height')).toEqual('40px'); setterFn(div, null); expect(DOM.getStyle(div, 'height')).toEqual(''); - var otherSetterFn = setterFactory('style.height.px'); + var otherSetterFn = setterFactory.createSetter('style.height.px'); expect(setterFn).toBe(otherSetterFn); }); it('should return a setter for innerHtml', () => { - var setterFn = setterFactory('innerHtml'); + var setterFn = setterFactory.createSetter('innerHtml'); setterFn(div, ''); expect(DOM.getInnerHTML(div)).toEqual(''); - var otherSetterFn = setterFactory('innerHtml'); + var otherSetterFn = setterFactory.createSetter('innerHtml'); expect(setterFn).toBe(otherSetterFn); });