feat(render): don’t use the reflector for setting properties
BREAKING CHANGES: - host actions don't take an expression as value any more but only a method name, and assumes to get an array via the EventEmitter with the method arguments. - Renderer.setElementProperty does not take `style.`/... prefixes any more. Use the new methods `Renderer.setElementAttribute`, ... instead Part of #2476 Closes #2637
This commit is contained in:
@ -17,7 +17,6 @@ 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
|
||||
@ -25,8 +24,6 @@ import {PropertySetterFactory} from '../view/property_setter_factory';
|
||||
* the CompilePipeline and the CompileSteps.
|
||||
*/
|
||||
export class DomCompiler extends RenderCompiler {
|
||||
_propertySetterFactory: PropertySetterFactory = new PropertySetterFactory();
|
||||
|
||||
constructor(public _stepFactory: CompileStepFactory, public _templateLoader: TemplateLoader) {
|
||||
super();
|
||||
}
|
||||
@ -58,7 +55,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(this._propertySetterFactory);
|
||||
var protoView = compileElements[0].inheritedProtoView.build();
|
||||
|
||||
if (subTaskPromises.length > 0) {
|
||||
return PromiseWrapper.all(subTaskPromises).then((_) => protoView);
|
||||
|
@ -91,11 +91,6 @@ export class DirectiveParser implements CompileStep {
|
||||
this._bindDirectiveEvent(eventName, action, current, directiveBinderBuilder);
|
||||
});
|
||||
}
|
||||
if (isPresent(dirMetadata.hostActions)) {
|
||||
MapWrapper.forEach(dirMetadata.hostActions, (action, actionName) => {
|
||||
this._bindHostAction(actionName, action, current, directiveBinderBuilder);
|
||||
});
|
||||
}
|
||||
if (isPresent(dirMetadata.hostProperties)) {
|
||||
MapWrapper.forEach(dirMetadata.hostProperties, (expression, hostPropertyName) => {
|
||||
this._bindHostProperty(hostPropertyName, expression, current, directiveBinderBuilder);
|
||||
@ -134,9 +129,8 @@ export class DirectiveParser implements CompileStep {
|
||||
elProp = bindConfig;
|
||||
pipes = [];
|
||||
}
|
||||
|
||||
var bindingAst = compileElement.bindElement().propertyBindings.get(dashCaseToCamelCase(elProp));
|
||||
|
||||
elProp = dashCaseToCamelCase(elProp);
|
||||
var bindingAst = compileElement.bindElement().propertyBindings.get(elProp);
|
||||
if (isBlank(bindingAst)) {
|
||||
var attributeValue = compileElement.attrs().get(camelCaseToDashCase(elProp));
|
||||
if (isPresent(attributeValue)) {
|
||||
@ -147,9 +141,8 @@ export class DirectiveParser implements CompileStep {
|
||||
|
||||
// Bindings are optional, so this binding only needs to be set up if an expression is given.
|
||||
if (isPresent(bindingAst)) {
|
||||
directiveBinderBuilder.bindProperty(dirProperty, bindingAst);
|
||||
directiveBinderBuilder.bindProperty(dirProperty, bindingAst, elProp);
|
||||
}
|
||||
compileElement.bindElement().bindPropertyToDirective(dashCaseToCamelCase(elProp));
|
||||
}
|
||||
|
||||
_bindDirectiveEvent(eventName, action, compileElement, directiveBinderBuilder) {
|
||||
@ -162,11 +155,6 @@ export class DirectiveParser implements CompileStep {
|
||||
}
|
||||
}
|
||||
|
||||
_bindHostAction(actionName, actionExpression, compileElement, directiveBinderBuilder) {
|
||||
var ast = this._parser.parseAction(actionExpression, compileElement.elementDescription);
|
||||
directiveBinderBuilder.bindHostAction(actionName, actionExpression, ast);
|
||||
}
|
||||
|
||||
_bindHostProperty(hostPropertyName, expression, compileElement, directiveBinderBuilder) {
|
||||
var ast = this._parser.parseSimpleBinding(
|
||||
expression, `hostProperties of ${compileElement.elementDescription}`);
|
||||
|
@ -187,10 +187,28 @@ export class DomRenderer extends Renderer {
|
||||
view.setElementProperty(elementIndex, propertyName, propertyValue);
|
||||
}
|
||||
|
||||
callAction(viewRef: RenderViewRef, elementIndex: number, actionExpression: string,
|
||||
actionArgs: any): void {
|
||||
setElementAttribute(viewRef: RenderViewRef, elementIndex: number, attributeName: string,
|
||||
attributeValue: string): void {
|
||||
var view = resolveInternalDomView(viewRef);
|
||||
view.callAction(elementIndex, actionExpression, actionArgs);
|
||||
view.setElementAttribute(elementIndex, attributeName, attributeValue);
|
||||
}
|
||||
|
||||
setElementClass(viewRef: RenderViewRef, elementIndex: number, className: string,
|
||||
isAdd: boolean): void {
|
||||
var view = resolveInternalDomView(viewRef);
|
||||
view.setElementClass(elementIndex, className, isAdd);
|
||||
}
|
||||
|
||||
setElementStyle(viewRef: RenderViewRef, elementIndex: number, styleName: string,
|
||||
styleValue: string): void {
|
||||
var view = resolveInternalDomView(viewRef);
|
||||
view.setElementStyle(elementIndex, styleName, styleValue);
|
||||
}
|
||||
|
||||
invokeElementMethod(viewRef: RenderViewRef, elementIndex: number, methodName: string,
|
||||
args: List<any>): void {
|
||||
var view = resolveInternalDomView(viewRef);
|
||||
view.invokeElementMethod(elementIndex, methodName, args);
|
||||
}
|
||||
|
||||
setText(viewRef: RenderViewRef, textNodeIndex: number, text: string): void {
|
||||
|
@ -1,5 +1,4 @@
|
||||
import {AST} from 'angular2/change_detection';
|
||||
import {SetterFn} from 'angular2/src/reflection/types';
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import * as protoViewModule from './proto_view';
|
||||
|
||||
@ -13,13 +12,10 @@ export class ElementBinder {
|
||||
componentId: string;
|
||||
parentIndex: number;
|
||||
distanceToParent: number;
|
||||
propertySetters: Map<string, SetterFn>;
|
||||
hostActions: Map<string, AST>;
|
||||
elementIsEmpty: boolean;
|
||||
|
||||
constructor({textNodeIndices, contentTagSelector, nestedProtoView, componentId, eventLocals,
|
||||
localEvents, globalEvents, hostActions, parentIndex, distanceToParent,
|
||||
propertySetters, elementIsEmpty}: {
|
||||
localEvents, globalEvents, parentIndex, distanceToParent, elementIsEmpty}: {
|
||||
contentTagSelector?: string,
|
||||
textNodeIndices?: List<number>,
|
||||
nestedProtoView?: protoViewModule.DomProtoView,
|
||||
@ -29,8 +25,6 @@ export class ElementBinder {
|
||||
componentId?: string,
|
||||
parentIndex?: number,
|
||||
distanceToParent?: number,
|
||||
propertySetters?: Map<string, SetterFn>,
|
||||
hostActions?: Map<string, AST>,
|
||||
elementIsEmpty?: boolean
|
||||
} = {}) {
|
||||
this.textNodeIndices = textNodeIndices;
|
||||
@ -40,10 +34,8 @@ export class ElementBinder {
|
||||
this.eventLocals = eventLocals;
|
||||
this.localEvents = localEvents;
|
||||
this.globalEvents = globalEvents;
|
||||
this.hostActions = hostActions;
|
||||
this.parentIndex = parentIndex;
|
||||
this.distanceToParent = distanceToParent;
|
||||
this.propertySetters = propertySetters;
|
||||
this.elementIsEmpty = elementIsEmpty;
|
||||
}
|
||||
}
|
||||
|
@ -1,156 +0,0 @@
|
||||
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 = '.';
|
||||
const ATTRIBUTE_PREFIX = 'attr.';
|
||||
const CLASS_PREFIX = 'class.';
|
||||
const STYLE_PREFIX = 'style.';
|
||||
|
||||
export class PropertySetterFactory {
|
||||
static noopSetter(el, value) {}
|
||||
|
||||
private _lazyPropertySettersCache: StringMap<string, Function> = StringMapWrapper.create();
|
||||
private _eagerPropertySettersCache: StringMap<string, Function> = StringMapWrapper.create();
|
||||
private _innerHTMLSetterCache: Function = (el, value) => DOM.setInnerHTML(el, value);
|
||||
private _attributeSettersCache: StringMap<string, Function> = StringMapWrapper.create();
|
||||
private _classSettersCache: StringMap<string, Function> = StringMapWrapper.create();
|
||||
private _styleSettersCache: StringMap<string, Function> = StringMapWrapper.create();
|
||||
|
||||
createSetter(protoElement: /*element*/ any, isNgComponent: boolean, 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')) {
|
||||
setterFn = this._innerHTMLSetterCache;
|
||||
} else {
|
||||
property = this._resolvePropertyName(property);
|
||||
setterFn = this._propertySetterFactory(protoElement, isNgComponent, property);
|
||||
}
|
||||
return setterFn;
|
||||
}
|
||||
|
||||
private _propertySetterFactory(protoElement, isNgComponent: boolean, property: string): Function {
|
||||
var setterFn;
|
||||
var tagName = DOM.tagName(protoElement);
|
||||
var possibleCustomElement = tagName.indexOf('-') !== -1;
|
||||
if (possibleCustomElement && !isNgComponent) {
|
||||
// need to use late check to be able to set properties on custom elements
|
||||
setterFn = StringMapWrapper.get(this._lazyPropertySettersCache, property);
|
||||
if (isBlank(setterFn)) {
|
||||
var propertySetterFn = reflector.setter(property);
|
||||
setterFn = (receiver, value) => {
|
||||
if (DOM.hasProperty(receiver, property)) {
|
||||
return propertySetterFn(receiver, value);
|
||||
}
|
||||
};
|
||||
StringMapWrapper.set(this._lazyPropertySettersCache, property, setterFn);
|
||||
}
|
||||
} else {
|
||||
setterFn = StringMapWrapper.get(this._eagerPropertySettersCache, property);
|
||||
if (isBlank(setterFn)) {
|
||||
if (DOM.hasProperty(protoElement, property)) {
|
||||
setterFn = reflector.setter(property);
|
||||
} else {
|
||||
setterFn = PropertySetterFactory.noopSetter;
|
||||
}
|
||||
StringMapWrapper.set(this._eagerPropertySettersCache, property, setterFn);
|
||||
}
|
||||
}
|
||||
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)) {
|
||||
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(this._attributeSettersCache, attrName, setterFn);
|
||||
}
|
||||
|
||||
return setterFn;
|
||||
}
|
||||
|
||||
private _classSetterFactory(className: string): Function {
|
||||
var setterFn = StringMapWrapper.get(this._classSettersCache, className);
|
||||
var dashCasedClassName;
|
||||
if (isBlank(setterFn)) {
|
||||
dashCasedClassName = camelCaseToDashCase(className);
|
||||
setterFn = (element, isAdd) => {
|
||||
if (isAdd) {
|
||||
DOM.addClass(element, dashCasedClassName);
|
||||
} else {
|
||||
DOM.removeClass(element, dashCasedClassName);
|
||||
}
|
||||
};
|
||||
StringMapWrapper.set(this._classSettersCache, className, setterFn);
|
||||
}
|
||||
|
||||
return setterFn;
|
||||
}
|
||||
|
||||
private _styleSetterFactory(styleName: string, styleSuffix: string): Function {
|
||||
var cacheKey = styleName + styleSuffix;
|
||||
var setterFn = StringMapWrapper.get(this._styleSettersCache, cacheKey);
|
||||
var dashCasedStyleName;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private _resolvePropertyName(attrName: string): string {
|
||||
var mappedPropName = StringMapWrapper.get(DOM.attrToPropMap, attrName);
|
||||
return isPresent(mappedPropName) ? mappedPropName : attrName;
|
||||
}
|
||||
}
|
@ -1,5 +1,12 @@
|
||||
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
|
||||
import {ListWrapper, MapWrapper, Set, SetWrapper, List} from 'angular2/src/facade/collection';
|
||||
import {isPresent, isBlank, BaseException, StringWrapper} from 'angular2/src/facade/lang';
|
||||
import {
|
||||
ListWrapper,
|
||||
MapWrapper,
|
||||
Set,
|
||||
SetWrapper,
|
||||
List,
|
||||
StringMapWrapper
|
||||
} from 'angular2/src/facade/collection';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
import {
|
||||
@ -13,7 +20,6 @@ import {
|
||||
|
||||
import {DomProtoView, DomProtoViewRef, resolveInternalDomProtoView} from './proto_view';
|
||||
import {ElementBinder, Event, HostAction} from './element_binder';
|
||||
import {PropertySetterFactory} from './property_setter_factory';
|
||||
|
||||
import * as api from '../../api';
|
||||
|
||||
@ -43,53 +49,28 @@ export class ProtoViewBuilder {
|
||||
this.variableBindings.set(value, name);
|
||||
}
|
||||
|
||||
build(setterFactory: PropertySetterFactory): api.ProtoViewDto {
|
||||
build(): api.ProtoViewDto {
|
||||
var renderElementBinders = [];
|
||||
|
||||
var apiElementBinders = [];
|
||||
var transitiveContentTagCount = 0;
|
||||
var boundTextNodeCount = 0;
|
||||
ListWrapper.forEach(this.elements, (ebb: ElementBinderBuilder) => {
|
||||
var propertySetters = new Map();
|
||||
var hostActions = new Map();
|
||||
|
||||
var directiveTemplatePropertyNames = new Set();
|
||||
var apiDirectiveBinders = ListWrapper.map(ebb.directives, (dbb: DirectiveBuilder) => {
|
||||
ebb.eventBuilder.merge(dbb.eventBuilder);
|
||||
|
||||
MapWrapper.forEach(dbb.hostPropertyBindings, (_, hostPropertyName) => {
|
||||
propertySetters.set(hostPropertyName,
|
||||
setterFactory.createSetter(ebb.element, isPresent(ebb.componentId),
|
||||
hostPropertyName));
|
||||
});
|
||||
|
||||
ListWrapper.forEach(dbb.hostActions, (hostAction) => {
|
||||
hostActions.set(hostAction.actionExpression, hostAction.expression);
|
||||
});
|
||||
|
||||
ListWrapper.forEach(dbb.templatePropertyNames,
|
||||
(name) => directiveTemplatePropertyNames.add(name));
|
||||
return new api.DirectiveBinder({
|
||||
directiveIndex: dbb.directiveIndex,
|
||||
propertyBindings: dbb.propertyBindings,
|
||||
eventBindings: dbb.eventBindings,
|
||||
hostPropertyBindings: dbb.hostPropertyBindings
|
||||
hostPropertyBindings:
|
||||
buildElementPropertyBindings(ebb.element, isPresent(ebb.componentId),
|
||||
dbb.hostPropertyBindings, directiveTemplatePropertyNames)
|
||||
});
|
||||
});
|
||||
|
||||
MapWrapper.forEach(ebb.propertyBindings, (_, propertyName) => {
|
||||
var propSetter =
|
||||
setterFactory.createSetter(ebb.element, isPresent(ebb.componentId), propertyName);
|
||||
|
||||
if (propSetter === PropertySetterFactory.noopSetter) {
|
||||
if (!SetWrapper.has(ebb.propertyBindingsToDirectives, propertyName)) {
|
||||
throw new BaseException(
|
||||
`Can't bind to '${propertyName}' since it isn't a know property of the '${DOM.tagName(ebb.element).toLowerCase()}' element and there are no matching directives with a corresponding property`);
|
||||
}
|
||||
}
|
||||
|
||||
propertySetters.set(propertyName, propSetter);
|
||||
});
|
||||
|
||||
var nestedProtoView =
|
||||
isPresent(ebb.nestedProtoView) ? ebb.nestedProtoView.build(setterFactory) : null;
|
||||
var nestedProtoView = isPresent(ebb.nestedProtoView) ? ebb.nestedProtoView.build() : null;
|
||||
var nestedRenderProtoView =
|
||||
isPresent(nestedProtoView) ? resolveInternalDomProtoView(nestedProtoView.render) : null;
|
||||
if (isPresent(nestedRenderProtoView)) {
|
||||
@ -105,7 +86,9 @@ export class ProtoViewBuilder {
|
||||
distanceToParent: ebb.distanceToParent,
|
||||
directives: apiDirectiveBinders,
|
||||
nestedProtoView: nestedProtoView,
|
||||
propertyBindings: ebb.propertyBindings,
|
||||
propertyBindings:
|
||||
buildElementPropertyBindings(ebb.element, isPresent(ebb.componentId),
|
||||
ebb.propertyBindings, directiveTemplatePropertyNames),
|
||||
variableBindings: ebb.variableBindings,
|
||||
eventBindings: ebb.eventBindings,
|
||||
textBindings: ebb.textBindings,
|
||||
@ -124,8 +107,6 @@ export class ProtoViewBuilder {
|
||||
eventLocals: new LiteralArray(ebb.eventBuilder.buildEventLocals()),
|
||||
localEvents: ebb.eventBuilder.buildLocalEvents(),
|
||||
globalEvents: ebb.eventBuilder.buildGlobalEvents(),
|
||||
hostActions: hostActions,
|
||||
propertySetters: propertySetters,
|
||||
elementIsEmpty: childNodeInfo.elementIsEmpty
|
||||
}));
|
||||
});
|
||||
@ -216,7 +197,9 @@ export class ElementBinderBuilder {
|
||||
return this.nestedProtoView;
|
||||
}
|
||||
|
||||
bindProperty(name, expression) { this.propertyBindings.set(name, expression); }
|
||||
bindProperty(name: string, expression: ASTWithSource) {
|
||||
this.propertyBindings.set(name, expression);
|
||||
}
|
||||
|
||||
bindPropertyToDirective(name: string) {
|
||||
// we are filling in a set of property names that are bound to a property
|
||||
@ -257,20 +240,27 @@ export class ElementBinderBuilder {
|
||||
}
|
||||
|
||||
export class DirectiveBuilder {
|
||||
// mapping from directive property name to AST for that directive
|
||||
propertyBindings: Map<string, ASTWithSource> = new Map();
|
||||
// property names used in the template
|
||||
templatePropertyNames: List<string> = [];
|
||||
hostPropertyBindings: Map<string, ASTWithSource> = new Map();
|
||||
hostActions: List<HostAction> = [];
|
||||
eventBindings: List<api.EventBinding> = [];
|
||||
eventBuilder: EventBuilder = new EventBuilder();
|
||||
|
||||
constructor(public directiveIndex: number) {}
|
||||
|
||||
bindProperty(name, expression) { this.propertyBindings.set(name, expression); }
|
||||
bindProperty(name: string, expression: ASTWithSource, elProp: string) {
|
||||
this.propertyBindings.set(name, expression);
|
||||
if (isPresent(elProp)) {
|
||||
// we are filling in a set of property names that are bound to a property
|
||||
// of at least one directive. This allows us to report "dangling" bindings.
|
||||
this.templatePropertyNames.push(elProp);
|
||||
}
|
||||
}
|
||||
|
||||
bindHostProperty(name, expression) { this.hostPropertyBindings.set(name, expression); }
|
||||
|
||||
bindHostAction(actionName: string, actionExpression: string, expression: ASTWithSource) {
|
||||
this.hostActions.push(new HostAction(actionName, actionExpression, expression));
|
||||
bindHostProperty(name: string, expression: ASTWithSource) {
|
||||
this.hostPropertyBindings.set(name, expression);
|
||||
}
|
||||
|
||||
bindEvent(name, expression, target = null) {
|
||||
@ -347,3 +337,60 @@ export class EventBuilder extends AstTransformer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var PROPERTY_PARTS_SEPARATOR = new RegExp('\\.');
|
||||
const ATTRIBUTE_PREFIX = 'attr';
|
||||
const CLASS_PREFIX = 'class';
|
||||
const STYLE_PREFIX = 'style';
|
||||
|
||||
function buildElementPropertyBindings(protoElement: /*element*/ any, isNgComponent: boolean,
|
||||
bindingsInTemplate: Map<string, ASTWithSource>,
|
||||
directiveTempaltePropertyNames: Set<string>) {
|
||||
var propertyBindings = [];
|
||||
MapWrapper.forEach(bindingsInTemplate, (ast, propertyNameInTemplate) => {
|
||||
var propertyBinding = createElementPropertyBinding(ast, propertyNameInTemplate);
|
||||
if (isValidElementPropertyBinding(protoElement, isNgComponent, propertyBinding)) {
|
||||
propertyBindings.push(propertyBinding);
|
||||
} else if (!SetWrapper.has(directiveTempaltePropertyNames, propertyNameInTemplate)) {
|
||||
throw new BaseException(
|
||||
`Can't bind to '${propertyNameInTemplate}' since it isn't a know property of the '${DOM.tagName(protoElement).toLowerCase()}' element and there are no matching directives with a corresponding property`);
|
||||
}
|
||||
});
|
||||
return propertyBindings;
|
||||
}
|
||||
|
||||
function isValidElementPropertyBinding(protoElement: /*element*/ any, isNgComponent: boolean,
|
||||
binding: api.ElementPropertyBinding): boolean {
|
||||
if (binding.type === api.PropertyBindingType.PROPERTY) {
|
||||
var tagName = DOM.tagName(protoElement);
|
||||
var possibleCustomElement = tagName.indexOf('-') !== -1;
|
||||
if (possibleCustomElement && !isNgComponent) {
|
||||
// can't tell now as we don't know which properties a custom element will get
|
||||
// once it is instantiated
|
||||
return true;
|
||||
} else {
|
||||
return DOM.hasProperty(protoElement, binding.property);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function createElementPropertyBinding(ast: ASTWithSource,
|
||||
propertyNameInTemplate: string): api.ElementPropertyBinding {
|
||||
var parts = StringWrapper.split(propertyNameInTemplate, PROPERTY_PARTS_SEPARATOR);
|
||||
if (parts.length === 1) {
|
||||
var propName = parts[0];
|
||||
var mappedPropName = StringMapWrapper.get(DOM.attrToPropMap, propName);
|
||||
propName = isPresent(mappedPropName) ? mappedPropName : propName;
|
||||
return new api.ElementPropertyBinding(api.PropertyBindingType.PROPERTY, ast, propName);
|
||||
} else if (parts[0] == ATTRIBUTE_PREFIX) {
|
||||
return new api.ElementPropertyBinding(api.PropertyBindingType.ATTRIBUTE, ast, parts[1]);
|
||||
} else if (parts[0] == CLASS_PREFIX) {
|
||||
return new api.ElementPropertyBinding(api.PropertyBindingType.CLASS, ast, parts[1]);
|
||||
} else if (parts[0] == STYLE_PREFIX) {
|
||||
var unit = parts.length > 2 ? parts[2] : null;
|
||||
return new api.ElementPropertyBinding(api.PropertyBindingType.STYLE, ast, parts[1], unit);
|
||||
} else {
|
||||
throw new BaseException(`Invalid property name ${propertyNameInTemplate}`);
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection';
|
||||
import {Locals} from 'angular2/change_detection';
|
||||
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
|
||||
import {isPresent, isBlank, BaseException, stringify} from 'angular2/src/facade/lang';
|
||||
|
||||
import {DomProtoView} from './proto_view';
|
||||
import {LightDom} from '../shadow_dom/light_dom';
|
||||
import {DomElement} from './element';
|
||||
|
||||
import {RenderViewRef, EventDispatcher} from '../../api';
|
||||
import {camelCaseToDashCase} from '../util';
|
||||
|
||||
export function resolveInternalDomView(viewRef: RenderViewRef) {
|
||||
return (<DomViewRef>viewRef)._view;
|
||||
@ -40,20 +40,42 @@ export class DomView {
|
||||
}
|
||||
|
||||
setElementProperty(elementIndex: number, propertyName: string, value: any) {
|
||||
var setter = this.proto.elementBinders[elementIndex].propertySetters.get(propertyName);
|
||||
setter(this.boundElements[elementIndex].element, value);
|
||||
DOM.setProperty(this.boundElements[elementIndex].element, propertyName, value);
|
||||
}
|
||||
|
||||
callAction(elementIndex: number, actionExpression: string, actionArgs: any) {
|
||||
var binder = this.proto.elementBinders[elementIndex];
|
||||
var hostAction = binder.hostActions.get(actionExpression);
|
||||
hostAction.eval(this.boundElements[elementIndex].element, this._localsWithAction(actionArgs));
|
||||
setElementAttribute(elementIndex: number, attributeName: string, value: string) {
|
||||
var element = this.boundElements[elementIndex].element;
|
||||
var dashCasedAttributeName = camelCaseToDashCase(attributeName);
|
||||
if (isPresent(value)) {
|
||||
DOM.setAttribute(element, dashCasedAttributeName, stringify(value));
|
||||
} else {
|
||||
DOM.removeAttribute(element, dashCasedAttributeName);
|
||||
}
|
||||
}
|
||||
|
||||
_localsWithAction(action: Object): Locals {
|
||||
var map = new Map();
|
||||
map.set('$action', action);
|
||||
return new Locals(null, map);
|
||||
setElementClass(elementIndex: number, className: string, isAdd: boolean) {
|
||||
var element = this.boundElements[elementIndex].element;
|
||||
var dashCasedClassName = camelCaseToDashCase(className);
|
||||
if (isAdd) {
|
||||
DOM.addClass(element, dashCasedClassName);
|
||||
} else {
|
||||
DOM.removeClass(element, dashCasedClassName);
|
||||
}
|
||||
}
|
||||
|
||||
setElementStyle(elementIndex: number, styleName: string, value: string) {
|
||||
var element = this.boundElements[elementIndex].element;
|
||||
var dashCasedStyleName = camelCaseToDashCase(styleName);
|
||||
if (isPresent(value)) {
|
||||
DOM.setStyle(element, dashCasedStyleName, stringify(value));
|
||||
} else {
|
||||
DOM.removeStyle(element, dashCasedStyleName);
|
||||
}
|
||||
}
|
||||
|
||||
invokeElementMethod(elementIndex: number, methodName: string, args: List<any>) {
|
||||
var element = this.boundElements[elementIndex].element;
|
||||
DOM.invoke(element, methodName, args);
|
||||
}
|
||||
|
||||
setText(textIndex: number, value: string) { DOM.setText(this.boundTextNodes[textIndex], value); }
|
||||
|
Reference in New Issue
Block a user