-
Notifications
You must be signed in to change notification settings - Fork 27.1k
View factory #4367
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
View factory #4367
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,10 @@ import { | |
| */ | ||
| @CONST() | ||
| export class CompiledTemplate { | ||
| static getChangeDetectorFromData(data: any[]): Function { return data[0]; } | ||
| static getCommandsFromData(data: any[]): TemplateCmd[] { return data[1]; } | ||
| static getSylesFromData(data: any[]): string[] { return data[2]; } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @tbosch, this was supposed to be named
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, thanks for catching.
|
||
|
|
||
| // Note: paramGetter is a function so that we can have cycles between templates! | ||
| // paramGetter returns a tuple with: | ||
| // - ChangeDetector factory function | ||
|
|
@@ -91,13 +95,11 @@ export function endElement(): TemplateCmd { | |
| export class BeginComponentCmd implements TemplateCmd, IBeginElementCmd, RenderBeginComponentCmd { | ||
| isBound: boolean = true; | ||
| templateId: number; | ||
| component: Type; | ||
| constructor(public name: string, public attrNameAndValues: string[], | ||
| public eventTargetAndNames: string[], | ||
| public variableNameAndValues: Array<string | number>, public directives: Type[], | ||
| public nativeShadow: boolean, public ngContentIndex: number, | ||
| public template: CompiledTemplate) { | ||
| this.component = directives[0]; | ||
| this.templateId = template.id; | ||
| } | ||
| visit(visitor: RenderCommandVisitor, context: any): any { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| import {BaseException} from 'angular2/src/core/facade/exceptions'; | ||
| import {ListWrapper, MapWrapper, Map, StringMapWrapper} from 'angular2/src/core/facade/collection'; | ||
| import {isPresent, isBlank, stringify} from 'angular2/src/core/facade/lang'; | ||
|
|
||
| import { | ||
| RenderViewRef, | ||
| RenderEventDispatcher, | ||
| RenderTemplateCmd, | ||
| RenderProtoViewRef, | ||
| RenderFragmentRef | ||
| } from './api'; | ||
|
|
||
| export class DefaultProtoViewRef extends RenderProtoViewRef { | ||
| constructor(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[]) { | ||
| 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(); | ||
| locals.set('$event', event); | ||
| allowDefaultBehavior = | ||
| this.eventDispatcher.dispatchRenderEvent(boundElementIndex, eventName, locals); | ||
| } | ||
| return allowDefaultBehavior; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,230 @@ | ||
| import {isBlank, isPresent} from 'angular2/src/core/facade/lang'; | ||
| import { | ||
| RenderEventDispatcher, | ||
| RenderTemplateCmd, | ||
| RenderCommandVisitor, | ||
| RenderBeginElementCmd, | ||
| RenderBeginComponentCmd, | ||
| RenderNgContentCmd, | ||
| RenderTextCmd, | ||
| RenderEmbeddedTemplateCmd | ||
| } from './api'; | ||
| import {DefaultRenderView, DefaultRenderFragmentRef} from './view'; | ||
|
|
||
| export function createRenderView(fragmentCmds: RenderTemplateCmd[], inplaceElement: any, | ||
| nodeFactory: NodeFactory<any>): DefaultRenderView<any> { | ||
| var builders: RenderViewBuilder<any>[] = []; | ||
| visitAll(new RenderViewBuilder<any>(null, null, inplaceElement, builders, nodeFactory), | ||
| fragmentCmds); | ||
| var boundElements: any[] = []; | ||
| var boundTextNodes: any[] = []; | ||
| var nativeShadowRoots: any[] = []; | ||
| var fragments: DefaultRenderFragmentRef<any>[] = []; | ||
| var viewElementOffset = 0; | ||
| var view: DefaultRenderView<any>; | ||
| var eventDispatcher = (boundElementIndex: number, eventName: string, event: any) => | ||
| view.dispatchRenderEvent(boundElementIndex, eventName, event); | ||
| var globalEventAdders: Function[] = []; | ||
|
|
||
| for (var i = 0; i < builders.length; i++) { | ||
| var builder = builders[i]; | ||
| addAll(builder.boundElements, boundElements); | ||
| addAll(builder.boundTextNodes, boundTextNodes); | ||
| addAll(builder.nativeShadowRoots, nativeShadowRoots); | ||
| if (isBlank(builder.rootNodesParent)) { | ||
| fragments.push(new DefaultRenderFragmentRef<any>(builder.fragmentRootNodes)); | ||
| } | ||
| for (var j = 0; j < builder.eventData.length; j++) { | ||
| var eventData = builder.eventData[j]; | ||
| var boundElementIndex = eventData[0] + viewElementOffset; | ||
| var target = eventData[1]; | ||
| var eventName = eventData[2]; | ||
| if (isPresent(target)) { | ||
| var handler = | ||
| createEventHandler(boundElementIndex, `${target}:${eventName}`, eventDispatcher); | ||
| globalEventAdders.push(createGlobalEventAdder(target, eventName, handler, nodeFactory)); | ||
| } else { | ||
| var handler = createEventHandler(boundElementIndex, eventName, eventDispatcher); | ||
| nodeFactory.on(boundElements[boundElementIndex], eventName, handler); | ||
| } | ||
| } | ||
| viewElementOffset += builder.boundElements.length; | ||
| } | ||
| view = new DefaultRenderView<any>(fragments, boundTextNodes, boundElements, nativeShadowRoots, | ||
| globalEventAdders); | ||
| return view; | ||
| } | ||
|
|
||
| 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); | ||
| } | ||
|
|
||
| export interface NodeFactory<N> { | ||
| resolveComponentTemplate(templateId: number): RenderTemplateCmd[]; | ||
| createTemplateAnchor(attrNameAndValues: string[]): N; | ||
| createElement(name: string, attrNameAndValues: string[]): N; | ||
| mergeElement(existing: N, attrNameAndValues: string[]); | ||
| createShadowRoot(host: N): 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 RenderViewBuilder<N> implements RenderCommandVisitor { | ||
| parentStack: Array<N | Component<N>>; | ||
| boundTextNodes: N[] = []; | ||
| boundElements: N[] = []; | ||
| eventData: any[][] = []; | ||
|
|
||
| fragmentRootNodes: N[] = []; | ||
| nativeShadowRoots: N[] = []; | ||
|
|
||
| constructor(public parentComponent: Component<N>, public rootNodesParent: N, | ||
| public inplaceElement: N, public allBuilders: RenderViewBuilder<N>[], | ||
| public factory: NodeFactory<N>) { | ||
| this.parentStack = [rootNodesParent]; | ||
| allBuilders.push(this); | ||
| } | ||
|
|
||
| get parent(): N | Component<N> { return this.parentStack[this.parentStack.length - 1]; } | ||
|
|
||
| visitText(cmd: RenderTextCmd, context: any): any { | ||
| var text = this.factory.createText(cmd.value); | ||
| this._addChild(text, cmd.ngContentIndex); | ||
| if (cmd.isBound) { | ||
| this.boundTextNodes.push(text); | ||
| } | ||
| return null; | ||
| } | ||
| visitNgContent(cmd: RenderNgContentCmd, context: any): any { | ||
| if (isPresent(this.parentComponent)) { | ||
| var projectedNodes = this.parentComponent.project(); | ||
| for (var i = 0; i < projectedNodes.length; i++) { | ||
| var node = projectedNodes[i]; | ||
| this._addChild(node, cmd.ngContentIndex); | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
| visitBeginElement(cmd: RenderBeginElementCmd, context: any): any { | ||
| this.parentStack.push(this._beginElement(cmd)); | ||
| return null; | ||
| } | ||
| visitEndElement(context: any): any { | ||
| this._endElement(); | ||
| return null; | ||
| } | ||
| visitBeginComponent(cmd: RenderBeginComponentCmd, context: any): any { | ||
| var el = this._beginElement(cmd); | ||
| var root = el; | ||
| if (cmd.nativeShadow) { | ||
| root = this.factory.createShadowRoot(el); | ||
| this.nativeShadowRoots.push(root); | ||
| } | ||
| this.parentStack.push(new Component(el, root, cmd, this.factory)); | ||
| return null; | ||
| } | ||
| visitEndComponent(context: any): any { | ||
| var c = <Component<N>>this.parent; | ||
| var template = this.factory.resolveComponentTemplate(c.cmd.templateId); | ||
| this._visitChildTemplate(template, c, c.shadowRoot); | ||
| this._endElement(); | ||
| return null; | ||
| } | ||
| visitEmbeddedTemplate(cmd: RenderEmbeddedTemplateCmd, context: any): any { | ||
| var el = this.factory.createTemplateAnchor(cmd.attrNameAndValues); | ||
| this._addChild(el, cmd.ngContentIndex); | ||
| this.boundElements.push(el); | ||
| if (cmd.isMerged) { | ||
| this._visitChildTemplate(cmd.children, this.parentComponent, null); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| private _beginElement(cmd: RenderBeginElementCmd): N { | ||
| var el: N; | ||
| if (isPresent(this.inplaceElement)) { | ||
| el = this.inplaceElement; | ||
| this.inplaceElement = null; | ||
| this.factory.mergeElement(el, cmd.attrNameAndValues); | ||
| this.fragmentRootNodes.push(el); | ||
| } else { | ||
| el = this.factory.createElement(cmd.name, cmd.attrNameAndValues); | ||
| this._addChild(el, cmd.ngContentIndex); | ||
| } | ||
| if (cmd.isBound) { | ||
| this.boundElements.push(el); | ||
| for (var i = 0; i < cmd.eventTargetAndNames.length; i += 2) { | ||
| var target = cmd.eventTargetAndNames[i]; | ||
| var eventName = cmd.eventTargetAndNames[i + 1]; | ||
| this.eventData.push([this.boundElements.length - 1, target, eventName]); | ||
| } | ||
| } | ||
| return el; | ||
| } | ||
|
|
||
| private _endElement() { this.parentStack.pop(); } | ||
|
|
||
| private _visitChildTemplate(cmds: RenderTemplateCmd[], parent: Component<N>, rootNodesParent: N) { | ||
| visitAll(new RenderViewBuilder(parent, rootNodesParent, null, this.allBuilders, this.factory), | ||
| cmds); | ||
| } | ||
|
|
||
| private _addChild(node: N, ngContentIndex: number) { | ||
| var parent = this.parent; | ||
| if (isPresent(parent)) { | ||
| if (parent instanceof Component) { | ||
| parent.addContentNode(ngContentIndex, node); | ||
| } else { | ||
| this.factory.appendChild(<N>parent, node); | ||
| } | ||
| } else { | ||
| this.fragmentRootNodes.push(node); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| class Component<N> { | ||
| private contentNodesByNgContentIndex: N[][] = []; | ||
| private projectingNgContentIndex: number = 0; | ||
|
|
||
| constructor(public hostElement: N, public shadowRoot: N, public cmd: RenderBeginComponentCmd, | ||
| public factory: NodeFactory<N>) {} | ||
| addContentNode(ngContentIndex: number, node: N) { | ||
| if (isBlank(ngContentIndex)) { | ||
| if (this.cmd.nativeShadow) { | ||
| this.factory.appendChild(this.hostElement, node); | ||
| } | ||
| } else { | ||
| while (this.contentNodesByNgContentIndex.length <= ngContentIndex) { | ||
| this.contentNodesByNgContentIndex.push([]); | ||
| } | ||
| this.contentNodesByNgContentIndex[ngContentIndex].push(node); | ||
| } | ||
| } | ||
| project(): N[] { | ||
| var ngContentIndex = this.projectingNgContentIndex++; | ||
| return ngContentIndex < this.contentNodesByNgContentIndex.length ? | ||
| this.contentNodesByNgContentIndex[ngContentIndex] : | ||
| []; | ||
| } | ||
| } | ||
|
|
||
| function addAll(source: any[], target: any[]) { | ||
| for (var i = 0; i < source.length; i++) { | ||
| target.push(source[i]); | ||
| } | ||
| } | ||
|
|
||
| function visitAll(visitor: RenderCommandVisitor, fragmentCmds: RenderTemplateCmd[]) { | ||
| for (var i = 0; i < fragmentCmds.length; i++) { | ||
| fragmentCmds[i].visit(visitor, null); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment: For non components there is nothing to be normalized