@ -134,7 +134,7 @@ export class TemplateMetadata {
|
||||
this.ngContentSelectors = ngContentSelectors;
|
||||
}
|
||||
|
||||
static fromJson(data: StringMap<string, any>):TemplateMetadata {
|
||||
static fromJson(data: StringMap<string, any>): TemplateMetadata {
|
||||
return new TemplateMetadata({
|
||||
encapsulation: isPresent(data['encapsulation']) ?
|
||||
viewEncapsulationFromJson(data['encapsulation']) :
|
||||
|
@ -36,47 +36,43 @@ import {
|
||||
export function createChangeDetectorDefinitions(
|
||||
componentType: TypeMetadata, componentStrategy: ChangeDetectionStrategy,
|
||||
genConfig: ChangeDetectorGenConfig, parsedTemplate: TemplateAst[]): ChangeDetectorDefinition[] {
|
||||
var visitor = new ProtoViewVisitor(componentStrategy);
|
||||
var pvVisitors = [];
|
||||
var visitor = new ProtoViewVisitor(null, pvVisitors, componentStrategy);
|
||||
templateVisitAll(visitor, parsedTemplate);
|
||||
return createChangeDefinitions(visitor.allProtoViews, componentType, genConfig);
|
||||
return createChangeDefinitions(pvVisitors, componentType, genConfig);
|
||||
}
|
||||
|
||||
class ProtoViewVisitor implements TemplateAstVisitor {
|
||||
viewCount: number = 0;
|
||||
protoViewStack: ProtoViewVisitorData[] = [];
|
||||
allProtoViews: ProtoViewVisitorData[] = [];
|
||||
viewIndex: number;
|
||||
boundTextCount: number = 0;
|
||||
boundElementCount: number = 0;
|
||||
variableNames: string[] = [];
|
||||
bindingRecords: BindingRecord[] = [];
|
||||
eventRecords: BindingRecord[] = [];
|
||||
directiveRecords: DirectiveRecord[] = [];
|
||||
|
||||
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];
|
||||
constructor(public parent: ProtoViewVisitor, public allVisitors: ProtoViewVisitor[],
|
||||
public strategy: ChangeDetectionStrategy) {
|
||||
this.viewIndex = allVisitors.length;
|
||||
allVisitors.push(this);
|
||||
}
|
||||
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
|
||||
this.currentProtoView.boundElementCount++;
|
||||
this.boundElementCount++;
|
||||
templateVisitAll(this, ast.directives);
|
||||
|
||||
this.viewCount++;
|
||||
this._beginProtoView(new ProtoViewVisitorData(
|
||||
this.currentProtoView, ChangeDetectionStrategy.Default, this.viewCount - 1));
|
||||
var childVisitor =
|
||||
new ProtoViewVisitor(this, this.allVisitors, ChangeDetectionStrategy.Default);
|
||||
// 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();
|
||||
templateVisitAll(childVisitor, ast.vars);
|
||||
templateVisitAll(childVisitor, ast.children);
|
||||
return null;
|
||||
}
|
||||
|
||||
visitElement(ast: ElementAst, context: any): any {
|
||||
if (ast.isBound()) {
|
||||
this.currentProtoView.boundElementCount++;
|
||||
this.boundElementCount++;
|
||||
}
|
||||
templateVisitAll(this, ast.properties, null);
|
||||
templateVisitAll(this, ast.events);
|
||||
@ -91,7 +87,7 @@ class ProtoViewVisitor implements TemplateAstVisitor {
|
||||
visitNgContent(ast: NgContentAst, context: any): any { return null; }
|
||||
|
||||
visitVariable(ast: VariableAst, context: any): any {
|
||||
this.currentProtoView.variableNames.push(ast.name);
|
||||
this.variableNames.push(ast.name);
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -99,14 +95,13 @@ class ProtoViewVisitor implements TemplateAstVisitor {
|
||||
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);
|
||||
BindingRecord.createForEvent(ast.handler, ast.name, this.boundElementCount - 1);
|
||||
this.eventRecords.push(bindingRecord);
|
||||
return null;
|
||||
}
|
||||
|
||||
visitElementProperty(ast: BoundElementPropertyAst, directiveRecord: DirectiveRecord): any {
|
||||
var boundElementIndex = this.currentProtoView.boundElementCount - 1;
|
||||
var boundElementIndex = this.boundElementCount - 1;
|
||||
var dirIndex = isPresent(directiveRecord) ? directiveRecord.directiveIndex : null;
|
||||
var bindingRecord;
|
||||
if (ast.type === PropertyBindingType.Property) {
|
||||
@ -130,20 +125,18 @@ class ProtoViewVisitor implements TemplateAstVisitor {
|
||||
BindingRecord.createForHostStyle(dirIndex, ast.value, ast.name, ast.unit) :
|
||||
BindingRecord.createForElementStyle(ast.value, boundElementIndex, ast.name, ast.unit);
|
||||
}
|
||||
this.currentProtoView.bindingRecords.push(bindingRecord);
|
||||
this.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));
|
||||
var boundTextIndex = this.boundTextCount++;
|
||||
this.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 directiveIndex = new DirectiveIndex(this.boundElementCount - 1, directiveIndexAsNumber);
|
||||
var directiveMetadata = ast.directive;
|
||||
var changeDetectionMeta = directiveMetadata.changeDetection;
|
||||
var directiveRecord = new DirectiveRecord({
|
||||
@ -157,10 +150,10 @@ class ProtoViewVisitor implements TemplateAstVisitor {
|
||||
callOnInit: changeDetectionMeta.callOnInit,
|
||||
changeDetection: changeDetectionMeta.changeDetection
|
||||
});
|
||||
this.currentProtoView.directiveRecords.push(directiveRecord);
|
||||
this.directiveRecords.push(directiveRecord);
|
||||
|
||||
templateVisitAll(this, ast.properties, directiveRecord);
|
||||
var bindingRecords = this.currentProtoView.bindingRecords;
|
||||
var bindingRecords = this.bindingRecords;
|
||||
if (directiveRecord.callOnChanges) {
|
||||
bindingRecords.push(BindingRecord.createDirectiveOnChanges(directiveRecord));
|
||||
}
|
||||
@ -178,39 +171,29 @@ class ProtoViewVisitor implements TemplateAstVisitor {
|
||||
// TODO: these setters should eventually be created by change detection, to make
|
||||
// it monomorphic!
|
||||
var setter = reflector.setter(ast.directiveName);
|
||||
this.currentProtoView.bindingRecords.push(
|
||||
this.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,
|
||||
function createChangeDefinitions(pvVisitors: ProtoViewVisitor[], 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);
|
||||
var pvVariableNames = _collectNestedProtoViewsVariableNames(pvVisitors);
|
||||
return pvVisitors.map(pvVisitor => {
|
||||
var viewType = pvVisitor.viewIndex === 0 ? 'component' : 'embedded';
|
||||
var id = _protoViewId(componentType, pvVisitor.viewIndex, viewType);
|
||||
return new ChangeDetectorDefinition(
|
||||
id, pvVisitor.strategy, pvVariableNames[pvVisitor.viewIndex], pvVisitor.bindingRecords,
|
||||
pvVisitor.eventRecords, pvVisitor.directiveRecords, genConfig);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function _collectNestedProtoViewsVariableNames(pvs: ProtoViewVisitorData[]): string[][] {
|
||||
var nestedPvVariableNames: string[][] = ListWrapper.createFixedSize(pvs.length);
|
||||
pvs.forEach((pv) => {
|
||||
function _collectNestedProtoViewsVariableNames(pvVisitors: ProtoViewVisitor[]): string[][] {
|
||||
var nestedPvVariableNames: string[][] = ListWrapper.createFixedSize(pvVisitors.length);
|
||||
pvVisitors.forEach((pv) => {
|
||||
var parentVariableNames: string[] =
|
||||
isPresent(pv.parent) ? nestedPvVariableNames[pv.parent.viewIndex] : [];
|
||||
nestedPvVariableNames[pv.viewIndex] = parentVariableNames.concat(pv.variableNames);
|
||||
|
241
modules/angular2/src/compiler/command_compiler.ts
Normal file
241
modules/angular2/src/compiler/command_compiler.ts
Normal file
@ -0,0 +1,241 @@
|
||||
import {isPresent, Type} from 'angular2/src/core/facade/lang';
|
||||
import {
|
||||
TemplateCmd,
|
||||
text,
|
||||
ngContent,
|
||||
beginElement,
|
||||
endElement,
|
||||
beginComponent,
|
||||
endComponent,
|
||||
embeddedTemplate
|
||||
} from 'angular2/src/core/compiler/template_commands';
|
||||
import {
|
||||
TemplateAst,
|
||||
TemplateAstVisitor,
|
||||
NgContentAst,
|
||||
EmbeddedTemplateAst,
|
||||
ElementAst,
|
||||
VariableAst,
|
||||
BoundEventAst,
|
||||
BoundElementPropertyAst,
|
||||
AttrAst,
|
||||
BoundTextAst,
|
||||
TextAst,
|
||||
DirectiveAst,
|
||||
BoundDirectivePropertyAst,
|
||||
templateVisitAll
|
||||
} from './template_ast';
|
||||
import {SourceModule, DirectiveMetadata, TypeMetadata} from './api';
|
||||
import {ViewEncapsulation} from 'angular2/src/core/render/api';
|
||||
import {shimHostAttribute, shimContentAttribute} from './style_compiler';
|
||||
import {escapeSingleQuoteString} from './util';
|
||||
|
||||
const TEMPLATE_COMMANDS_MODULE = 'angular2/src/core/compiler/template_commands';
|
||||
const TEMPLATE_COMMANDS_MODULE_ALIAS = 'tc';
|
||||
|
||||
export class CommandCompiler {
|
||||
compileComponentRuntime(component: DirectiveMetadata, template: TemplateAst[],
|
||||
componentTemplateFactory: Function): TemplateCmd[] {
|
||||
var visitor =
|
||||
new CommandBuilderVisitor(new RuntimeCommandFactory(componentTemplateFactory), component);
|
||||
templateVisitAll(visitor, template);
|
||||
return visitor.result;
|
||||
}
|
||||
|
||||
compileComponentCodeGen(component: DirectiveMetadata, template: TemplateAst[],
|
||||
componentTemplateFactory: Function): SourceModule {
|
||||
var imports: string[][] = [[TEMPLATE_COMMANDS_MODULE, TEMPLATE_COMMANDS_MODULE_ALIAS]];
|
||||
var visitor = new CommandBuilderVisitor(
|
||||
new CodegenCommandFactory(componentTemplateFactory, TEMPLATE_COMMANDS_MODULE_ALIAS,
|
||||
imports),
|
||||
component);
|
||||
templateVisitAll(visitor, template);
|
||||
var source = `var COMMANDS = [${visitor.result.join(',')}];`;
|
||||
return new SourceModule(null, source, imports);
|
||||
}
|
||||
}
|
||||
|
||||
interface CommandFactory<R> {
|
||||
createText(value: string, isBound: boolean, ngContentIndex: number): R;
|
||||
createNgContent(ngContentIndex: number): R;
|
||||
createBeginElement(name: string, attrNameAndValues: string[], eventNames: string[],
|
||||
variableNameAndValues: string[], directives: TypeMetadata[], isBound: boolean,
|
||||
ngContentIndex: number): R;
|
||||
createEndElement(): R;
|
||||
createBeginComponent(name: string, attrNameAndValues: string[], eventNames: string[],
|
||||
variableNameAndValues: string[], directives: TypeMetadata[],
|
||||
nativeShadow: boolean, ngContentIndex: number): R;
|
||||
createEndComponent(): R;
|
||||
createEmbeddedTemplate(attrNameAndValues: string[], variableNameAndValues: string[],
|
||||
directives: TypeMetadata[], isMerged: boolean, ngContentIndex: number,
|
||||
children: R[]): R;
|
||||
}
|
||||
|
||||
class RuntimeCommandFactory implements CommandFactory<TemplateCmd> {
|
||||
constructor(public componentTemplateFactory: Function) {}
|
||||
private _mapDirectives(directives: TypeMetadata[]): Type[] {
|
||||
return directives.map(directive => directive.type);
|
||||
}
|
||||
|
||||
createText(value: string, isBound: boolean, ngContentIndex: number): TemplateCmd {
|
||||
return text(value, isBound, ngContentIndex);
|
||||
}
|
||||
createNgContent(ngContentIndex: number): TemplateCmd { return ngContent(ngContentIndex); }
|
||||
createBeginElement(name: string, attrNameAndValues: string[], eventNames: string[],
|
||||
variableNameAndValues: string[], directives: TypeMetadata[], isBound: boolean,
|
||||
ngContentIndex: number): TemplateCmd {
|
||||
return beginElement(name, attrNameAndValues, eventNames, variableNameAndValues,
|
||||
this._mapDirectives(directives), isBound, ngContentIndex);
|
||||
}
|
||||
createEndElement(): TemplateCmd { return endElement(); }
|
||||
createBeginComponent(name: string, attrNameAndValues: string[], eventNames: string[],
|
||||
variableNameAndValues: string[], directives: TypeMetadata[],
|
||||
nativeShadow: boolean, ngContentIndex: number): TemplateCmd {
|
||||
return beginComponent(name, attrNameAndValues, eventNames, variableNameAndValues,
|
||||
this._mapDirectives(directives), nativeShadow, ngContentIndex,
|
||||
this.componentTemplateFactory(directives[0]));
|
||||
}
|
||||
createEndComponent(): TemplateCmd { return endComponent(); }
|
||||
createEmbeddedTemplate(attrNameAndValues: string[], variableNameAndValues: string[],
|
||||
directives: TypeMetadata[], isMerged: boolean, ngContentIndex: number,
|
||||
children: TemplateCmd[]): TemplateCmd {
|
||||
return embeddedTemplate(attrNameAndValues, variableNameAndValues,
|
||||
this._mapDirectives(directives), isMerged, ngContentIndex, children);
|
||||
}
|
||||
}
|
||||
|
||||
function escapeStringArray(data: string[]): string {
|
||||
return `[${data.map( value => escapeSingleQuoteString(value)).join(',')}]`;
|
||||
}
|
||||
|
||||
class CodegenCommandFactory implements CommandFactory<string> {
|
||||
constructor(public componentTemplateFactory: Function, public templateCommandsModuleAlias,
|
||||
public imports: string[][]) {}
|
||||
|
||||
private _escapeDirectives(directives: TypeMetadata[]): string[] {
|
||||
return directives.map(directiveType => {
|
||||
var importAlias = `dir${this.imports.length}`;
|
||||
this.imports.push([directiveType.typeUrl, importAlias]);
|
||||
return `${importAlias}.${directiveType.typeName}`;
|
||||
});
|
||||
}
|
||||
|
||||
createText(value: string, isBound: boolean, ngContentIndex: number): string {
|
||||
return `${this.templateCommandsModuleAlias}.text(${escapeSingleQuoteString(value)}, ${isBound}, ${ngContentIndex})`;
|
||||
}
|
||||
createNgContent(ngContentIndex: number): string {
|
||||
return `${this.templateCommandsModuleAlias}.ngContent(${ngContentIndex})`;
|
||||
}
|
||||
createBeginElement(name: string, attrNameAndValues: string[], eventNames: string[],
|
||||
variableNameAndValues: string[], directives: TypeMetadata[], isBound: boolean,
|
||||
ngContentIndex: number): string {
|
||||
return `${this.templateCommandsModuleAlias}.beginElement(${escapeSingleQuoteString(name)}, ${escapeStringArray(attrNameAndValues)}, ${escapeStringArray(eventNames)}, ${escapeStringArray(variableNameAndValues)}, [${this._escapeDirectives(directives).join(',')}], ${isBound}, ${ngContentIndex})`;
|
||||
}
|
||||
createEndElement(): string { return `${this.templateCommandsModuleAlias}.endElement()`; }
|
||||
createBeginComponent(name: string, attrNameAndValues: string[], eventNames: string[],
|
||||
variableNameAndValues: string[], directives: TypeMetadata[],
|
||||
nativeShadow: boolean, ngContentIndex: number): string {
|
||||
return `${this.templateCommandsModuleAlias}.beginComponent(${escapeSingleQuoteString(name)}, ${escapeStringArray(attrNameAndValues)}, ${escapeStringArray(eventNames)}, ${escapeStringArray(variableNameAndValues)}, [${this._escapeDirectives(directives).join(',')}], ${nativeShadow}, ${ngContentIndex}, ${this.componentTemplateFactory(directives[0], this.imports)})`;
|
||||
}
|
||||
createEndComponent(): string { return `${this.templateCommandsModuleAlias}.endComponent()`; }
|
||||
createEmbeddedTemplate(attrNameAndValues: string[], variableNameAndValues: string[],
|
||||
directives: TypeMetadata[], isMerged: boolean, ngContentIndex: number,
|
||||
children: string[]): string {
|
||||
return `${this.templateCommandsModuleAlias}.embeddedTemplate(${escapeStringArray(attrNameAndValues)}, ${escapeStringArray(variableNameAndValues)}, [${this._escapeDirectives(directives).join(',')}], ${isMerged}, ${ngContentIndex}, [${children.join(',')}])`;
|
||||
}
|
||||
}
|
||||
|
||||
function visitAndReturnContext(visitor: TemplateAstVisitor, asts: TemplateAst[], context: any):
|
||||
any {
|
||||
templateVisitAll(visitor, asts, context);
|
||||
return context;
|
||||
}
|
||||
|
||||
class CommandBuilderVisitor<R> implements TemplateAstVisitor {
|
||||
result: R[] = [];
|
||||
transitiveNgContentCount: number = 0;
|
||||
constructor(public commandFactory: CommandFactory<R>, public component: DirectiveMetadata) {}
|
||||
|
||||
private _readAttrNameAndValues(localComponent: DirectiveMetadata,
|
||||
attrAsts: TemplateAst[]): string[] {
|
||||
var attrNameAndValues: string[] = visitAndReturnContext(this, attrAsts, []);
|
||||
if (isPresent(localComponent) &&
|
||||
localComponent.template.encapsulation === ViewEncapsulation.Emulated) {
|
||||
attrNameAndValues.push(shimHostAttribute(localComponent.type));
|
||||
attrNameAndValues.push('');
|
||||
}
|
||||
if (this.component.template.encapsulation === ViewEncapsulation.Emulated) {
|
||||
attrNameAndValues.push(shimContentAttribute(this.component.type));
|
||||
attrNameAndValues.push('');
|
||||
}
|
||||
return attrNameAndValues;
|
||||
}
|
||||
|
||||
visitNgContent(ast: NgContentAst, context: any): any {
|
||||
this.transitiveNgContentCount++;
|
||||
this.result.push(this.commandFactory.createNgContent(ast.ngContentIndex));
|
||||
return null;
|
||||
}
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
|
||||
var childVisitor = new CommandBuilderVisitor(this.commandFactory, this.component);
|
||||
templateVisitAll(childVisitor, ast.children);
|
||||
var isMerged = childVisitor.transitiveNgContentCount > 0;
|
||||
this.transitiveNgContentCount += childVisitor.transitiveNgContentCount;
|
||||
var directivesAndEventNames = visitAndReturnContext(this, ast.directives, [[], []]);
|
||||
this.result.push(this.commandFactory.createEmbeddedTemplate(
|
||||
this._readAttrNameAndValues(null, ast.attrs), visitAndReturnContext(this, ast.vars, []),
|
||||
directivesAndEventNames[0], isMerged, ast.ngContentIndex, childVisitor.result));
|
||||
return null;
|
||||
}
|
||||
visitElement(ast: ElementAst, context: any): any {
|
||||
var component = ast.getComponent();
|
||||
var eventNames = visitAndReturnContext(this, ast.events, []);
|
||||
var directives = [];
|
||||
visitAndReturnContext(this, ast.directives, [directives, eventNames]);
|
||||
var attrNameAndValues = this._readAttrNameAndValues(component, ast.attrs);
|
||||
var vars = visitAndReturnContext(this, ast.vars, []);
|
||||
if (isPresent(component)) {
|
||||
this.result.push(this.commandFactory.createBeginComponent(
|
||||
ast.name, attrNameAndValues, eventNames, vars, directives,
|
||||
component.template.encapsulation === ViewEncapsulation.Native, ast.ngContentIndex));
|
||||
templateVisitAll(this, ast.children);
|
||||
this.result.push(this.commandFactory.createEndComponent());
|
||||
} else {
|
||||
this.result.push(this.commandFactory.createBeginElement(ast.name, attrNameAndValues,
|
||||
eventNames, vars, directives,
|
||||
ast.isBound(), ast.ngContentIndex));
|
||||
templateVisitAll(this, ast.children);
|
||||
this.result.push(this.commandFactory.createEndElement());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
visitVariable(ast: VariableAst, variableNameAndValues: string[]): any {
|
||||
variableNameAndValues.push(ast.name);
|
||||
variableNameAndValues.push(ast.value);
|
||||
return null;
|
||||
}
|
||||
visitAttr(ast: AttrAst, attrNameAndValues: string[]): any {
|
||||
attrNameAndValues.push(ast.name);
|
||||
attrNameAndValues.push(ast.value);
|
||||
return null;
|
||||
}
|
||||
visitBoundText(ast: BoundTextAst, context: any): any {
|
||||
this.result.push(this.commandFactory.createText(null, true, ast.ngContentIndex));
|
||||
return null;
|
||||
}
|
||||
visitText(ast: TextAst, context: any): any {
|
||||
this.result.push(this.commandFactory.createText(ast.value, false, ast.ngContentIndex));
|
||||
return null;
|
||||
}
|
||||
visitDirective(ast: DirectiveAst, directivesAndEventNames: any[][]): any {
|
||||
directivesAndEventNames[0].push(ast.directive.type);
|
||||
templateVisitAll(this, ast.hostEvents, directivesAndEventNames[1]);
|
||||
return null;
|
||||
}
|
||||
visitEvent(ast: BoundEventAst, eventNames: string[]): any {
|
||||
eventNames.push(ast.getFullName());
|
||||
return null;
|
||||
}
|
||||
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any { return null; }
|
||||
visitElementProperty(ast: BoundElementPropertyAst, context: any): any { return null; }
|
||||
}
|
@ -1,16 +1,17 @@
|
||||
import {DirectiveMetadata, SourceModule, ViewEncapsulation} from './api';
|
||||
import {DirectiveMetadata, SourceModule, TypeMetadata} from './api';
|
||||
import {ViewEncapsulation} from 'angular2/src/core/render/api';
|
||||
import {XHR} from 'angular2/src/core/render/xhr';
|
||||
import {StringWrapper, isJsObject, isBlank} from 'angular2/src/core/facade/lang';
|
||||
import {PromiseWrapper, Promise} from 'angular2/src/core/facade/async';
|
||||
import {ShadowCss} from 'angular2/src/core/render/dom/compiler/shadow_css';
|
||||
import {UrlResolver} from 'angular2/src/core/services/url_resolver';
|
||||
import {resolveStyleUrls} from './style_url_resolver';
|
||||
import {escapeSingleQuoteString} from './util';
|
||||
|
||||
const COMPONENT_VARIABLE = '%COMP%';
|
||||
var COMPONENT_REGEX = /%COMP%/g;
|
||||
const HOST_ATTR = `_nghost-${COMPONENT_VARIABLE}`;
|
||||
const CONTENT_ATTR = `_ngcontent-${COMPONENT_VARIABLE}`;
|
||||
var ESCAPE_STRING_RE = /'|\\|\n/g;
|
||||
var IS_DART = !isJsObject({});
|
||||
|
||||
export class StyleCompiler {
|
||||
@ -78,7 +79,7 @@ export class StyleCompiler {
|
||||
var imports: string[][] = [];
|
||||
var moduleSource = `var STYLES = (`;
|
||||
moduleSource +=
|
||||
`[${plainStyles.map( plainStyle => escapeString(this._shimIfNeeded(plainStyle, shim)) ).join(',')}]`;
|
||||
`[${plainStyles.map( plainStyle => escapeSingleQuoteString(this._shimIfNeeded(plainStyle, shim)) ).join(',')}]`;
|
||||
for (var i = 0; i < absUrls.length; i++) {
|
||||
var url = absUrls[i];
|
||||
var moduleAlias = `import${i}`;
|
||||
@ -98,15 +99,12 @@ export class StyleCompiler {
|
||||
}
|
||||
}
|
||||
|
||||
function escapeString(input: string): string {
|
||||
var escapedInput = StringWrapper.replaceAllMapped(input, ESCAPE_STRING_RE, (match) => {
|
||||
if (match[0] == "'" || match[0] == '\\') {
|
||||
return `\\${match[0]}`;
|
||||
} else {
|
||||
return '\\n';
|
||||
}
|
||||
});
|
||||
return `'${escapedInput}'`;
|
||||
export function shimContentAttribute(component: TypeMetadata): string {
|
||||
return StringWrapper.replaceAll(CONTENT_ATTR, COMPONENT_REGEX, `${component.id}`);
|
||||
}
|
||||
|
||||
export function shimHostAttribute(component: TypeMetadata): string {
|
||||
return StringWrapper.replaceAll(HOST_ATTR, COMPONENT_REGEX, `${component.id}`);
|
||||
}
|
||||
|
||||
function codeGenConcatArray(expression: string): string {
|
||||
|
@ -8,12 +8,12 @@ export interface TemplateAst {
|
||||
}
|
||||
|
||||
export class TextAst implements TemplateAst {
|
||||
constructor(public value: string, public sourceInfo: string) {}
|
||||
constructor(public value: string, public ngContentIndex: number, public sourceInfo: string) {}
|
||||
visit(visitor: TemplateAstVisitor, context: any): any { return visitor.visitText(this, context); }
|
||||
}
|
||||
|
||||
export class BoundTextAst implements TemplateAst {
|
||||
constructor(public value: AST, public sourceInfo: string) {}
|
||||
constructor(public value: AST, public ngContentIndex: number, public sourceInfo: string) {}
|
||||
visit(visitor: TemplateAstVisitor, context: any): any {
|
||||
return visitor.visitBoundText(this, context);
|
||||
}
|
||||
@ -38,6 +38,13 @@ export class BoundEventAst implements TemplateAst {
|
||||
visit(visitor: TemplateAstVisitor, context: any): any {
|
||||
return visitor.visitEvent(this, context);
|
||||
}
|
||||
getFullName(): string {
|
||||
if (isPresent(this.target)) {
|
||||
return `${this.target}:${this.name}`;
|
||||
} else {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class VariableAst implements TemplateAst {
|
||||
@ -48,9 +55,10 @@ export class VariableAst implements TemplateAst {
|
||||
}
|
||||
|
||||
export class ElementAst implements TemplateAst {
|
||||
constructor(public attrs: AttrAst[], public properties: BoundElementPropertyAst[],
|
||||
public events: BoundEventAst[], public vars: VariableAst[],
|
||||
public directives: DirectiveAst[], public children: TemplateAst[],
|
||||
constructor(public name: string, public attrs: AttrAst[],
|
||||
public properties: BoundElementPropertyAst[], public events: BoundEventAst[],
|
||||
public vars: VariableAst[], public directives: DirectiveAst[],
|
||||
public children: TemplateAst[], public ngContentIndex: number,
|
||||
public sourceInfo: string) {}
|
||||
visit(visitor: TemplateAstVisitor, context: any): any {
|
||||
return visitor.visitElement(this, context);
|
||||
@ -60,12 +68,18 @@ export class ElementAst implements TemplateAst {
|
||||
return (this.properties.length > 0 || this.events.length > 0 || this.vars.length > 0 ||
|
||||
this.directives.length > 0);
|
||||
}
|
||||
|
||||
getComponent(): DirectiveMetadata {
|
||||
return this.directives.length > 0 && this.directives[0].directive.isComponent ?
|
||||
this.directives[0].directive :
|
||||
null;
|
||||
}
|
||||
}
|
||||
|
||||
export class EmbeddedTemplateAst implements TemplateAst {
|
||||
constructor(public attrs: AttrAst[], public vars: VariableAst[],
|
||||
public directives: DirectiveAst[], public children: TemplateAst[],
|
||||
public sourceInfo: string) {}
|
||||
public ngContentIndex: number, public sourceInfo: string) {}
|
||||
visit(visitor: TemplateAstVisitor, context: any): any {
|
||||
return visitor.visitEmbeddedTemplate(this, context);
|
||||
}
|
||||
@ -89,7 +103,7 @@ export class DirectiveAst implements TemplateAst {
|
||||
}
|
||||
|
||||
export class NgContentAst implements TemplateAst {
|
||||
constructor(public select: string, public sourceInfo: string) {}
|
||||
constructor(public ngContentIndex: number, public sourceInfo: string) {}
|
||||
visit(visitor: TemplateAstVisitor, context: any): any {
|
||||
return visitor.visitNgContent(this, context);
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import {BaseException} from 'angular2/src/core/facade/exceptions';
|
||||
import {Parser, AST, ASTWithSource} from 'angular2/src/core/change_detection/change_detection';
|
||||
import {TemplateBinding} from 'angular2/src/core/change_detection/parser/ast';
|
||||
|
||||
import {DirectiveMetadata} from './api';
|
||||
import {DirectiveMetadata, TemplateMetadata} from './api';
|
||||
import {
|
||||
ElementAst,
|
||||
BoundElementPropertyAst,
|
||||
@ -54,7 +54,6 @@ import {dashCaseToCamelCase, camelCaseToDashCase} from './util';
|
||||
var BIND_NAME_REGEXP =
|
||||
/^(?:(?:(?:(bind-)|(var-|#)|(on-)|(bindon-))(.+))|\[\(([^\)]+)\)\]|\[([^\]]+)\]|\(([^\)]+)\))$/g;
|
||||
|
||||
const NG_CONTENT_SELECT_ATTR = 'select';
|
||||
const NG_CONTENT_ELEMENT = 'ng-content';
|
||||
const TEMPLATE_ELEMENT = 'template';
|
||||
const TEMPLATE_ATTR = 'template';
|
||||
@ -67,12 +66,14 @@ const ATTRIBUTE_PREFIX = 'attr';
|
||||
const CLASS_PREFIX = 'class';
|
||||
const STYLE_PREFIX = 'style';
|
||||
|
||||
var TEXT_CSS_SELECTOR = CssSelector.parse('*')[0];
|
||||
|
||||
export class TemplateParser {
|
||||
constructor(private _exprParser: Parser, private _schemaRegistry: ElementSchemaRegistry) {}
|
||||
|
||||
parse(domNodes: HtmlAst[], directives: DirectiveMetadata[]): TemplateAst[] {
|
||||
var parseVisitor = new TemplateParseVisitor(directives, this._exprParser, this._schemaRegistry);
|
||||
var result = htmlVisitAll(parseVisitor, domNodes);
|
||||
var result = htmlVisitAll(parseVisitor, domNodes, EMPTY_COMPONENT);
|
||||
if (parseVisitor.errors.length > 0) {
|
||||
var errorString = parseVisitor.errors.join('\n');
|
||||
throw new BaseException(`Template parse errors:\n${errorString}`);
|
||||
@ -131,18 +132,21 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
visitText(ast: HtmlTextAst): any {
|
||||
visitText(ast: HtmlTextAst, component: Component): any {
|
||||
var ngContentIndex = component.findNgContentIndex(TEXT_CSS_SELECTOR);
|
||||
var expr = this._parseInterpolation(ast.value, ast.sourceInfo);
|
||||
if (isPresent(expr)) {
|
||||
return new BoundTextAst(expr, ast.sourceInfo);
|
||||
return new BoundTextAst(expr, ngContentIndex, ast.sourceInfo);
|
||||
} else {
|
||||
return new TextAst(ast.value, ast.sourceInfo);
|
||||
return new TextAst(ast.value, ngContentIndex, ast.sourceInfo);
|
||||
}
|
||||
}
|
||||
|
||||
visitAttr(ast: HtmlAttrAst): any { return new AttrAst(ast.name, ast.value, ast.sourceInfo); }
|
||||
visitAttr(ast: HtmlAttrAst, contex: any): any {
|
||||
return new AttrAst(ast.name, ast.value, ast.sourceInfo);
|
||||
}
|
||||
|
||||
visitElement(element: HtmlElementAst): any {
|
||||
visitElement(element: HtmlElementAst, component: Component): any {
|
||||
var nodeName = element.name;
|
||||
var matchableAttrs: string[][] = [];
|
||||
var elementOrDirectiveProps: BoundElementOrDirectiveProperty[] = [];
|
||||
@ -154,52 +158,53 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||
var templateMatchableAttrs: string[][] = [];
|
||||
var hasInlineTemplates = false;
|
||||
var attrs = [];
|
||||
var selectAttr = null;
|
||||
element.attrs.forEach(attr => {
|
||||
matchableAttrs.push([attr.name, attr.value]);
|
||||
if (attr.name == NG_CONTENT_SELECT_ATTR) {
|
||||
selectAttr = attr.value;
|
||||
}
|
||||
var hasBinding = this._parseAttr(attr, matchableAttrs, elementOrDirectiveProps, events, vars);
|
||||
var hasTemplateBinding = this._parseInlineTemplateBinding(
|
||||
attr, templateMatchableAttrs, templateElementOrDirectiveProps, templateVars);
|
||||
if (!hasBinding && !hasTemplateBinding) {
|
||||
// don't include the bindings as attributes as well in the AST
|
||||
attrs.push(this.visitAttr(attr));
|
||||
attrs.push(this.visitAttr(attr, null));
|
||||
}
|
||||
if (hasTemplateBinding) {
|
||||
hasInlineTemplates = true;
|
||||
}
|
||||
});
|
||||
var elementCssSelector = this._createElementCssSelector(nodeName, matchableAttrs);
|
||||
var directives = this._createDirectiveAsts(
|
||||
element.name, this._parseDirectives(this.selectorMatcher, nodeName, matchableAttrs),
|
||||
element.name, this._parseDirectives(this.selectorMatcher, elementCssSelector),
|
||||
elementOrDirectiveProps, element.sourceInfo);
|
||||
var elementProps: BoundElementPropertyAst[] =
|
||||
this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directives);
|
||||
var children = htmlVisitAll(this, element.children);
|
||||
var children = htmlVisitAll(this, element.children, Component.create(directives));
|
||||
var elementNgContentIndex =
|
||||
hasInlineTemplates ? null : component.findNgContentIndex(elementCssSelector);
|
||||
var parsedElement;
|
||||
if (nodeName == NG_CONTENT_ELEMENT) {
|
||||
parsedElement = new NgContentAst(selectAttr, element.sourceInfo);
|
||||
parsedElement = new NgContentAst(elementNgContentIndex, element.sourceInfo);
|
||||
} else if (nodeName == TEMPLATE_ELEMENT) {
|
||||
this._assertNoComponentsNorElementBindingsOnTemplate(directives, elementProps, events,
|
||||
element.sourceInfo);
|
||||
parsedElement =
|
||||
new EmbeddedTemplateAst(attrs, vars, directives, children, element.sourceInfo);
|
||||
parsedElement = new EmbeddedTemplateAst(attrs, vars, directives, children,
|
||||
elementNgContentIndex, element.sourceInfo);
|
||||
} else {
|
||||
this._assertOnlyOneComponent(directives, element.sourceInfo);
|
||||
parsedElement = new ElementAst(attrs, elementProps, events, vars, directives, children,
|
||||
element.sourceInfo);
|
||||
parsedElement = new ElementAst(nodeName, attrs, elementProps, events, vars, directives,
|
||||
children, elementNgContentIndex, element.sourceInfo);
|
||||
}
|
||||
if (hasInlineTemplates) {
|
||||
var templateCssSelector =
|
||||
this._createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs);
|
||||
var templateDirectives = this._createDirectiveAsts(
|
||||
element.name,
|
||||
this._parseDirectives(this.selectorMatcher, TEMPLATE_ELEMENT, templateMatchableAttrs),
|
||||
element.name, this._parseDirectives(this.selectorMatcher, templateCssSelector),
|
||||
templateElementOrDirectiveProps, element.sourceInfo);
|
||||
var templateElementProps: BoundElementPropertyAst[] = this._createElementPropertyAsts(
|
||||
element.name, templateElementOrDirectiveProps, templateDirectives);
|
||||
this._assertNoComponentsNorElementBindingsOnTemplate(templateDirectives, templateElementProps,
|
||||
[], element.sourceInfo);
|
||||
parsedElement = new EmbeddedTemplateAst([], templateVars, templateDirectives, [parsedElement],
|
||||
component.findNgContentIndex(templateCssSelector),
|
||||
element.sourceInfo);
|
||||
}
|
||||
return parsedElement;
|
||||
@ -349,8 +354,7 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||
sourceInfo));
|
||||
}
|
||||
|
||||
private _parseDirectives(selectorMatcher: SelectorMatcher, elementName: string,
|
||||
matchableAttrs: string[][]): DirectiveMetadata[] {
|
||||
private _createElementCssSelector(elementName: string, matchableAttrs: string[][]): CssSelector {
|
||||
var cssSelector = new CssSelector();
|
||||
|
||||
cssSelector.setElement(elementName);
|
||||
@ -363,8 +367,14 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||
classes.forEach(className => cssSelector.addClassName(className));
|
||||
}
|
||||
}
|
||||
return cssSelector;
|
||||
}
|
||||
|
||||
private _parseDirectives(selectorMatcher: SelectorMatcher,
|
||||
elementCssSelector: CssSelector): DirectiveMetadata[] {
|
||||
var directives = [];
|
||||
selectorMatcher.match(cssSelector, (selector, directive) => { directives.push(directive); });
|
||||
selectorMatcher.match(elementCssSelector,
|
||||
(selector, directive) => { directives.push(directive); });
|
||||
// Need to sort the directives so that we get consistent results throughout,
|
||||
// as selectorMatcher uses Maps inside.
|
||||
// Also need to make components the first directive in the array
|
||||
@ -516,9 +526,10 @@ class TemplateParseVisitor implements HtmlAstVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
_assertNoComponentsNorElementBindingsOnTemplate(directives: DirectiveAst[],
|
||||
elementProps: BoundElementPropertyAst[],
|
||||
events: BoundEventAst[], sourceInfo: string) {
|
||||
private _assertNoComponentsNorElementBindingsOnTemplate(directives: DirectiveAst[],
|
||||
elementProps: BoundElementPropertyAst[],
|
||||
events: BoundEventAst[],
|
||||
sourceInfo: string) {
|
||||
var componentTypeNames: string[] = this._findComponentDirectiveNames(directives);
|
||||
if (componentTypeNames.length > 0) {
|
||||
this._reportError(
|
||||
@ -555,4 +566,39 @@ export function splitAtColon(input: string, defaultValues: string[]): string[] {
|
||||
} else {
|
||||
return defaultValues;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Component {
|
||||
static create(directives: DirectiveAst[]): Component {
|
||||
if (directives.length === 0 || !directives[0].directive.isComponent) {
|
||||
return EMPTY_COMPONENT;
|
||||
}
|
||||
var matcher = new SelectorMatcher();
|
||||
var ngContentSelectors = directives[0].directive.template.ngContentSelectors;
|
||||
var wildcardNgContentIndex = null;
|
||||
for (var i = 0; i < ngContentSelectors.length; i++) {
|
||||
var selector = ngContentSelectors[i];
|
||||
if (StringWrapper.equals(selector, '*')) {
|
||||
wildcardNgContentIndex = i;
|
||||
} else {
|
||||
matcher.addSelectables(CssSelector.parse(ngContentSelectors[i]), i);
|
||||
}
|
||||
}
|
||||
return new Component(matcher, wildcardNgContentIndex);
|
||||
}
|
||||
constructor(public ngContentIndexMatcher: SelectorMatcher,
|
||||
public wildcardNgContentIndex: number) {}
|
||||
|
||||
findNgContentIndex(selector: CssSelector): number {
|
||||
var ngContentIndices = [];
|
||||
if (isPresent(this.wildcardNgContentIndex)) {
|
||||
ngContentIndices.push(this.wildcardNgContentIndex);
|
||||
}
|
||||
this.ngContentIndexMatcher.match(
|
||||
selector, (selector, ngContentIndex) => { ngContentIndices.push(ngContentIndex); });
|
||||
ListWrapper.sort(ngContentIndices);
|
||||
return ngContentIndices.length > 0 ? ngContentIndices[0] : null;
|
||||
}
|
||||
}
|
||||
|
||||
var EMPTY_COMPONENT = new Component(new SelectorMatcher(), null);
|
||||
|
@ -1,8 +1,9 @@
|
||||
import {StringWrapper} from 'angular2/src/core/facade/lang';
|
||||
import {StringWrapper, isBlank} from 'angular2/src/core/facade/lang';
|
||||
|
||||
var CAMEL_CASE_REGEXP = /([A-Z])/g;
|
||||
var DASH_CASE_REGEXP = /-([a-z])/g;
|
||||
|
||||
var SINGLE_QUOTE_ESCAPE_STRING_RE = /'|\\|\n/g;
|
||||
var DOUBLE_QUOTE_ESCAPE_STRING_RE = /"|\\|\n/g;
|
||||
|
||||
export function camelCaseToDashCase(input: string): string {
|
||||
return StringWrapper.replaceAllMapped(input, CAMEL_CASE_REGEXP,
|
||||
@ -13,3 +14,27 @@ export function dashCaseToCamelCase(input: string): string {
|
||||
return StringWrapper.replaceAllMapped(input, DASH_CASE_REGEXP,
|
||||
(m) => { return m[1].toUpperCase(); });
|
||||
}
|
||||
|
||||
export function escapeSingleQuoteString(input: string): string {
|
||||
if (isBlank(input)) {
|
||||
return null;
|
||||
}
|
||||
return `'${escapeString(input, SINGLE_QUOTE_ESCAPE_STRING_RE)}'`;
|
||||
}
|
||||
|
||||
export function escapeDoubleQuoteString(input: string): string {
|
||||
if (isBlank(input)) {
|
||||
return null;
|
||||
}
|
||||
return `"${escapeString(input, DOUBLE_QUOTE_ESCAPE_STRING_RE)}"`;
|
||||
}
|
||||
|
||||
function escapeString(input: string, re: RegExp): string {
|
||||
return StringWrapper.replaceAllMapped(input, re, (match) => {
|
||||
if (match[0] == '\n') {
|
||||
return '\\n';
|
||||
} else {
|
||||
return `\\${match[0]}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user