refactor(render): user render compiler
This commit is contained in:
@ -1,4 +1,5 @@
|
||||
import {AST} from 'angular2/change_detection';
|
||||
import {SetterFn} from 'angular2/src/reflection/types';
|
||||
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import * as protoViewModule from './proto_view';
|
||||
@ -15,6 +16,7 @@ export class ElementBinder {
|
||||
componentId: string;
|
||||
parentIndex:number;
|
||||
distanceToParent:number;
|
||||
propertySetters: Map<string, SetterFn>;
|
||||
|
||||
constructor({
|
||||
textNodeIndices,
|
||||
@ -24,7 +26,8 @@ export class ElementBinder {
|
||||
eventLocals,
|
||||
eventNames,
|
||||
parentIndex,
|
||||
distanceToParent
|
||||
distanceToParent,
|
||||
propertySetters
|
||||
}) {
|
||||
this.textNodeIndices = textNodeIndices;
|
||||
this.contentTagSelector = contentTagSelector;
|
||||
@ -34,6 +37,7 @@ export class ElementBinder {
|
||||
this.eventNames = eventNames;
|
||||
this.parentIndex = parentIndex;
|
||||
this.distanceToParent = distanceToParent;
|
||||
this.propertySetters = propertySetters;
|
||||
}
|
||||
|
||||
mergeChildComponentProtoViews(protoViews:List<protoViewModule.ProtoView>, target:List<protoViewModule.ProtoView>):ElementBinder {
|
||||
@ -45,15 +49,14 @@ export class ElementBinder {
|
||||
}
|
||||
return new ElementBinder({
|
||||
parentIndex: this.parentIndex,
|
||||
// Don't clone as we assume immutability!
|
||||
textNodeIndices: this.textNodeIndices,
|
||||
contentTagSelector: this.contentTagSelector,
|
||||
nestedProtoView: nestedProtoView,
|
||||
componentId: this.componentId,
|
||||
// Don't clone as we assume immutability!
|
||||
eventLocals: this.eventLocals,
|
||||
eventNames: this.eventNames,
|
||||
distanceToParent: this.distanceToParent
|
||||
distanceToParent: this.distanceToParent,
|
||||
propertySetters: this.propertySetters
|
||||
});
|
||||
}
|
||||
}
|
||||
|
125
modules/angular2/src/render/dom/view/property_setter_factory.js
vendored
Normal file
125
modules/angular2/src/render/dom/view/property_setter_factory.js
vendored
Normal file
@ -0,0 +1,125 @@
|
||||
import {StringWrapper, RegExpWrapper, BaseException, isPresent, isBlank, isString, stringify} from 'angular2/src/facade/lang';
|
||||
import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {camelCaseToDashCase, dashCaseToCamelCase} from '../util';
|
||||
import {reflector} from 'angular2/src/reflection/reflection';
|
||||
|
||||
const STYLE_SEPARATOR = '.';
|
||||
var propertySettersCache = StringMapWrapper.create();
|
||||
var innerHTMLSetterCache;
|
||||
|
||||
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);
|
||||
}
|
||||
setterFn = innerHTMLSetterCache;
|
||||
} else {
|
||||
property = resolvePropertyName(property);
|
||||
setterFn = StringMapWrapper.get(propertySettersCache, property);
|
||||
if (isBlank(setterFn)) {
|
||||
var propertySetterFn = reflector.setter(property);
|
||||
setterFn = function(receiver, value) {
|
||||
if (DOM.hasProperty(receiver, property)) {
|
||||
return propertySetterFn(receiver, value);
|
||||
}
|
||||
};
|
||||
StringMapWrapper.set(propertySettersCache, property, setterFn);
|
||||
}
|
||||
}
|
||||
return setterFn;
|
||||
}
|
||||
|
||||
const ATTRIBUTE_PREFIX = 'attr.';
|
||||
var attributeSettersCache = StringMapWrapper.create();
|
||||
|
||||
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) + "'");
|
||||
}
|
||||
DOM.removeAttribute(element, dashCasedAttributeName);
|
||||
}
|
||||
};
|
||||
StringMapWrapper.set(attributeSettersCache, attrName, setterFn);
|
||||
}
|
||||
|
||||
return setterFn;
|
||||
}
|
||||
|
||||
const CLASS_PREFIX = 'class.';
|
||||
var classSettersCache = StringMapWrapper.create();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return setterFn;
|
||||
}
|
||||
|
||||
const STYLE_PREFIX = 'style.';
|
||||
var styleSettersCache = StringMapWrapper.create();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return setterFn;
|
||||
}
|
||||
|
||||
function resolvePropertyName(attrName:string): string {
|
||||
var mappedPropName = StringMapWrapper.get(DOM.attrToPropMap, attrName);
|
||||
return isPresent(mappedPropName) ? mappedPropName : attrName;
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
import {isPresent} from 'angular2/src/facade/lang';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {SetterFn} from 'angular2/src/reflection/types';
|
||||
|
||||
import {List, Map, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
@ -16,20 +15,17 @@ export class ProtoView {
|
||||
isTemplateElement:boolean;
|
||||
isRootView:boolean;
|
||||
rootBindingOffset:int;
|
||||
propertySetters: Map<string, SetterFn>;
|
||||
|
||||
constructor({
|
||||
elementBinders,
|
||||
element,
|
||||
isRootView,
|
||||
propertySetters
|
||||
isRootView
|
||||
}) {
|
||||
this.element = element;
|
||||
this.elementBinders = elementBinders;
|
||||
this.isTemplateElement = DOM.isTemplateElement(this.element);
|
||||
this.isRootView = isRootView;
|
||||
this.rootBindingOffset = (isPresent(this.element) && DOM.hasClass(this.element, NG_BINDING_CLASS)) ? 1 : 0;
|
||||
this.propertySetters = propertySetters;
|
||||
}
|
||||
|
||||
mergeChildComponentProtoViews(protoViews:List<ProtoView>, target:List<ProtoView>):ProtoView {
|
||||
@ -45,9 +41,7 @@ export class ProtoView {
|
||||
var result = new ProtoView({
|
||||
elementBinders: elementBinders,
|
||||
element: this.element,
|
||||
isRootView: this.isRootView,
|
||||
// Don't clone as we assume immutability!
|
||||
propertySetters: this.propertySetters
|
||||
isRootView: this.isRootView
|
||||
});
|
||||
ListWrapper.insert(target, 0, result);
|
||||
return result
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {isPresent, BaseException} from 'angular2/src/facade/lang';
|
||||
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
|
||||
import {ListWrapper, MapWrapper, Set, SetWrapper} from 'angular2/src/facade/collection';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
@ -9,6 +9,7 @@ import {SetterFn} from 'angular2/src/reflection/types';
|
||||
|
||||
import {ProtoView} from './proto_view';
|
||||
import {ElementBinder} from './element_binder';
|
||||
import {setterFactory} from './property_setter_factory';
|
||||
|
||||
import * as api from '../../api';
|
||||
import * as directDomRenderer from '../direct_dom_renderer';
|
||||
@ -20,14 +21,12 @@ export class ProtoViewBuilder {
|
||||
variableBindings: Map<string, string>;
|
||||
elements:List<ElementBinderBuilder>;
|
||||
isRootView:boolean;
|
||||
propertySetters:Set<string>;
|
||||
|
||||
constructor(rootElement) {
|
||||
this.rootElement = rootElement;
|
||||
this.elements = [];
|
||||
this.isRootView = false;
|
||||
this.variableBindings = MapWrapper.create();
|
||||
this.propertySetters = new Set();
|
||||
}
|
||||
|
||||
bindElement(element, description = null):ElementBinderBuilder {
|
||||
@ -55,13 +54,10 @@ export class ProtoViewBuilder {
|
||||
var renderElementBinders = [];
|
||||
|
||||
var apiElementBinders = [];
|
||||
var propertySetters = MapWrapper.create();
|
||||
ListWrapper.forEach(this.elements, (ebb) => {
|
||||
var propertySetters = MapWrapper.create();
|
||||
var eventLocalsAstSplitter = new EventLocalsAstSplitter();
|
||||
var apiDirectiveBinders = ListWrapper.map(ebb.directives, (db) => {
|
||||
MapWrapper.forEach(db.propertySetters, (setter, propertyName) => {
|
||||
MapWrapper.set(propertySetters, propertyName, setter);
|
||||
});
|
||||
return new api.DirectiveBinder({
|
||||
directiveIndex: db.directiveIndex,
|
||||
propertyBindings: db.propertyBindings,
|
||||
@ -74,15 +70,14 @@ export class ProtoViewBuilder {
|
||||
var nestedProtoView =
|
||||
isPresent(ebb.nestedProtoView) ? ebb.nestedProtoView.build() : null;
|
||||
var parentIndex = isPresent(ebb.parent) ? ebb.parent.index : -1;
|
||||
var parentWithDirectivesIndex = isPresent(ebb.parentWithDirectives) ? ebb.parentWithDirectives.index : -1;
|
||||
ListWrapper.push(apiElementBinders, new api.ElementBinder({
|
||||
index: ebb.index, parentIndex:parentIndex, distanceToParent:ebb.distanceToParent,
|
||||
parentWithDirectivesIndex: parentWithDirectivesIndex, distanceToParentWithDirectives: ebb.distanceToParentWithDirectives,
|
||||
directives: apiDirectiveBinders,
|
||||
nestedProtoView: nestedProtoView,
|
||||
propertyBindings: ebb.propertyBindings, variableBindings: ebb.variableBindings,
|
||||
eventBindings: eventLocalsAstSplitter.splitEventAstIntoLocals(ebb.eventBindings),
|
||||
textBindings: ebb.textBindings
|
||||
textBindings: ebb.textBindings,
|
||||
readAttributes: ebb.readAttributes
|
||||
}));
|
||||
ListWrapper.push(renderElementBinders, new ElementBinder({
|
||||
textNodeIndices: ebb.textBindingIndices,
|
||||
@ -92,15 +87,15 @@ export class ProtoViewBuilder {
|
||||
nestedProtoView: isPresent(nestedProtoView) ? nestedProtoView.render.delegate : null,
|
||||
componentId: ebb.componentId,
|
||||
eventLocals: eventLocalsAstSplitter.buildEventLocals(),
|
||||
eventNames: eventLocalsAstSplitter.buildEventNames()
|
||||
eventNames: eventLocalsAstSplitter.buildEventNames(),
|
||||
propertySetters: propertySetters
|
||||
}));
|
||||
});
|
||||
return new api.ProtoView({
|
||||
render: new directDomRenderer.DirectDomProtoViewRef(new ProtoView({
|
||||
element: this.rootElement,
|
||||
elementBinders: renderElementBinders,
|
||||
isRootView: this.isRootView,
|
||||
propertySetters: propertySetters
|
||||
isRootView: this.isRootView
|
||||
})),
|
||||
elementBinders: apiElementBinders,
|
||||
variableBindings: this.variableBindings
|
||||
@ -113,8 +108,6 @@ export class ElementBinderBuilder {
|
||||
index:number;
|
||||
parent:ElementBinderBuilder;
|
||||
distanceToParent:number;
|
||||
parentWithDirectives:ElementBinderBuilder;
|
||||
distanceToParentWithDirectives:number;
|
||||
directives:List<DirectiveBuilder>;
|
||||
nestedProtoView:ProtoViewBuilder;
|
||||
propertyBindings: Map<string, ASTWithSource>;
|
||||
@ -124,6 +117,7 @@ export class ElementBinderBuilder {
|
||||
textBindings: List<ASTWithSource>;
|
||||
contentTagSelector:string;
|
||||
propertySetters: Map<string, SetterFn>;
|
||||
readAttributes: Map<string, string>;
|
||||
componentId: string;
|
||||
|
||||
constructor(index, element, description) {
|
||||
@ -131,8 +125,6 @@ export class ElementBinderBuilder {
|
||||
this.index = index;
|
||||
this.parent = null;
|
||||
this.distanceToParent = 0;
|
||||
this.parentWithDirectives = null;
|
||||
this.distanceToParentWithDirectives = 0;
|
||||
this.directives = [];
|
||||
this.nestedProtoView = null;
|
||||
this.propertyBindings = MapWrapper.create();
|
||||
@ -143,25 +135,23 @@ export class ElementBinderBuilder {
|
||||
this.contentTagSelector = null;
|
||||
this.propertySetters = MapWrapper.create();
|
||||
this.componentId = null;
|
||||
this.readAttributes = MapWrapper.create();
|
||||
}
|
||||
|
||||
setParent(parent:ElementBinderBuilder, distanceToParent):ElementBinderBuilder {
|
||||
this.parent = parent;
|
||||
if (isPresent(parent)) {
|
||||
this.distanceToParent = distanceToParent;
|
||||
if (parent.directives.length > 0) {
|
||||
this.parentWithDirectives = parent;
|
||||
this.distanceToParentWithDirectives = distanceToParent;
|
||||
} else {
|
||||
this.parentWithDirectives = parent.parentWithDirectives;
|
||||
if (isPresent(this.parentWithDirectives)) {
|
||||
this.distanceToParentWithDirectives = distanceToParent + parent.distanceToParentWithDirectives;
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
readAttribute(attrName:string) {
|
||||
if (isBlank(MapWrapper.get(this.readAttributes, attrName))) {
|
||||
MapWrapper.set(this.readAttributes, attrName, DOM.getAttribute(this.element, attrName));
|
||||
}
|
||||
}
|
||||
|
||||
bindDirective(directiveIndex:number):DirectiveBuilder {
|
||||
var directive = new DirectiveBuilder(directiveIndex);
|
||||
ListWrapper.push(this.directives, directive);
|
||||
@ -178,6 +168,11 @@ export class ElementBinderBuilder {
|
||||
|
||||
bindProperty(name, expression) {
|
||||
MapWrapper.set(this.propertyBindings, name, expression);
|
||||
this.bindPropertySetter(name);
|
||||
}
|
||||
|
||||
bindPropertySetter(name) {
|
||||
MapWrapper.set(this.propertySetters, name, setterFactory(name));
|
||||
}
|
||||
|
||||
bindVariable(name, value) {
|
||||
@ -209,10 +204,6 @@ export class ElementBinderBuilder {
|
||||
this.contentTagSelector = value;
|
||||
}
|
||||
|
||||
bindPropertySetter(propertyName, setter) {
|
||||
MapWrapper.set(this.propertySetters, propertyName, setter);
|
||||
}
|
||||
|
||||
setComponentId(componentId:string) {
|
||||
this.componentId = componentId;
|
||||
}
|
||||
@ -222,13 +213,11 @@ export class DirectiveBuilder {
|
||||
directiveIndex:number;
|
||||
propertyBindings: Map<string, ASTWithSource>;
|
||||
eventBindings: Map<string, ASTWithSource>;
|
||||
propertySetters: Map<string, SetterFn>;
|
||||
|
||||
constructor(directiveIndex) {
|
||||
this.directiveIndex = directiveIndex;
|
||||
this.propertyBindings = MapWrapper.create();
|
||||
this.eventBindings = MapWrapper.create();
|
||||
this.propertySetters = MapWrapper.create();
|
||||
}
|
||||
|
||||
bindProperty(name, expression) {
|
||||
@ -238,10 +227,6 @@ export class DirectiveBuilder {
|
||||
bindEvent(name, expression) {
|
||||
MapWrapper.set(this.eventBindings, name, expression);
|
||||
}
|
||||
|
||||
bindPropertySetter(propertyName, setter) {
|
||||
MapWrapper.set(this.propertySetters, propertyName, setter);
|
||||
}
|
||||
}
|
||||
|
||||
export class EventLocalsAstSplitter extends AstTransformer {
|
||||
@ -257,15 +242,19 @@ export class EventLocalsAstSplitter extends AstTransformer {
|
||||
}
|
||||
|
||||
splitEventAstIntoLocals(eventBindings:Map<string, ASTWithSource>):Map<string, ASTWithSource> {
|
||||
if (isPresent(eventBindings)) {
|
||||
var result = MapWrapper.create();
|
||||
MapWrapper.forEach(eventBindings, (astWithSource, eventName) => {
|
||||
MapWrapper.set(result, eventName, astWithSource.ast.visit(this));
|
||||
ListWrapper.push(this.eventNames, eventName);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
// TODO(tbosch): reenable this when we are using
|
||||
// the render views
|
||||
return eventBindings;
|
||||
// if (isPresent(eventBindings)) {
|
||||
// var result = MapWrapper.create();
|
||||
// MapWrapper.forEach(eventBindings, (astWithSource, eventName) => {
|
||||
// var adjustedAst = astWithSource.ast.visit(this);
|
||||
// MapWrapper.set(result, eventName, new ASTWithSource(adjustedAst, astWithSource.source, ''));
|
||||
// ListWrapper.push(this.eventNames, eventName);
|
||||
// });
|
||||
// return result;
|
||||
// }
|
||||
// return null;
|
||||
}
|
||||
|
||||
visitAccessMember(ast:AccessMember) {
|
||||
|
2
modules/angular2/src/render/dom/view/view.js
vendored
2
modules/angular2/src/render/dom/view/view.js
vendored
@ -52,7 +52,7 @@ export class View {
|
||||
}
|
||||
|
||||
setElementProperty(elementIndex:number, propertyName:string, value:any) {
|
||||
var setter = MapWrapper.get(this.proto.propertySetters, propertyName);
|
||||
var setter = MapWrapper.get(this.proto.elementBinders[elementIndex].propertySetters, propertyName);
|
||||
setter(this.boundElements[elementIndex], value);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user