refactor(render): don’t use a global cache for property setters
Related to #2359
This commit is contained in:
parent
46eeee6b5e
commit
87b3b718e3
|
@ -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<ProtoViewDto> {
|
||||
|
@ -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);
|
||||
|
|
|
@ -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<string, Function> = StringMapWrapper.create();
|
||||
private _innerHTMLSetterCache: Function;
|
||||
private _attributeSettersCache: StringMap<string, Function> = StringMapWrapper.create();
|
||||
private _classSettersCache: StringMap<string, Function> = StringMapWrapper.create();
|
||||
private _styleSettersCache: StringMap<string, Function> = 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;
|
||||
}
|
||||
|
|
|
@ -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<string, string>;
|
||||
elements: List<ElementBinderBuilder>;
|
||||
type: number;
|
||||
variableBindings: Map<string, string> = MapWrapper.create();
|
||||
elements: List<ElementBinderBuilder> = [];
|
||||
|
||||
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)) {
|
||||
|
|
|
@ -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('<div></div>'); });
|
||||
var div, setterFactory;
|
||||
beforeEach(() => {
|
||||
div = el('<div></div>');
|
||||
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, '<span></span>');
|
||||
expect(DOM.getInnerHTML(div)).toEqual('<span></span>');
|
||||
|
||||
var otherSetterFn = setterFactory('innerHtml');
|
||||
var otherSetterFn = setterFactory.createSetter('innerHtml');
|
||||
expect(setterFn).toBe(otherSetterFn);
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue