Files
Angular-Docs-Cn/modules/angular2/src/render/api.ts
T
Tobias Bosch b1df54501a feat(compiler): attach components and project light dom during compilation.
Closes #2529

BREAKING CHANGES:
- shadow dom emulation no longer
  supports the `<content>` tag. Use the new `<ng-content>` instead
  (works with all shadow dom strategies).
- removed `DomRenderer.setViewRootNodes` and `AppViewManager.getComponentView`
  -> use `DomRenderer.getNativeElementSync(elementRef)` and change shadow dom directly
- the `Renderer` interface has changed:
  * `createView` now also has to support sub views
  * the notion of a container has been removed. Instead, the renderer has
    to implement methods to attach views next to elements or other views.
  * a RenderView now contains multiple RenderFragments. Fragments
    are used to move DOM nodes around.

Internal changes / design changes:
- Introduce notion of view fragments on render side
- DomProtoViews and DomViews on render side are merged,
  AppProtoViews are not merged, AppViews are partially merged
  (they share arrays with the other merged AppViews but we keep
  individual AppView instances for now).
- DomProtoViews always have a `<template>` element as root
  * needed for storing subviews
  * we have less chunks of DOM to clone now
- remove fake ElementBinder / Bound element for root text bindings
  and model them explicitly. This removes a lot of special cases we had!
- AppView shares data with nested component views
- some methods in AppViewManager (create, hydrate, dehydrate) are iterative now
  * now possible as we have all child AppViews / ElementRefs already in an array!
2015-07-15 20:23:27 -07:00

477 lines
16 KiB
TypeScript

