feat(core): speed up view creation via code gen for view factories.
BREAKING CHANGE: - Platform pipes can only contain types and arrays of types, but no bindings any more. - When using transformers, platform pipes need to be specified explicitly in the pubspec.yaml via the new config option `platform_pipes`. - `Compiler.compileInHost` now returns a `HostViewFactoryRef` - Component view is not yet created when component constructor is called. -> use `onInit` lifecycle callback to access the view of a component - `ViewRef#setLocal` has been moved to new type `EmbeddedViewRef` - `internalView` is gone, use `EmbeddedViewRef.rootNodes` to access the root nodes of an embedded view - `renderer.setElementProperty`, `..setElementStyle`, `..setElementAttribute` now take a native element instead of an ElementRef - `Renderer` interface now operates on plain native nodes, instead of `RenderElementRef`s or `RenderViewRef`s Closes #5993
This commit is contained in:
@@ -1,196 +1,54 @@
|
||||
import {unimplemented} from 'angular2/src/facade/exceptions';
|
||||
import {Map} from 'angular2/src/facade/collection';
|
||||
import {ViewEncapsulation} from 'angular2/src/core/metadata';
|
||||
import {ViewEncapsulation} from 'angular2/src/core/metadata/view';
|
||||
|
||||
/**
|
||||
* Represents an Angular ProtoView in the Rendering Context.
|
||||
*
|
||||
* When you implement a custom {@link Renderer}, `RenderProtoViewRef` specifies what Render View
|
||||
* your renderer should create.
|
||||
*
|
||||
* `RenderProtoViewRef` is a counterpart to {@link ProtoViewRef} available in the Application
|
||||
* Context. But unlike `ProtoViewRef`, `RenderProtoViewRef` contains all static nested Proto Views
|
||||
* that are recursively merged into a single Render Proto View.
|
||||
|
||||
*
|
||||
* <!-- TODO: this is created by Renderer#createProtoView in the new compiler -->
|
||||
*/
|
||||
export class RenderProtoViewRef {}
|
||||
|
||||
/**
|
||||
* Represents a list of sibling Nodes that can be moved by the {@link Renderer} independently of
|
||||
* other Render Fragments.
|
||||
*
|
||||
* Any {@link RenderViewRef} has one Render Fragment.
|
||||
*
|
||||
* Additionally any View with an Embedded View that contains a {@link NgContentAst View Projection}
|
||||
* results in additional Render Fragment.
|
||||
*/
|
||||
/*
|
||||
<div>foo</div>
|
||||
{{bar}}
|
||||
|
||||
|
||||
<div>foo</div> -> view 1 / fragment 1
|
||||
<ul>
|
||||
<template ngFor>
|
||||
<li>{{fg}}</li> -> view 2 / fragment 1
|
||||
</template>
|
||||
</ul>
|
||||
{{bar}}
|
||||
|
||||
|
||||
<div>foo</div> -> view 1 / fragment 1
|
||||
<ul>
|
||||
<template ngIf>
|
||||
<li><ng-content></></li> -> view 1 / fragment 2
|
||||
</template>
|
||||
<template ngFor>
|
||||
<li><ng-content></></li> ->
|
||||
<li></li> -> view 1 / fragment 2 + view 2 / fragment 1..n-1
|
||||
</template>
|
||||
</ul>
|
||||
{{bar}}
|
||||
*/
|
||||
// TODO(i): refactor into an interface
|
||||
export class RenderFragmentRef {}
|
||||
|
||||
|
||||
/**
|
||||
* Represents an Angular View in the Rendering Context.
|
||||
*
|
||||
* `RenderViewRef` specifies to the {@link Renderer} what View to update or destroy.
|
||||
*
|
||||
* Unlike a {@link ViewRef} available in the Application Context, Render View contains all the
|
||||
* static Component Views that have been recursively merged into a single Render View.
|
||||
*
|
||||
* Each `RenderViewRef` contains one or more {@link RenderFragmentRef Render Fragments}, these
|
||||
* Fragments are created, hydrated, dehydrated and destroyed as a single unit together with the
|
||||
* View.
|
||||
*/
|
||||
// TODO(i): refactor into an interface
|
||||
export class RenderViewRef {}
|
||||
|
||||
/**
|
||||
* Abstract base class for commands to the Angular renderer, using the visitor pattern.
|
||||
*/
|
||||
export abstract class RenderTemplateCmd {
|
||||
abstract visit(visitor: RenderCommandVisitor, context: any): any;
|
||||
export class RenderComponentType {
|
||||
constructor(public id: string, public encapsulation: ViewEncapsulation,
|
||||
public styles: Array<string | any[]>) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Command to begin rendering.
|
||||
*/
|
||||
export abstract class RenderBeginCmd extends RenderTemplateCmd {
|
||||
get ngContentIndex(): number { return unimplemented(); };
|
||||
get isBound(): boolean { return unimplemented(); };
|
||||
}
|
||||
export interface ParentRenderer { renderComponent(componentType: RenderComponentType): Renderer; }
|
||||
|
||||
/**
|
||||
* Command to render text.
|
||||
*/
|
||||
export abstract class RenderTextCmd extends RenderBeginCmd {
|
||||
get value(): string { return unimplemented(); };
|
||||
}
|
||||
export abstract class Renderer implements ParentRenderer {
|
||||
abstract renderComponent(componentType: RenderComponentType): Renderer;
|
||||
|
||||
/**
|
||||
* Command to render projected content.
|
||||
*/
|
||||
export abstract class RenderNgContentCmd extends RenderTemplateCmd {
|
||||
// The index of this NgContent element
|
||||
get index(): number { return unimplemented(); };
|
||||
// The index of the NgContent element into which this
|
||||
// NgContent element should be projected (if any)
|
||||
get ngContentIndex(): number { return unimplemented(); };
|
||||
}
|
||||
abstract selectRootElement(selector: string): any;
|
||||
|
||||
/**
|
||||
* Command to begin rendering an element.
|
||||
*/
|
||||
export abstract class RenderBeginElementCmd extends RenderBeginCmd {
|
||||
get name(): string { return unimplemented(); };
|
||||
get attrNameAndValues(): string[] { return unimplemented(); };
|
||||
get eventTargetAndNames(): string[] { return unimplemented(); };
|
||||
}
|
||||
abstract createElement(parentElement: any, name: string): any;
|
||||
|
||||
/**
|
||||
* Command to begin rendering a component.
|
||||
*/
|
||||
export abstract class RenderBeginComponentCmd extends RenderBeginElementCmd {
|
||||
get templateId(): string { return unimplemented(); };
|
||||
}
|
||||
abstract createViewRoot(hostElement: any): any;
|
||||
|
||||
/**
|
||||
* Command to render a component's template.
|
||||
*/
|
||||
export abstract class RenderEmbeddedTemplateCmd extends RenderBeginElementCmd {
|
||||
get isMerged(): boolean { return unimplemented(); };
|
||||
get children(): RenderTemplateCmd[] { return unimplemented(); };
|
||||
}
|
||||
abstract createTemplateAnchor(parentElement: any): any;
|
||||
|
||||
/**
|
||||
* Visitor for a {@link RenderTemplateCmd}.
|
||||
*/
|
||||
export interface RenderCommandVisitor {
|
||||
visitText(cmd: RenderTextCmd, context: any): any;
|
||||
visitNgContent(cmd: RenderNgContentCmd, context: any): any;
|
||||
visitBeginElement(cmd: RenderBeginElementCmd, context: any): any;
|
||||
visitEndElement(context: any): any;
|
||||
visitBeginComponent(cmd: RenderBeginComponentCmd, context: any): any;
|
||||
visitEndComponent(context: any): any;
|
||||
visitEmbeddedTemplate(cmd: RenderEmbeddedTemplateCmd, context: any): any;
|
||||
}
|
||||
abstract createText(parentElement: any, value: string): any;
|
||||
|
||||
abstract projectNodes(parentElement: any, nodes: any[]);
|
||||
|
||||
/**
|
||||
* Container class produced by a {@link Renderer} when creating a Render View.
|
||||
*
|
||||
* An instance of `RenderViewWithFragments` contains a {@link RenderViewRef} and an array of
|
||||
* {@link RenderFragmentRef}s belonging to this Render View.
|
||||
*/
|
||||
// TODO(i): refactor this by RenderViewWithFragments and adding fragments directly to RenderViewRef
|
||||
export class RenderViewWithFragments {
|
||||
constructor(
|
||||
/**
|
||||
* Reference to the {@link RenderViewRef}.
|
||||
*/
|
||||
public viewRef: RenderViewRef,
|
||||
/**
|
||||
* Array of {@link RenderFragmentRef}s ordered in the depth-first order.
|
||||
*/
|
||||
public fragmentRefs: RenderFragmentRef[]) {}
|
||||
}
|
||||
abstract attachViewAfter(node: any, viewRootNodes: any[]);
|
||||
|
||||
/**
|
||||
* Represents an Element that is part of a {@link RenderViewRef Render View}.
|
||||
*
|
||||
* `RenderElementRef` is a counterpart to {@link ElementRef} available in the Application Context.
|
||||
*
|
||||
* When using `Renderer` from the Application Context, `ElementRef` can be used instead of
|
||||
* `RenderElementRef`.
|
||||
*/
|
||||
export interface RenderElementRef {
|
||||
/**
|
||||
* Reference to the Render View that contains this Element.
|
||||
*/
|
||||
renderView: RenderViewRef;
|
||||
abstract detachView(viewRootNodes: any[]);
|
||||
|
||||
abstract destroyView(hostElement: any, viewAllNodes: any[]);
|
||||
|
||||
abstract listen(renderElement: any, name: string, callback: Function);
|
||||
|
||||
abstract listenGlobal(target: string, name: string, callback: Function): Function;
|
||||
|
||||
abstract setElementProperty(renderElement: any, propertyName: string, propertyValue: any);
|
||||
|
||||
abstract setElementAttribute(renderElement: any, attributeName: string, attributeValue: string);
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* Index of the Element (in the depth-first order) inside the Render View.
|
||||
*
|
||||
* This index is used internally by Angular to locate elements.
|
||||
* Used only in debug mode to serialize property changes to comment nodes,
|
||||
* such as <template> placeholders.
|
||||
*/
|
||||
boundElementIndex: number;
|
||||
}
|
||||
abstract setBindingDebugInfo(renderElement: any, propertyName: string, propertyValue: string);
|
||||
|
||||
/**
|
||||
* Template for rendering a component, including commands and styles.
|
||||
*/
|
||||
export class RenderComponentTemplate {
|
||||
constructor(public id: string, public shortId: string, public encapsulation: ViewEncapsulation,
|
||||
public commands: RenderTemplateCmd[], public styles: string[]) {}
|
||||
abstract setElementClass(renderElement: any, className: string, isAdd: boolean);
|
||||
|
||||
abstract setElementStyle(renderElement: any, styleName: string, styleValue: string);
|
||||
|
||||
abstract invokeElementMethod(renderElement: any, methodName: string, args: any[]);
|
||||
|
||||
abstract setText(renderNode: any, text: string);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -205,184 +63,7 @@ export class RenderComponentTemplate {
|
||||
*
|
||||
* The default Renderer implementation is `DomRenderer`. Also available is `WebWorkerRenderer`.
|
||||
*/
|
||||
export abstract class Renderer {
|
||||
/**
|
||||
* Registers a component template represented as arrays of {@link RenderTemplateCmd}s and styles
|
||||
* with the Renderer.
|
||||
*
|
||||
* Once a template is registered it can be referenced via {@link RenderBeginComponentCmd} when
|
||||
* {@link #createProtoView creating Render ProtoView}.
|
||||
*/
|
||||
abstract registerComponentTemplate(template: RenderComponentTemplate);
|
||||
|
||||
/**
|
||||
* Creates a {@link RenderProtoViewRef} from an array of {@link RenderTemplateCmd}`s.
|
||||
*/
|
||||
abstract createProtoView(componentTemplateId: string,
|
||||
cmds: RenderTemplateCmd[]): RenderProtoViewRef;
|
||||
|
||||
/**
|
||||
* Creates a Root Host View based on the provided `hostProtoViewRef`.
|
||||
*
|
||||
* `fragmentCount` is the number of nested {@link RenderFragmentRef}s in this View. This parameter
|
||||
* is non-optional so that the renderer can create a result synchronously even when application
|
||||
* runs in a different context (e.g. in a Web Worker).
|
||||
*
|
||||
* `hostElementSelector` is a (CSS) selector for querying the main document to find the Host
|
||||
* Element. The newly created Root Host View should be attached to this element.
|
||||
*
|
||||
* Returns an instance of {@link RenderViewWithFragments}, representing the Render View.
|
||||
*/
|
||||
abstract createRootHostView(hostProtoViewRef: RenderProtoViewRef, fragmentCount: number,
|
||||
hostElementSelector: string): RenderViewWithFragments;
|
||||
|
||||
/**
|
||||
* Creates a Render View based on the provided `protoViewRef`.
|
||||
*
|
||||
* `fragmentCount` is the number of nested {@link RenderFragmentRef}s in this View. This parameter
|
||||
* is non-optional so that the renderer can create a result synchronously even when application
|
||||
* runs in a different context (e.g. in a Web Worker).
|
||||
*
|
||||
* Returns an instance of {@link RenderViewWithFragments}, representing the Render View.
|
||||
*/
|
||||
abstract createView(protoViewRef: RenderProtoViewRef,
|
||||
fragmentCount: number): RenderViewWithFragments;
|
||||
|
||||
/**
|
||||
* Destroys a Render View specified via `viewRef`.
|
||||
*
|
||||
* This operation should be performed only on a View that has already been dehydrated and
|
||||
* all of its Render Fragments have been detached.
|
||||
*
|
||||
* Destroying a View indicates to the Renderer that this View is not going to be referenced in any
|
||||
* future operations. If the Renderer created any renderer-specific objects for this View, these
|
||||
* objects should now be destroyed to prevent memory leaks.
|
||||
*/
|
||||
abstract destroyView(viewRef: RenderViewRef);
|
||||
|
||||
/**
|
||||
* Attaches the Nodes of a Render Fragment after the last Node of `previousFragmentRef`.
|
||||
*/
|
||||
abstract attachFragmentAfterFragment(previousFragmentRef: RenderFragmentRef,
|
||||
fragmentRef: RenderFragmentRef);
|
||||
|
||||
/**
|
||||
* Attaches the Nodes of the Render Fragment after an Element.
|
||||
*/
|
||||
abstract attachFragmentAfterElement(elementRef: RenderElementRef, fragmentRef: RenderFragmentRef);
|
||||
|
||||
/**
|
||||
* Detaches the Nodes of a Render Fragment from their parent.
|
||||
*
|
||||
* This operations should be called only on a View that has been already
|
||||
* {@link #dehydrateView dehydrated}.
|
||||
*/
|
||||
abstract detachFragment(fragmentRef: RenderFragmentRef);
|
||||
|
||||
/**
|
||||
* Notifies a custom Renderer to initialize a Render View.
|
||||
*
|
||||
* This method is called by Angular after a Render View has been created, or when a previously
|
||||
* dehydrated Render View is about to be reused.
|
||||
*/
|
||||
abstract hydrateView(viewRef: RenderViewRef);
|
||||
|
||||
/**
|
||||
* Notifies a custom Renderer that a Render View is no longer active.
|
||||
*
|
||||
* This method is called by Angular before a Render View will be destroyed, or when a hydrated
|
||||
* Render View is about to be put into a pool for future reuse.
|
||||
*/
|
||||
abstract dehydrateView(viewRef: RenderViewRef);
|
||||
|
||||
/**
|
||||
* Returns the underlying native element at the specified `location`, or `null` if direct access
|
||||
* to native elements is not supported (e.g. when the application runs in a web worker).
|
||||
*
|
||||
* <div class="callout is-critical">
|
||||
* <header>Use with caution</header>
|
||||
* <p>
|
||||
* Use this api as the last resort when direct access to DOM is needed. Use templating and
|
||||
* data-binding, or other {@link Renderer} methods instead.
|
||||
* </p>
|
||||
* <p>
|
||||
* Relying on direct DOM access creates tight coupling between your application and rendering
|
||||
* layers which will make it impossible to separate the two and deploy your application into a
|
||||
* web worker.
|
||||
* </p>
|
||||
* </div>
|
||||
*/
|
||||
abstract getNativeElementSync(location: RenderElementRef): any;
|
||||
|
||||
/**
|
||||
* Sets a property on the Element specified via `location`.
|
||||
*/
|
||||
abstract setElementProperty(location: RenderElementRef, propertyName: string, propertyValue: any);
|
||||
|
||||
/**
|
||||
* Sets an attribute on the Element specified via `location`.
|
||||
*
|
||||
* If `attributeValue` is `null`, the attribute is removed.
|
||||
*/
|
||||
abstract setElementAttribute(location: RenderElementRef, attributeName: string,
|
||||
attributeValue: string);
|
||||
|
||||
abstract setBindingDebugInfo(location: RenderElementRef, propertyName: string,
|
||||
propertyValue: string);
|
||||
|
||||
/**
|
||||
* Sets a (CSS) class on the Element specified via `location`.
|
||||
*
|
||||
* `isAdd` specifies if the class should be added or removed.
|
||||
*/
|
||||
abstract setElementClass(location: RenderElementRef, className: string, isAdd: boolean);
|
||||
|
||||
/**
|
||||
* Sets a (CSS) inline style on the Element specified via `location`.
|
||||
*
|
||||
* If `styleValue` is `null`, the style is removed.
|
||||
*/
|
||||
abstract setElementStyle(location: RenderElementRef, styleName: string, styleValue: string);
|
||||
|
||||
/**
|
||||
* Calls a method on the Element specified via `location`.
|
||||
*/
|
||||
abstract invokeElementMethod(location: RenderElementRef, methodName: string, args: any[]);
|
||||
|
||||
/**
|
||||
* Sets the value of an interpolated TextNode at the specified index to the `text` value.
|
||||
*
|
||||
* `textNodeIndex` is the depth-first index of the Node among interpolated Nodes in the Render
|
||||
* View.
|
||||
*/
|
||||
abstract setText(viewRef: RenderViewRef, textNodeIndex: number, text: string);
|
||||
|
||||
/**
|
||||
* Sets a dispatcher to relay all events triggered in the given Render View.
|
||||
*
|
||||
* Each Render View can have only one Event Dispatcher, if this method is called multiple times,
|
||||
* the last provided dispatcher will be used.
|
||||
*/
|
||||
abstract setEventDispatcher(viewRef: RenderViewRef, dispatcher: RenderEventDispatcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* A dispatcher that relays all events that occur in a Render View.
|
||||
*
|
||||
* Use {@link Renderer#setEventDispatcher} to register a dispatcher for a particular Render View.
|
||||
*/
|
||||
export interface RenderEventDispatcher {
|
||||
/**
|
||||
* Called when Event called `eventName` was triggered on an Element with an Event Binding for this
|
||||
* Event.
|
||||
*
|
||||
* `elementIndex` specifies the depth-first index of the Element in the Render View.
|
||||
*
|
||||
* `locals` is a map for local variable to value mapping that should be used when evaluating the
|
||||
* Event Binding expression.
|
||||
*
|
||||
* Returns `false` if `preventDefault` should be called to stop the default behavior of the Event
|
||||
* in the Rendering Context.
|
||||
*/
|
||||
dispatchRenderEvent(elementIndex: number, eventName: string, locals: Map<string, any>): boolean;
|
||||
export abstract class RootRenderer implements ParentRenderer {
|
||||
abstract renderComponent(componentType: RenderComponentType): Renderer;
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
import {BaseException} from 'angular2/src/facade/exceptions';
|
||||
import {ListWrapper, MapWrapper, Map, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
import {isPresent, isBlank, stringify} from 'angular2/src/facade/lang';
|
||||
|
||||
import {
|
||||
RenderComponentTemplate,
|
||||
RenderViewRef,
|
||||
RenderEventDispatcher,
|
||||
RenderTemplateCmd,
|
||||
RenderProtoViewRef,
|
||||
RenderFragmentRef
|
||||
} from './api';
|
||||
|
||||
export class DefaultProtoViewRef extends RenderProtoViewRef {
|
||||
constructor(public template: RenderComponentTemplate, public cmds: RenderTemplateCmd[]) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
export class DefaultRenderFragmentRef<N> extends RenderFragmentRef {
|
||||
constructor(public nodes: N[]) { super(); }
|
||||
}
|
||||
|
||||
export class DefaultRenderView<N> extends RenderViewRef {
|
||||
hydrated: boolean = false;
|
||||
eventDispatcher: RenderEventDispatcher = null;
|
||||
globalEventRemovers: Function[] = null;
|
||||
|
||||
constructor(public fragments: DefaultRenderFragmentRef<N>[], public boundTextNodes: N[],
|
||||
public boundElements: N[], public nativeShadowRoots: N[],
|
||||
public globalEventAdders: Function[], public rootContentInsertionPoints: N[]) {
|
||||
super();
|
||||
}
|
||||
|
||||
hydrate() {
|
||||
if (this.hydrated) throw new BaseException('The view is already hydrated.');
|
||||
this.hydrated = true;
|
||||
this.globalEventRemovers = ListWrapper.createFixedSize(this.globalEventAdders.length);
|
||||
for (var i = 0; i < this.globalEventAdders.length; i++) {
|
||||
this.globalEventRemovers[i] = this.globalEventAdders[i]();
|
||||
}
|
||||
}
|
||||
|
||||
dehydrate() {
|
||||
if (!this.hydrated) throw new BaseException('The view is already dehydrated.');
|
||||
for (var i = 0; i < this.globalEventRemovers.length; i++) {
|
||||
this.globalEventRemovers[i]();
|
||||
}
|
||||
this.globalEventRemovers = null;
|
||||
this.hydrated = false;
|
||||
}
|
||||
|
||||
setEventDispatcher(dispatcher: RenderEventDispatcher) { this.eventDispatcher = dispatcher; }
|
||||
|
||||
dispatchRenderEvent(boundElementIndex: number, eventName: string, event: any): boolean {
|
||||
var allowDefaultBehavior = true;
|
||||
if (isPresent(this.eventDispatcher)) {
|
||||
var locals = new Map<string, any>();
|
||||
locals.set('$event', event);
|
||||
allowDefaultBehavior =
|
||||
this.eventDispatcher.dispatchRenderEvent(boundElementIndex, eventName, locals);
|
||||
}
|
||||
return allowDefaultBehavior;
|
||||
}
|
||||
}
|
||||
@@ -1,321 +0,0 @@
|
||||
import {isBlank, isPresent, StringWrapper} from 'angular2/src/facade/lang';
|
||||
import {
|
||||
RenderEventDispatcher,
|
||||
RenderTemplateCmd,
|
||||
RenderCommandVisitor,
|
||||
RenderBeginElementCmd,
|
||||
RenderBeginComponentCmd,
|
||||
RenderNgContentCmd,
|
||||
RenderTextCmd,
|
||||
RenderEmbeddedTemplateCmd,
|
||||
RenderComponentTemplate
|
||||
} from './api';
|
||||
import {DefaultRenderView, DefaultRenderFragmentRef} from './view';
|
||||
import {ViewEncapsulation} from 'angular2/src/core/metadata';
|
||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
|
||||
export function encapsulateStyles(componentTemplate: RenderComponentTemplate): string[] {
|
||||
var processedStyles = componentTemplate.styles;
|
||||
if (componentTemplate.encapsulation === ViewEncapsulation.Emulated) {
|
||||
processedStyles = ListWrapper.createFixedSize(componentTemplate.styles.length);
|
||||
for (var i = 0; i < componentTemplate.styles.length; i++) {
|
||||
processedStyles[i] = StringWrapper.replaceAll(componentTemplate.styles[i], COMPONENT_REGEX,
|
||||
componentTemplate.shortId);
|
||||
}
|
||||
}
|
||||
return processedStyles;
|
||||
}
|
||||
|
||||
export function createRenderView(componentTemplate: RenderComponentTemplate,
|
||||
cmds: RenderTemplateCmd[], inplaceElement: any,
|
||||
nodeFactory: NodeFactory<any>): DefaultRenderView<any> {
|
||||
var view: DefaultRenderView<any>;
|
||||
var eventDispatcher = (boundElementIndex: number, eventName: string, event: any) =>
|
||||
view.dispatchRenderEvent(boundElementIndex, eventName, event);
|
||||
var context = new BuildContext(eventDispatcher, nodeFactory, inplaceElement);
|
||||
context.build(componentTemplate, cmds);
|
||||
var fragments: DefaultRenderFragmentRef<any>[] = [];
|
||||
for (var i = 0; i < context.fragments.length; i++) {
|
||||
fragments.push(new DefaultRenderFragmentRef(context.fragments[i]));
|
||||
}
|
||||
view = new DefaultRenderView<any>(fragments, context.boundTextNodes, context.boundElements,
|
||||
context.nativeShadowRoots, context.globalEventAdders,
|
||||
context.rootContentInsertionPoints);
|
||||
return view;
|
||||
}
|
||||
|
||||
export interface NodeFactory<N> {
|
||||
resolveComponentTemplate(templateId: string): RenderComponentTemplate;
|
||||
createTemplateAnchor(attrNameAndValues: string[]): N;
|
||||
createElement(name: string, attrNameAndValues: string[]): N;
|
||||
createRootContentInsertionPoint(): N;
|
||||
mergeElement(existing: N, attrNameAndValues: string[]);
|
||||
createShadowRoot(host: N, templateId: string): N;
|
||||
createText(value: string): N;
|
||||
appendChild(parent: N, child: N);
|
||||
on(element: N, eventName: string, callback: Function);
|
||||
globalOn(target: string, eventName: string, callback: Function): Function;
|
||||
}
|
||||
|
||||
class BuildContext<N> {
|
||||
constructor(private _eventDispatcher: Function, public factory: NodeFactory<N>,
|
||||
private _inplaceElement: N) {
|
||||
this.isHost = isPresent((_inplaceElement));
|
||||
}
|
||||
private _builders: RenderViewBuilder<N>[] = [];
|
||||
|
||||
globalEventAdders: Function[] = [];
|
||||
boundElements: N[] = [];
|
||||
boundTextNodes: N[] = [];
|
||||
nativeShadowRoots: N[] = [];
|
||||
fragments: N[][] = [];
|
||||
rootContentInsertionPoints: N[] = [];
|
||||
componentCount: number = 0;
|
||||
isHost: boolean;
|
||||
|
||||
build(template: RenderComponentTemplate, cmds: RenderTemplateCmd[]) {
|
||||
this.enqueueRootBuilder(template, cmds);
|
||||
this._build(this._builders[0]);
|
||||
}
|
||||
|
||||
private _build(builder: RenderViewBuilder<N>) {
|
||||
this._builders = [];
|
||||
builder.build(this);
|
||||
var enqueuedBuilders = this._builders;
|
||||
for (var i = 0; i < enqueuedBuilders.length; i++) {
|
||||
this._build(enqueuedBuilders[i]);
|
||||
}
|
||||
}
|
||||
|
||||
enqueueComponentBuilder(component: Component<N>) {
|
||||
this.componentCount++;
|
||||
this._builders.push(
|
||||
new RenderViewBuilder<N>(component, null, component.template, component.template.commands));
|
||||
}
|
||||
|
||||
enqueueFragmentBuilder(parentComponent: Component<N>, parentTemplate: RenderComponentTemplate,
|
||||
commands: RenderTemplateCmd[]) {
|
||||
var rootNodes = [];
|
||||
this.fragments.push(rootNodes);
|
||||
this._builders.push(
|
||||
new RenderViewBuilder<N>(parentComponent, rootNodes, parentTemplate, commands));
|
||||
}
|
||||
|
||||
enqueueRootBuilder(template: RenderComponentTemplate, cmds: RenderTemplateCmd[]) {
|
||||
var rootNodes = [];
|
||||
this.fragments.push(rootNodes);
|
||||
this._builders.push(new RenderViewBuilder<N>(null, rootNodes, template, cmds));
|
||||
}
|
||||
|
||||
consumeInplaceElement(): N {
|
||||
var result = this._inplaceElement;
|
||||
this._inplaceElement = null;
|
||||
return result;
|
||||
}
|
||||
|
||||
addEventListener(boundElementIndex: number, target: string, eventName: string) {
|
||||
if (isPresent(target)) {
|
||||
var handler =
|
||||
createEventHandler(boundElementIndex, `${target}:${eventName}`, this._eventDispatcher);
|
||||
this.globalEventAdders.push(createGlobalEventAdder(target, eventName, handler, this.factory));
|
||||
} else {
|
||||
var handler = createEventHandler(boundElementIndex, eventName, this._eventDispatcher);
|
||||
this.factory.on(this.boundElements[boundElementIndex], eventName, handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function createEventHandler(boundElementIndex: number, eventName: string,
|
||||
eventDispatcher: Function): Function {
|
||||
return ($event) => eventDispatcher(boundElementIndex, eventName, $event);
|
||||
}
|
||||
|
||||
function createGlobalEventAdder(target: string, eventName: string, eventHandler: Function,
|
||||
nodeFactory: NodeFactory<any>): Function {
|
||||
return () => nodeFactory.globalOn(target, eventName, eventHandler);
|
||||
}
|
||||
|
||||
class RenderViewBuilder<N> implements RenderCommandVisitor {
|
||||
parentStack: Array<N | Component<N>>;
|
||||
|
||||
constructor(public parentComponent: Component<N>, public fragmentRootNodes: N[],
|
||||
public template: RenderComponentTemplate, public cmds: RenderTemplateCmd[]) {
|
||||
var rootNodesParent = isPresent(fragmentRootNodes) ? null : parentComponent.shadowRoot;
|
||||
this.parentStack = [rootNodesParent];
|
||||
}
|
||||
|
||||
build(context: BuildContext<N>) {
|
||||
var cmds = this.cmds;
|
||||
for (var i = 0; i < cmds.length; i++) {
|
||||
cmds[i].visit(this, context);
|
||||
}
|
||||
}
|
||||
|
||||
get parent(): N | Component<N> { return this.parentStack[this.parentStack.length - 1]; }
|
||||
|
||||
visitText(cmd: RenderTextCmd, context: BuildContext<N>): any {
|
||||
var text = context.factory.createText(cmd.value);
|
||||
this._addChild(text, cmd.ngContentIndex, context);
|
||||
if (cmd.isBound) {
|
||||
context.boundTextNodes.push(text);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
visitNgContent(cmd: RenderNgContentCmd, context: BuildContext<N>): any {
|
||||
if (isPresent(this.parentComponent)) {
|
||||
if (this.parentComponent.isRoot) {
|
||||
var insertionPoint = context.factory.createRootContentInsertionPoint();
|
||||
if (this.parent instanceof Component) {
|
||||
context.factory.appendChild((<Component<N>>this.parent).shadowRoot, insertionPoint);
|
||||
} else {
|
||||
context.factory.appendChild(<N>this.parent, insertionPoint);
|
||||
}
|
||||
context.rootContentInsertionPoints.push(insertionPoint);
|
||||
} else {
|
||||
var projectedNodes = this.parentComponent.project(cmd.index);
|
||||
for (var i = 0; i < projectedNodes.length; i++) {
|
||||
var node = projectedNodes[i];
|
||||
this._addChild(node, cmd.ngContentIndex, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
visitBeginElement(cmd: RenderBeginElementCmd, context: BuildContext<N>): any {
|
||||
this.parentStack.push(this._beginElement(cmd, context, null));
|
||||
return null;
|
||||
}
|
||||
visitEndElement(context: BuildContext<N>): any {
|
||||
this._endElement();
|
||||
return null;
|
||||
}
|
||||
visitBeginComponent(cmd: RenderBeginComponentCmd, context: BuildContext<N>): any {
|
||||
var templateId = cmd.templateId;
|
||||
var tpl = context.factory.resolveComponentTemplate(templateId);
|
||||
var el = this._beginElement(cmd, context, tpl);
|
||||
var root = el;
|
||||
|
||||
if (tpl.encapsulation === ViewEncapsulation.Native) {
|
||||
root = context.factory.createShadowRoot(el, templateId);
|
||||
context.nativeShadowRoots.push(root);
|
||||
}
|
||||
var isRoot = context.componentCount === 0 && context.isHost;
|
||||
var component = new Component(el, root, isRoot, tpl);
|
||||
context.enqueueComponentBuilder(component);
|
||||
this.parentStack.push(component);
|
||||
return null;
|
||||
}
|
||||
visitEndComponent(context: BuildContext<N>): any {
|
||||
this._endElement();
|
||||
return null;
|
||||
}
|
||||
visitEmbeddedTemplate(cmd: RenderEmbeddedTemplateCmd, context: BuildContext<N>): any {
|
||||
var el = context.factory.createTemplateAnchor(cmd.attrNameAndValues);
|
||||
this._addChild(el, cmd.ngContentIndex, context);
|
||||
context.boundElements.push(el);
|
||||
if (cmd.isMerged) {
|
||||
context.enqueueFragmentBuilder(this.parentComponent, this.template, cmd.children);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private _beginElement(cmd: RenderBeginElementCmd, context: BuildContext<N>,
|
||||
componentTemplate: RenderComponentTemplate): N {
|
||||
var el: N = context.consumeInplaceElement();
|
||||
var attrNameAndValues = cmd.attrNameAndValues;
|
||||
var templateEmulatedEncapsulation = this.template.encapsulation === ViewEncapsulation.Emulated;
|
||||
var componentEmulatedEncapsulation =
|
||||
isPresent(componentTemplate) &&
|
||||
componentTemplate.encapsulation === ViewEncapsulation.Emulated;
|
||||
var newAttrLength = attrNameAndValues.length + (templateEmulatedEncapsulation ? 2 : 0) +
|
||||
(componentEmulatedEncapsulation ? 2 : 0);
|
||||
if (newAttrLength > attrNameAndValues.length) {
|
||||
// Note: Need to clone attrNameAndValues to make it writable!
|
||||
var newAttrNameAndValues = ListWrapper.createFixedSize(newAttrLength);
|
||||
var attrIndex;
|
||||
for (attrIndex = 0; attrIndex < attrNameAndValues.length; attrIndex++) {
|
||||
newAttrNameAndValues[attrIndex] = attrNameAndValues[attrIndex];
|
||||
}
|
||||
if (templateEmulatedEncapsulation) {
|
||||
newAttrNameAndValues[attrIndex++] = _shimContentAttribute(this.template.shortId);
|
||||
newAttrNameAndValues[attrIndex++] = '';
|
||||
}
|
||||
if (componentEmulatedEncapsulation) {
|
||||
newAttrNameAndValues[attrIndex++] = _shimHostAttribute(componentTemplate.shortId);
|
||||
newAttrNameAndValues[attrIndex++] = '';
|
||||
}
|
||||
attrNameAndValues = newAttrNameAndValues;
|
||||
}
|
||||
if (isPresent(el)) {
|
||||
context.factory.mergeElement(el, attrNameAndValues);
|
||||
this.fragmentRootNodes.push(el);
|
||||
} else {
|
||||
el = context.factory.createElement(cmd.name, attrNameAndValues);
|
||||
this._addChild(el, cmd.ngContentIndex, context);
|
||||
}
|
||||
if (cmd.isBound) {
|
||||
var boundElementIndex = context.boundElements.length;
|
||||
context.boundElements.push(el);
|
||||
for (var i = 0; i < cmd.eventTargetAndNames.length; i += 2) {
|
||||
var target = cmd.eventTargetAndNames[i];
|
||||
var eventName = cmd.eventTargetAndNames[i + 1];
|
||||
context.addEventListener(boundElementIndex, target, eventName);
|
||||
}
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
private _endElement() { this.parentStack.pop(); }
|
||||
|
||||
private _addChild(node: N, ngContentIndex: number, context: BuildContext<N>) {
|
||||
var parent = this.parent;
|
||||
if (isPresent(parent)) {
|
||||
if (parent instanceof Component) {
|
||||
parent.addContentNode(ngContentIndex, node, context);
|
||||
} else {
|
||||
context.factory.appendChild(<N>parent, node);
|
||||
}
|
||||
} else {
|
||||
this.fragmentRootNodes.push(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Component<N> {
|
||||
private contentNodesByNgContentIndex: N[][] = [];
|
||||
|
||||
constructor(public hostElement: N, public shadowRoot: N, public isRoot: boolean,
|
||||
public template: RenderComponentTemplate) {}
|
||||
addContentNode(ngContentIndex: number, node: N, context: BuildContext<N>) {
|
||||
if (isBlank(ngContentIndex)) {
|
||||
if (this.template.encapsulation === ViewEncapsulation.Native) {
|
||||
context.factory.appendChild(this.hostElement, node);
|
||||
}
|
||||
} else {
|
||||
while (this.contentNodesByNgContentIndex.length <= ngContentIndex) {
|
||||
this.contentNodesByNgContentIndex.push([]);
|
||||
}
|
||||
this.contentNodesByNgContentIndex[ngContentIndex].push(node);
|
||||
}
|
||||
}
|
||||
project(ngContentIndex: number): N[] {
|
||||
return ngContentIndex < this.contentNodesByNgContentIndex.length ?
|
||||
this.contentNodesByNgContentIndex[ngContentIndex] :
|
||||
[];
|
||||
}
|
||||
}
|
||||
|
||||
var COMPONENT_REGEX = /%COMP%/g;
|
||||
export const COMPONENT_VARIABLE = '%COMP%';
|
||||
export const HOST_ATTR = `_nghost-${COMPONENT_VARIABLE}`;
|
||||
export const CONTENT_ATTR = `_ngcontent-${COMPONENT_VARIABLE}`;
|
||||
|
||||
function _shimContentAttribute(componentShortId: string): string {
|
||||
return StringWrapper.replaceAll(CONTENT_ATTR, COMPONENT_REGEX, componentShortId);
|
||||
}
|
||||
|
||||
function _shimHostAttribute(componentShortId: string): string {
|
||||
return StringWrapper.replaceAll(HOST_ATTR, COMPONENT_REGEX, componentShortId);
|
||||
}
|
||||
Reference in New Issue
Block a user