225 lines
8.9 KiB
TypeScript
225 lines
8.9 KiB
TypeScript
|
import {ListWrapper} from 'angular2/src/core/facade/collection';
|
||
|
import {isPresent, isBlank} from 'angular2/src/core/facade/lang';
|
||
|
import {reflector} from 'angular2/src/core/reflection/reflection';
|
||
|
|
||
|
import {
|
||
|
ChangeDetection,
|
||
|
DirectiveIndex,
|
||
|
BindingRecord,
|
||
|
DirectiveRecord,
|
||
|
ProtoChangeDetector,
|
||
|
ChangeDetectionStrategy,
|
||
|
ChangeDetectorDefinition,
|
||
|
ChangeDetectorGenConfig,
|
||
|
ASTWithSource
|
||
|
} from 'angular2/src/core/change_detection/change_detection';
|
||
|
|
||
|
import {DirectiveMetadata, TypeMetadata} from './api';
|
||
|
import {
|
||
|
TemplateAst,
|
||
|
ElementAst,
|
||
|
BoundTextAst,
|
||
|
PropertyBindingType,
|
||
|
DirectiveAst,
|
||
|
TemplateAstVisitor,
|
||
|
templateVisitAll,
|
||
|
NgContentAst,
|
||
|
EmbeddedTemplateAst,
|
||
|
VariableAst,
|
||
|
BoundElementPropertyAst,
|
||
|
BoundEventAst,
|
||
|
BoundDirectivePropertyAst,
|
||
|
AttrAst,
|
||
|
TextAst
|
||
|
} from './template_ast';
|
||
|
|
||
|
export function createChangeDetectorDefinitions(
|
||
|
componentType: TypeMetadata, componentStrategy: ChangeDetectionStrategy,
|
||
|
genConfig: ChangeDetectorGenConfig, parsedTemplate: TemplateAst[]): ChangeDetectorDefinition[] {
|
||
|
var visitor = new ProtoViewVisitor(componentStrategy);
|
||
|
templateVisitAll(visitor, parsedTemplate);
|
||
|
return createChangeDefinitions(visitor.allProtoViews, componentType, genConfig);
|
||
|
}
|
||
|
|
||
|
class ProtoViewVisitor implements TemplateAstVisitor {
|
||
|
viewCount: number = 0;
|
||
|
protoViewStack: ProtoViewVisitorData[] = [];
|
||
|
allProtoViews: ProtoViewVisitorData[] = [];
|
||
|
|
||
|
constructor(componentStrategy: ChangeDetectionStrategy) {
|
||
|
this._beginProtoView(new ProtoViewVisitorData(null, componentStrategy, this.viewCount++));
|
||
|
}
|
||
|
|
||
|
private _beginProtoView(data: ProtoViewVisitorData) {
|
||
|
this.protoViewStack.push(data);
|
||
|
this.allProtoViews.push(data);
|
||
|
}
|
||
|
|
||
|
get currentProtoView(): ProtoViewVisitorData {
|
||
|
return this.protoViewStack[this.protoViewStack.length - 1];
|
||
|
}
|
||
|
|
||
|
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
|
||
|
this.currentProtoView.boundElementCount++;
|
||
|
templateVisitAll(this, ast.directives);
|
||
|
|
||
|
this.viewCount++;
|
||
|
this._beginProtoView(new ProtoViewVisitorData(
|
||
|
this.currentProtoView, ChangeDetectionStrategy.Default, this.viewCount - 1));
|
||
|
// Attention: variables present on an embedded template count towards
|
||
|
// the embedded template and not the template anchor!
|
||
|
templateVisitAll(this, ast.vars);
|
||
|
templateVisitAll(this, ast.children);
|
||
|
this.protoViewStack.pop();
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
visitElement(ast: ElementAst, context: any): any {
|
||
|
if (ast.isBound()) {
|
||
|
this.currentProtoView.boundElementCount++;
|
||
|
}
|
||
|
templateVisitAll(this, ast.properties, null);
|
||
|
templateVisitAll(this, ast.events);
|
||
|
templateVisitAll(this, ast.vars);
|
||
|
for (var i = 0; i < ast.directives.length; i++) {
|
||
|
ast.directives[i].visit(this, i);
|
||
|
}
|
||
|
templateVisitAll(this, ast.children);
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
visitNgContent(ast: NgContentAst, context: any): any { return null; }
|
||
|
|
||
|
visitVariable(ast: VariableAst, context: any): any {
|
||
|
this.currentProtoView.variableNames.push(ast.name);
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
visitEvent(ast: BoundEventAst, directiveRecord: DirectiveRecord): any {
|
||
|
var bindingRecord =
|
||
|
isPresent(directiveRecord) ?
|
||
|
BindingRecord.createForHostEvent(ast.handler, ast.name, directiveRecord) :
|
||
|
BindingRecord.createForEvent(ast.handler, ast.name,
|
||
|
this.currentProtoView.boundElementCount - 1);
|
||
|
this.currentProtoView.eventRecords.push(bindingRecord);
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
visitElementProperty(ast: BoundElementPropertyAst, directiveRecord: DirectiveRecord): any {
|
||
|
var boundElementIndex = this.currentProtoView.boundElementCount - 1;
|
||
|
var dirIndex = isPresent(directiveRecord) ? directiveRecord.directiveIndex : null;
|
||
|
var bindingRecord;
|
||
|
if (ast.type === PropertyBindingType.Property) {
|
||
|
bindingRecord =
|
||
|
isPresent(dirIndex) ?
|
||
|
BindingRecord.createForHostProperty(dirIndex, ast.value, ast.name) :
|
||
|
BindingRecord.createForElementProperty(ast.value, boundElementIndex, ast.name);
|
||
|
} else if (ast.type === PropertyBindingType.Attribute) {
|
||
|
bindingRecord =
|
||
|
isPresent(dirIndex) ?
|
||
|
BindingRecord.createForHostAttribute(dirIndex, ast.value, ast.name) :
|
||
|
BindingRecord.createForElementAttribute(ast.value, boundElementIndex, ast.name);
|
||
|
} else if (ast.type === PropertyBindingType.Class) {
|
||
|
bindingRecord =
|
||
|
isPresent(dirIndex) ?
|
||
|
BindingRecord.createForHostClass(dirIndex, ast.value, ast.name) :
|
||
|
BindingRecord.createForElementClass(ast.value, boundElementIndex, ast.name);
|
||
|
} else if (ast.type === PropertyBindingType.Style) {
|
||
|
bindingRecord =
|
||
|
isPresent(dirIndex) ?
|
||
|
BindingRecord.createForHostStyle(dirIndex, ast.value, ast.name, ast.unit) :
|
||
|
BindingRecord.createForElementStyle(ast.value, boundElementIndex, ast.name, ast.unit);
|
||
|
}
|
||
|
this.currentProtoView.bindingRecords.push(bindingRecord);
|
||
|
return null;
|
||
|
}
|
||
|
visitAttr(ast: AttrAst, context: any): any { return null; }
|
||
|
visitBoundText(ast: BoundTextAst, context: any): any {
|
||
|
var boundTextIndex = this.currentProtoView.boundTextCount++;
|
||
|
this.currentProtoView.bindingRecords.push(
|
||
|
BindingRecord.createForTextNode(ast.value, boundTextIndex));
|
||
|
return null;
|
||
|
}
|
||
|
visitText(ast: TextAst, context: any): any { return null; }
|
||
|
visitDirective(ast: DirectiveAst, directiveIndexAsNumber: number): any {
|
||
|
var directiveIndex =
|
||
|
new DirectiveIndex(this.currentProtoView.boundElementCount - 1, directiveIndexAsNumber);
|
||
|
var directiveMetadata = ast.directive;
|
||
|
var changeDetectionMeta = directiveMetadata.changeDetection;
|
||
|
var directiveRecord = new DirectiveRecord({
|
||
|
directiveIndex: directiveIndex,
|
||
|
callAfterContentInit: changeDetectionMeta.callAfterContentInit,
|
||
|
callAfterContentChecked: changeDetectionMeta.callAfterContentChecked,
|
||
|
callAfterViewInit: changeDetectionMeta.callAfterViewInit,
|
||
|
callAfterViewChecked: changeDetectionMeta.callAfterViewChecked,
|
||
|
callOnChanges: changeDetectionMeta.callOnChanges,
|
||
|
callDoCheck: changeDetectionMeta.callDoCheck,
|
||
|
callOnInit: changeDetectionMeta.callOnInit,
|
||
|
changeDetection: changeDetectionMeta.changeDetection
|
||
|
});
|
||
|
this.currentProtoView.directiveRecords.push(directiveRecord);
|
||
|
|
||
|
templateVisitAll(this, ast.properties, directiveRecord);
|
||
|
var bindingRecords = this.currentProtoView.bindingRecords;
|
||
|
if (directiveRecord.callOnChanges) {
|
||
|
bindingRecords.push(BindingRecord.createDirectiveOnChanges(directiveRecord));
|
||
|
}
|
||
|
if (directiveRecord.callOnInit) {
|
||
|
bindingRecords.push(BindingRecord.createDirectiveOnInit(directiveRecord));
|
||
|
}
|
||
|
if (directiveRecord.callDoCheck) {
|
||
|
bindingRecords.push(BindingRecord.createDirectiveDoCheck(directiveRecord));
|
||
|
}
|
||
|
templateVisitAll(this, ast.hostProperties, directiveRecord);
|
||
|
templateVisitAll(this, ast.hostEvents, directiveRecord);
|
||
|
return null;
|
||
|
}
|
||
|
visitDirectiveProperty(ast: BoundDirectivePropertyAst, directiveRecord: DirectiveRecord): any {
|
||
|
// TODO: these setters should eventually be created by change detection, to make
|
||
|
// it monomorphic!
|
||
|
var setter = reflector.setter(ast.directiveName);
|
||
|
this.currentProtoView.bindingRecords.push(
|
||
|
BindingRecord.createForDirective(ast.value, ast.directiveName, setter, directiveRecord));
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class ProtoViewVisitorData {
|
||
|
boundTextCount: number = 0;
|
||
|
boundElementCount: number = 0;
|
||
|
variableNames: string[] = [];
|
||
|
bindingRecords: BindingRecord[] = [];
|
||
|
eventRecords: BindingRecord[] = [];
|
||
|
directiveRecords: DirectiveRecord[] = [];
|
||
|
constructor(public parent: ProtoViewVisitorData, public strategy: ChangeDetectionStrategy,
|
||
|
public viewIndex: number) {}
|
||
|
}
|
||
|
|
||
|
function createChangeDefinitions(pvDatas: ProtoViewVisitorData[], componentType: TypeMetadata,
|
||
|
genConfig: ChangeDetectorGenConfig): ChangeDetectorDefinition[] {
|
||
|
var pvVariableNames = _collectNestedProtoViewsVariableNames(pvDatas);
|
||
|
return pvDatas.map(pvData => {
|
||
|
var viewType = pvData.viewIndex === 0 ? 'component' : 'embedded';
|
||
|
var id = _protoViewId(componentType, pvData.viewIndex, viewType);
|
||
|
return new ChangeDetectorDefinition(id, pvData.strategy, pvVariableNames[pvData.viewIndex],
|
||
|
pvData.bindingRecords, pvData.eventRecords,
|
||
|
pvData.directiveRecords, genConfig);
|
||
|
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function _collectNestedProtoViewsVariableNames(pvs: ProtoViewVisitorData[]): string[][] {
|
||
|
var nestedPvVariableNames: string[][] = ListWrapper.createFixedSize(pvs.length);
|
||
|
pvs.forEach((pv) => {
|
||
|
var parentVariableNames: string[] =
|
||
|
isPresent(pv.parent) ? nestedPvVariableNames[pv.parent.viewIndex] : [];
|
||
|
nestedPvVariableNames[pv.viewIndex] = parentVariableNames.concat(pv.variableNames);
|
||
|
});
|
||
|
return nestedPvVariableNames;
|
||
|
}
|
||
|
|
||
|
|
||
|
function _protoViewId(hostComponentType: TypeMetadata, pvIndex: number, viewType: string): string {
|
||
|
return `${hostComponentType.typeName}_${viewType}_${pvIndex}`;
|
||
|
}
|