import {isPresent, isBlank, RegExpWrapper} from 'angular2/src/facade/lang';
import {Promise} from 'angular2/src/facade/async';
import {List, Map, MapWrapper, StringMap, StringMapWrapper} from 'angular2/src/facade/collection';
import {ASTWithSource} from 'angular2/change_detection';
/**
* General notes:
*
* The methods for creating / destroying views in this API are used in the AppViewHydrator
* and RenderViewHydrator as well.
*
* We are already parsing expressions on the render side:
* - this makes the ElementBinders more compact
* (e.g. no need to distinguish interpolations from regular expressions from literals)
* - allows to retrieve which properties should be accessed from the event
* by looking at the expression
* - we need the parse at least for the `template` attribute to match
* directives in it
* - render compiler is not on the critical path as
* its output will be stored in precompiled templates.
*/
export class EventBinding {
constructor(public fullName: string, public source: ASTWithSource) {}
}
export enum PropertyBindingType {
PROPERTY,
ATTRIBUTE,
CLASS,
STYLE
}
export class ElementPropertyBinding {
constructor(public type: PropertyBindingType, public astWithSource: ASTWithSource,
public property: string, public unit: string = null) {}
}
export class ElementBinder {
index: number;
parentIndex: number;
distanceToParent: number;
directives: List<DirectiveBinder>;
nestedProtoView: ProtoViewDto;
propertyBindings: List<ElementPropertyBinding>;
variableBindings: Map<string, string>;
// Note: this contains a preprocessed AST
// that replaced the values that should be extracted from the element
// with a local name
eventBindings: List<EventBinding>;
readAttributes: Map<string, string>;
constructor({index, parentIndex, distanceToParent, directives, nestedProtoView, propertyBindings,
variableBindings, eventBindings, readAttributes}: {
index?: number,
parentIndex?: number,
distanceToParent?: number,
directives?: List<DirectiveBinder>,
nestedProtoView?: ProtoViewDto,
propertyBindings?: List<ElementPropertyBinding>,
variableBindings?: Map<string, string>,
eventBindings?: List<EventBinding>,
readAttributes?: Map<string, string>
} = {}) {
this.index = index;
this.parentIndex = parentIndex;
this.distanceToParent = distanceToParent;
this.directives = directives;
this.nestedProtoView = nestedProtoView;
this.propertyBindings = propertyBindings;
this.variableBindings = variableBindings;
this.eventBindings = eventBindings;
this.readAttributes = readAttributes;
}
}
export class DirectiveBinder {
// Index into the array of directives in the View instance
directiveIndex: number;
propertyBindings: Map<string, ASTWithSource>;
// Note: this contains a preprocessed AST
// that replaced the values that should be extracted from the element
// with a local name
eventBindings: List<EventBinding>;
hostPropertyBindings: List<ElementPropertyBinding>;
constructor({directiveIndex, propertyBindings, eventBindings, hostPropertyBindings}: {
directiveIndex?: number,
propertyBindings?: Map<string, ASTWithSource>,
eventBindings?: List<EventBinding>,
hostPropertyBindings?: List<ElementPropertyBinding>
}) {
this.directiveIndex = directiveIndex;
this.propertyBindings = propertyBindings;
this.eventBindings = eventBindings;
this.hostPropertyBindings = hostPropertyBindings;
}
}
export enum ViewType {
// A view that contains the host element with bound component directive.
// Contains a COMPONENT view
HOST,
// The view of the component
// Can contain 0 to n EMBEDDED views
COMPONENT,
// A view that is embedded into another View via a <template> element
// inside of a COMPONENT view
EMBEDDED
}
export class ProtoViewDto {
render: RenderProtoViewRef;
elementBinders: List<ElementBinder>;
variableBindings: Map<string, string>;
type: ViewType;
textBindings: List<ASTWithSource>;
constructor({render, elementBinders, variableBindings, type, textBindings}: {
render?: RenderProtoViewRef,
elementBinders?: List<ElementBinder>,
variableBindings?: Map<string, string>,
type?: ViewType,
textBindings?: List<ASTWithSource>
}) {
this.render = render;
this.elementBinders = elementBinders;
this.variableBindings = variableBindings;
this.type = type;
this.textBindings = textBindings;
}
}
export class DirectiveMetadata {
static get DIRECTIVE_TYPE() { return 0; }
static get COMPONENT_TYPE() { return 1; }
id: any;
selector: string;
compileChildren: boolean;
events: List<string>;
properties: List<string>;
readAttributes: List<string>;
type: number;
callOnDestroy: boolean;
callOnChange: boolean;
callOnCheck: boolean;
callOnInit: boolean;
callOnAllChangesDone: boolean;
changeDetection: string;
exportAs: string;
hostListeners: Map<string, string>;
hostProperties: Map<string, string>;
hostAttributes: Map<string, string>;
hostActions: Map<string, string>;
// group 1: "property" from "[property]"
// group 2: "event" from "(event)"
// group 3: "action" from "@action"
private static _hostRegExp = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\))|(?:@(.+)))$/g;
constructor({id, selector, compileChildren, events, hostListeners, hostProperties, hostAttributes,
hostActions, properties, readAttributes, type, callOnDestroy, callOnChange,
callOnCheck, callOnInit, callOnAllChangesDone, changeDetection, exportAs}: {
id?: string,
selector?: string,
compileChildren?: boolean,
events?: List<string>,
hostListeners?: Map<string, string>,
hostProperties?: Map<string, string>,
hostAttributes?: Map<string, string>,
hostActions?: Map<string, string>,
properties?: List<string>,
readAttributes?: List<string>,
type?: number,
callOnDestroy?: boolean,
callOnChange?: boolean,
callOnCheck?: boolean,
callOnInit?: boolean,
callOnAllChangesDone?: boolean,
changeDetection?: string,
exportAs?: string
}) {
this.id = id;
this.selector = selector;
this.compileChildren = isPresent(compileChildren) ? compileChildren : true;
this.events = events;
this.hostListeners = hostListeners;
this.hostAttributes = hostAttributes;
this.hostProperties = hostProperties;
this.hostActions = hostActions;
this.properties = properties;
this.readAttributes = readAttributes;
this.type = type;
this.callOnDestroy = callOnDestroy;
this.callOnChange = callOnChange;
this.callOnCheck = callOnCheck;
this.callOnInit = callOnInit;
this.callOnAllChangesDone = callOnAllChangesDone;
this.changeDetection = changeDetection;
this.exportAs = exportAs;
}
static create({id, selector, compileChildren, events, host, properties, readAttributes, type,
callOnDestroy, callOnChange, callOnCheck, callOnInit, callOnAllChangesDone,
changeDetection, exportAs}: {
id?: string,
selector?: string,
compileChildren?: boolean,
events?: List<string>,
host?: Map<string, string>,
properties?: List<string>,
readAttributes?: List<string>,
type?: number,
callOnDestroy?: boolean,
callOnChange?: boolean,
callOnCheck?: boolean,
callOnInit?: boolean,
callOnAllChangesDone?: boolean,
changeDetection?: string,
exportAs?: string
}): DirectiveMetadata {
let hostListeners = new Map();
let hostProperties = new Map();
let hostAttributes = new Map();
let hostActions = new Map();
if (isPresent(host)) {
MapWrapper.forEach(host, (value: string, key: string) => {
var matches = RegExpWrapper.firstMatch(DirectiveMetadata._hostRegExp, key);
if (isBlank(matches)) {
hostAttributes.set(key, value);
} else if (isPresent(matches[1])) {
hostProperties.set(matches[1], value);
} else if (isPresent(matches[2])) {
hostListeners.set(matches[2], value);
} else if (isPresent(matches[3])) {
hostActions.set(matches[3], value);
}
});
}
return new DirectiveMetadata({
id: id,
selector: selector,
compileChildren: compileChildren,
events: events,
hostListeners: hostListeners,
hostProperties: hostProperties,
hostAttributes: hostAttributes,
hostActions: hostActions,
properties: properties,
readAttributes: readAttributes,
type: type,
callOnDestroy: callOnDestroy,
callOnChange: callOnChange,
callOnCheck: callOnCheck,
callOnInit: callOnInit,
callOnAllChangesDone: callOnAllChangesDone,
changeDetection: changeDetection,
exportAs: exportAs
});
}
}
// An opaque reference to a render proto ivew
export class RenderProtoViewRef {}
// An opaque reference to a part of a view
export class RenderFragmentRef {}
// An opaque reference to a view
export class RenderViewRef {}
export class ViewDefinition {
componentId: string;
templateAbsUrl: string;
template: string;
directives: List<DirectiveMetadata>;
styleAbsUrls: List<string>;
styles: List<string>;
constructor({componentId, templateAbsUrl, template, styleAbsUrls, styles, directives}: {
componentId?: string,
templateAbsUrl?: string,
template?: string,
styleAbsUrls?: List<string>,
styles?: List<string>,
directives?: List<DirectiveMetadata>
}) {
this.componentId = componentId;
this.templateAbsUrl = templateAbsUrl;
this.template = template;
this.styleAbsUrls = styleAbsUrls;
this.styles = styles;
this.directives = directives;
}
}
export class RenderProtoViewMergeMapping {
constructor(public mergedProtoViewRef: RenderProtoViewRef,
// Number of fragments in the merged ProtoView.
// Fragments are stored in depth first order of nested ProtoViews.
public fragmentCount: number,
// Mapping from app element index to render element index.
// Mappings of nested ProtoViews are in depth first order, with all
// indices for one ProtoView in a consecuitve block.
public mappedElementIndices: number[],
// Mapping from app text index to render text index.
// Mappings of nested ProtoViews are in depth first order, with all
// indices for one ProtoView in a consecuitve block.
public mappedTextIndices: number[],
// Mapping from view index to app element index
public hostElementIndicesByViewIndex: number[]) {}
}
export class RenderCompiler {
/**
* Creats a ProtoViewDto that contains a single nested component with the given componentId.
*/
compileHost(directiveMetadata: DirectiveMetadata): Promise<ProtoViewDto> { return null; }
/**
* Compiles a single DomProtoView. Non recursive so that
* we don't need to serialize all possible components over the wire,
* but only the needed ones based on previous calls.
*/
compile(view: ViewDefinition): Promise<ProtoViewDto> { return null; }
/**
* Merges ProtoViews.
* The first entry of the array is the protoview into which all the other entries of the array
* should be merged.
* If the array contains other arrays, they will be merged before processing the parent array.
* The array must contain an entry for every component and embedded ProtoView of the first entry.
* @param protoViewRefs List of ProtoViewRefs or nested
* @return the merge result for every input array in depth first order.
*/
mergeProtoViewsRecursively(
protoViewRefs: List<RenderProtoViewRef | List<any>>): Promise<RenderProtoViewMergeMapping[]> {
return null;
}
}
export class RenderViewWithFragments {
constructor(public viewRef: RenderViewRef, public fragmentRefs: RenderFragmentRef[]) {}
}
/**
* Abstract reference to the element which can be marshaled across web-worker boundry.
*
* This interface is used by the {@link Renderer} api.
*/
export interface RenderElementRef {
/**
* Reference to the {@link RenderViewRef} where the `RenderElementRef` is inside of.
*/
renderView: RenderViewRef;
/**
* Index of the element inside the {@link RenderViewRef}.
*
* This is used internally by the Angular framework to locate elements.
*/
renderBoundElementIndex: number;
}
export class Renderer {
/**
* Creates a root host view that includes the given element.
* Note that the fragmentCount needs to be passed in so that we can create a result
* synchronously even when dealing with webworkers!
*
* @param {RenderProtoViewRef} hostProtoViewRef a RenderProtoViewRef of type
* ProtoViewDto.HOST_VIEW_TYPE
* @param {any} hostElementSelector css selector for the host element (will be queried against the
* main document)
* @return {RenderViewWithFragments} the created view including fragments
*/
createRootHostView(hostProtoViewRef: RenderProtoViewRef, fragmentCount: number,
hostElementSelector: string): RenderViewWithFragments {
return null;
}
/**
* Creates a regular view out of the given ProtoView.
* Note that the fragmentCount needs to be passed in so that we can create a result
* synchronously even when dealing with webworkers!
*/
createView(protoViewRef: RenderProtoViewRef, fragmentCount: number): RenderViewWithFragments {
return null;
}
/**
* Destroys the given view after it has been dehydrated and detached
*/
destroyView(viewRef: RenderViewRef) {}
/**
* Attaches a fragment after another fragment.
*/
attachFragmentAfterFragment(previousFragmentRef: RenderFragmentRef,
fragmentRef: RenderFragmentRef) {}
/**
* Attaches a fragment after an element.
*/
attachFragmentAfterElement(elementRef: RenderElementRef, fragmentRef: RenderFragmentRef) {}
/**
* Detaches a fragment.
*/
detachFragment(fragmentRef: RenderFragmentRef) {}
/**
* Hydrates a view after it has been attached. Hydration/dehydration is used for reusing views
* inside of the view pool.
*/
hydrateView(viewRef: RenderViewRef) {}
/**
* Dehydrates a view after it has been attached. Hydration/dehydration is used for reusing views
* inside of the view pool.
*/
dehydrateView(viewRef: RenderViewRef) {}
/**
* Returns the native element at the given location.
* Attention: In a WebWorker scenario, this should always return null!
*/
getNativeElementSync(location: RenderElementRef): any { return null; }
/**
* Sets a property on an element.
*/
setElementProperty(location: RenderElementRef, propertyName: string, propertyValue: any) {}
/**
* Sets an attribute on an element.
*/
setElementAttribute(location: RenderElementRef, attributeName: string, attributeValue: string) {}
/**
* Sets a class on an element.
*/
setElementClass(location: RenderElementRef, className: string, isAdd: boolean) {}
/**
* Sets a style on an element.
*/
setElementStyle(location: RenderElementRef, styleName: string, styleValue: string) {}
/**
* Calls a method on an element.
*/
invokeElementMethod(location: RenderElementRef, methodName: string, args: List<any>) {}
/**
* Sets the value of a text node.
*/
setText(viewRef: RenderViewRef, textNodeIndex: number, text: string) {}
/**
* Sets the dispatcher for all events of the given view
*/
setEventDispatcher(viewRef: RenderViewRef, dispatcher: RenderEventDispatcher) {}
}
/**
* A dispatcher for all events happening in a view.
*/
export interface RenderEventDispatcher {
/**
* Called when an event was triggered for a on-* attribute on an element.
* @param {Map<string, any>} locals Locals to be used to evaluate the
* event expressions
*/
dispatchRenderEvent(elementIndex: number, eventName: string, locals: Map<string, any>);
}