refactor(render): remove recursion from renderer

The goal is to make implementing a renderer straight forward.

BREAKING_CHANGE:

- Renderer interface was redone / simplified.
- `DirectDomRenderer` was replaced by `DomRenderer`.
- `DirectDomRenderer.setImperativeComponentRootNodes` is replaced
  by the following 2 steps:
    1. `ViewManager.getComponentView(elementRef) -> ViewRef`
    2. `DomRenderer.setComponentViewRootNodes(viewRef, rootNodes)`
- all `@View` annotations need to have a template, but the template
  may be empty. Previously views that had a `renderer` property did
  not have to have a `template`.
- `dynamicComponentLoader.loadIntoNewLocation` does no more allow
  to pass an element, but requires a css selector.
  Special syntax: `:document` can be used as prefix to search globally
  on the document instead of in the provided parent view.

Part of #1675
This commit is contained in:
Tobias Bosch
2015-05-06 10:49:42 -07:00
parent d2507ac760
commit c68fa27444
51 changed files with 1242 additions and 2228 deletions

View File

@ -134,18 +134,11 @@ export class DirectiveMetadata {
}
// An opaque reference to a DomProtoView
export class RenderProtoViewRef {}
export class RenderProtoViewRef {
}
// An opaque reference to a DomView
export class RenderViewRef {}
export class RenderViewContainerRef {
view:RenderViewRef;
elementIndex:number;
constructor(view:RenderViewRef, elementIndex: number) {
this.view = view;
this.elementIndex = elementIndex;
}
export class RenderViewRef {
}
export class ViewDefinition {
@ -168,113 +161,101 @@ export class RenderCompiler {
*/
compileHost(componentId):Promise<ProtoViewDto> { return null; }
/**
* Creats a ProtoViewDto for a component that will use an imperative View using the given
* renderer.
* Note: Rigth now, the renderer argument is ignored, but will be used in the future to define
* a custom handler.
*/
createImperativeComponentProtoView(rendererId):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(template:ViewDefinition):Promise<ProtoViewDto> { return null; }
/**
* Sets the preset nested components,
* which will be instantiated when this protoView is instantiated.
* Note: We can't create new ProtoViewRefs here as we need to support cycles / recursive components.
* @param {List<RenderProtoViewRef>} protoViewRefs
* DomProtoView for every element with a component in this protoView or in a view container's protoView
*/
mergeChildComponentProtoViews(protoViewRef:RenderProtoViewRef, componentProtoViewRefs:List<RenderProtoViewRef>) { return null; }
}
export class Renderer {
/**
* Creates a view and inserts it into a ViewContainer.
* @param {RenderViewContainerRef} viewContainerRef
* @param {RenderProtoViewRef} protoViewRef A RenderProtoViewRef of type ProtoViewDto.HOST_VIEW_TYPE or ProtoViewDto.EMBEDDED_VIEW_TYPE
* @param {number} atIndex
* @return {List<RenderViewRef>} the view and all of its nested child component views
*/
createViewInContainer(vcRef:RenderViewContainerRef, atIndex:number, protoViewRef:RenderProtoViewRef):List<RenderViewRef> { return null; }
/**
* Destroys the view in the given ViewContainer
*/
destroyViewInContainer(vcRef:RenderViewContainerRef, atIndex:number):void {}
/**
* Inserts a detached view into a viewContainer.
*/
insertViewIntoContainer(vcRef:RenderViewContainerRef, atIndex:number, view:RenderViewRef):void {}
/**
* Detaches a view from a container so that it can be inserted later on
*/
detachViewFromContainer(vcRef:RenderViewContainerRef, atIndex:number):void {}
/**
* Creates a view and
* installs it as a shadow view for an element.
*
* Note: only allowed if there is a dynamic component directive at this place.
* @param {RenderViewRef} hostView
* @param {number} elementIndex
* @param {RenderProtoViewRef} componentProtoViewRef A RenderProtoViewRef of type ProtoViewDto.COMPONENT_VIEW_TYPE
* @return {List<RenderViewRef>} the view and all of its nested child component views
*/
createDynamicComponentView(hostViewRef:RenderViewRef, elementIndex:number, componentProtoViewRef:RenderProtoViewRef):List<RenderViewRef> { return null; }
/**
* Destroys the component view at the given index
*
* Note: only allowed if there is a dynamic component directive at this place.
*/
destroyDynamicComponentView(hostViewRef:RenderViewRef, elementIndex:number):void {}
/**
* Creates a host view that includes the given element.
* @param {RenderViewRef} parentViewRef (might be null)
* @param {any} hostElementSelector element or css selector for the host element
* @param {RenderProtoViewRef} hostProtoView a RenderProtoViewRef of type ProtoViewDto.HOST_VIEW_TYPE
* @return {List<RenderViewRef>} the view and all of its nested child component views
* @param {RenderViewRef} parentHostViewRef (might be null)
* @param {any} hostElementSelector css selector for the host element
* @param {RenderProtoViewRef} hostProtoViewRef a RenderProtoViewRef of type ProtoViewDto.HOST_VIEW_TYPE
* @return {RenderViewRef} the created view
*/
createInPlaceHostView(parentViewRef:RenderViewRef, hostElementSelector, hostProtoViewRef:RenderProtoViewRef):List<RenderViewRef> { return null; }
createInPlaceHostView(parentHostViewRef:RenderViewRef, hostElementSelector:string, hostProtoViewRef:RenderProtoViewRef):RenderViewRef {
return null;
}
/**
* Destroys the given host view in the given parent view.
*/
destroyInPlaceHostView(parentViewRef:RenderViewRef, hostViewRef:RenderViewRef):void {}
destroyInPlaceHostView(parentHostViewRef:RenderViewRef, hostViewRef:RenderViewRef) {
}
/**
* Sets a property on an element.
* Creates a regular view out of the given ProtoView
*/
createView(protoViewRef:RenderProtoViewRef):RenderViewRef {
return null;
}
/**
* Destroys the given view after it has been dehydrated and detached
*/
destroyView(viewRef:RenderViewRef) {
}
/**
* Attaches a componentView into the given hostView at the given element
*/
attachComponentView(hostViewRef:RenderViewRef, elementIndex:number, componentViewRef:RenderViewRef) {
}
/**
* Detaches a componentView into the given hostView at the given element
*/
detachComponentView(hostViewRef:RenderViewRef, boundElementIndex:number, componentViewRef:RenderViewRef) {
}
/**
* Attaches a view into a ViewContainer (in the given parentView at the given element) at the given index.
*/
attachViewInContainer(parentViewRef:RenderViewRef, boundElementIndex:number, atIndex:number, viewRef:RenderViewRef) {
}
/**
* Detaches a view into a ViewContainer (in the given parentView at the given element) at the given index.
*/
// TODO(tbosch): this should return a promise as it can be animated!
detachViewInContainer(parentViewRef:RenderViewRef, boundElementIndex:number, atIndex:number, viewRef:RenderViewRef) {
}
/**
* Hydrates a view after it has been attached. Hydration/dehydration is used for reusing views inside of the view pool.
*/
hydrateView(hviewRef:RenderViewRef) {
}
/**
* Dehydrates a view after it has been attached. Hydration/dehydration is used for reusing views inside of the view pool.
*/
dehydrateView(viewRef:RenderViewRef) {
}
/**
* Sets a porperty on an element.
* Note: This will fail if the property was not mentioned previously as a host property
* in the View.
* in the ProtoView
*/
setElementProperty(view:RenderViewRef, elementIndex:number, propertyName:string, propertyValue:any):void {}
setElementProperty(viewRef:RenderViewRef, elementIndex:number, propertyName:string, propertyValue:any):void {
}
/*
* Sets the value of a text node.
*/
setText(viewRef:RenderViewRef, textNodeIndex:number, text:string):void {
}
/**
* This will set the value for a text node.
* Note: This needs to be separate from setElementProperty as we don't have ElementBinders
* for text nodes in the DomProtoView either.
* Sets the dispatcher for all events of the given view
*/
setText(view:RenderViewRef, textNodeIndex:number, text:string):void {}
/**
* Sets the dispatcher for all events that have been defined in the template or in directives
* in the given view.
*/
setEventDispatcher(viewRef:RenderViewRef, dispatcher:any/*EventDispatcher*/):void {}
/**
* To be called at the end of the VmTurn so the API can buffer calls
*/
flush():void {}
setEventDispatcher(viewRef:RenderViewRef, dispatcher:any/*api.EventDispatcher*/):void {
}
}

View File

@ -2,7 +2,6 @@ import {Injectable} from 'angular2/src/di/annotations_impl';
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {BaseException, isPresent} from 'angular2/src/facade/lang';
import {ListWrapper} from 'angular2/src/facade/collection';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {ViewDefinition, ProtoViewDto, DirectiveMetadata, RenderCompiler, RenderProtoViewRef} from '../../api';
@ -11,13 +10,6 @@ import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader';
import {CompileStepFactory, DefaultStepFactory} from './compile_step_factory';
import {Parser} from 'angular2/change_detection';
import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy';
import {ProtoViewBuilder} from '../view/proto_view_builder';
import {DirectDomProtoViewRef} from '../direct_dom_renderer';
function _resolveProtoView(protoViewRef:DirectDomProtoViewRef) {
return isPresent(protoViewRef) ? protoViewRef.delegate : null;
}
/**
* The compiler loads and translates the html templates of components into
@ -53,18 +45,6 @@ export class DomCompiler extends RenderCompiler {
return this._compileTemplate(hostViewDef, element);
}
createImperativeComponentProtoView(rendererId):Promise<ProtoViewDto> {
var protoViewBuilder = new ProtoViewBuilder(null);
protoViewBuilder.setImperativeRendererId(rendererId);
return PromiseWrapper.resolve(protoViewBuilder.build());
}
mergeChildComponentProtoViews(protoViewRef:RenderProtoViewRef, protoViewRefs:List<RenderProtoViewRef>) {
_resolveProtoView(protoViewRef).mergeChildComponentProtoViews(
ListWrapper.map(protoViewRefs, _resolveProtoView)
);
}
_compileTemplate(viewDef: ViewDefinition, tplElement):Promise<ProtoViewDto> {
var subTaskPromises = [];
var pipeline = new CompilePipeline(this._stepFactory.createSteps(viewDef, subTaskPromises));

View File

@ -1,158 +0,0 @@
import {Injectable} from 'angular2/src/di/annotations_impl';
import {List, ListWrapper} from 'angular2/src/facade/collection';
import {isBlank, isPresent, BaseException} from 'angular2/src/facade/lang';
import * as api from '../api';
import {DomView} from './view/view';
import {DomProtoView} from './view/proto_view';
import {ViewFactory} from './view/view_factory';
import {RenderViewHydrator} from './view/view_hydrator';
import {ShadowDomStrategy} from './shadow_dom/shadow_dom_strategy';
import {ViewContainer} from './view/view_container';
function _resolveViewContainer(vc:api.RenderViewContainerRef) {
return _resolveView(vc.view).getOrCreateViewContainer(vc.elementIndex);
}
function _resolveView(viewRef:DirectDomViewRef) {
return isPresent(viewRef) ? viewRef.delegate : null;
}
function _resolveProtoView(protoViewRef:DirectDomProtoViewRef) {
return isPresent(protoViewRef) ? protoViewRef.delegate : null;
}
function _wrapView(view:DomView) {
return new DirectDomViewRef(view);
}
function _collectComponentChildViewRefs(view, target = null) {
if (isBlank(target)) {
target = [];
}
ListWrapper.push(target, _wrapView(view));
ListWrapper.forEach(view.componentChildViews, (view) => {
if (isPresent(view)) {
_collectComponentChildViewRefs(view, target);
}
});
return target;
}
// public so that the compiler can use it.
export class DirectDomProtoViewRef extends api.RenderProtoViewRef {
delegate:DomProtoView;
constructor(delegate:DomProtoView) {
super();
this.delegate = delegate;
}
}
export class DirectDomViewRef extends api.RenderViewRef {
delegate:DomView;
constructor(delegate:DomView) {
super();
this.delegate = delegate;
}
}
@Injectable()
export class DirectDomRenderer extends api.Renderer {
_viewFactory: ViewFactory;
_viewHydrator: RenderViewHydrator;
_shadowDomStrategy: ShadowDomStrategy;
constructor(
viewFactory: ViewFactory, viewHydrator: RenderViewHydrator, shadowDomStrategy: ShadowDomStrategy) {
super();
this._viewFactory = viewFactory;
this._viewHydrator = viewHydrator;
this._shadowDomStrategy = shadowDomStrategy;
}
createViewInContainer(vcRef:api.RenderViewContainerRef, atIndex:number, protoViewRef:api.RenderProtoViewRef):List<api.RenderViewRef> {
var view = this._viewFactory.getView(_resolveProtoView(protoViewRef));
var vc = _resolveViewContainer(vcRef);
this._viewHydrator.hydrateViewInViewContainer(vc, view);
vc.insert(view, atIndex);
return _collectComponentChildViewRefs(view);
}
destroyViewInContainer(vcRef:api.RenderViewContainerRef, atIndex:number):void {
var vc = _resolveViewContainer(vcRef);
var view = vc.detach(atIndex);
this._viewHydrator.dehydrateViewInViewContainer(vc, view);
this._viewFactory.returnView(view);
}
insertViewIntoContainer(vcRef:api.RenderViewContainerRef, atIndex=-1, viewRef:api.RenderViewRef):void {
_resolveViewContainer(vcRef).insert(_resolveView(viewRef), atIndex);
}
detachViewFromContainer(vcRef:api.RenderViewContainerRef, atIndex:number):void {
_resolveViewContainer(vcRef).detach(atIndex);
}
createDynamicComponentView(hostViewRef:api.RenderViewRef, elementIndex:number, componentViewRef:api.RenderProtoViewRef):List<api.RenderViewRef> {
var hostView = _resolveView(hostViewRef);
var componentView = this._viewFactory.getView(_resolveProtoView(componentViewRef));
this._viewHydrator.hydrateDynamicComponentView(hostView, elementIndex, componentView);
return _collectComponentChildViewRefs(componentView);
}
destroyDynamicComponentView(hostViewRef:api.RenderViewRef, elementIndex:number):void {
throw new BaseException('Not supported yet');
// Something along these lines:
// var hostView = _resolveView(hostViewRef);
// var componentView = hostView.childComponentViews[elementIndex];
// this._viewHydrator.dehydrateDynamicComponentView(hostView, componentView);
}
createInPlaceHostView(parentViewRef:api.RenderViewRef, hostElementSelector, hostProtoViewRef:api.RenderProtoViewRef):List<api.RenderViewRef> {
var parentView = _resolveView(parentViewRef);
var hostView = this._viewFactory.createInPlaceHostView(hostElementSelector, _resolveProtoView(hostProtoViewRef));
this._viewHydrator.hydrateInPlaceHostView(parentView, hostView);
return _collectComponentChildViewRefs(hostView);
}
/**
* Destroys the given host view in the given parent view.
*/
destroyInPlaceHostView(parentViewRef:api.RenderViewRef, hostViewRef:api.RenderViewRef):void {
var parentView = _resolveView(parentViewRef);
var hostView = _resolveView(hostViewRef);
this._viewHydrator.dehydrateInPlaceHostView(parentView, hostView);
}
setImperativeComponentRootNodes(parentViewRef:api.RenderViewRef, elementIndex:number, nodes:List):void {
var parentView = _resolveView(parentViewRef);
var hostElement = parentView.boundElements[elementIndex];
var componentView = parentView.componentChildViews[elementIndex];
if (isBlank(componentView)) {
throw new BaseException(`There is no componentChildView at index ${elementIndex}`);
}
if (isBlank(componentView.proto.imperativeRendererId)) {
throw new BaseException(`This component view has no imperative renderer`);
}
ViewContainer.removeViewNodes(componentView);
componentView.rootNodes = nodes;
this._shadowDomStrategy.attachTemplate(hostElement, componentView);
}
setElementProperty(viewRef:api.RenderViewRef, elementIndex:number, propertyName:string, propertyValue:any):void {
_resolveView(viewRef).setElementProperty(elementIndex, propertyName, propertyValue);
}
setText(viewRef:api.RenderViewRef, textNodeIndex:number, text:string):void {
_resolveView(viewRef).setText(textNodeIndex, text);
}
setEventDispatcher(viewRef:api.RenderViewRef, dispatcher:any/*api.EventDispatcher*/):void {
_resolveView(viewRef).setEventDispatcher(dispatcher);
}
}

View File

@ -0,0 +1,338 @@
import {Inject, Injectable} from 'angular2/src/di/annotations_impl';
import {int, isPresent, isBlank, BaseException, RegExpWrapper} from 'angular2/src/facade/lang';
import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {Content} from './shadow_dom/content_tag';
import {ShadowDomStrategy} from './shadow_dom/shadow_dom_strategy';
import {EventManager} from './events/event_manager';
import {DomProtoView, DomProtoViewRef, resolveInternalDomProtoView} from './view/proto_view';
import {DomView, DomViewRef, resolveInternalDomView} from './view/view';
import {DomViewContainer} from './view/view_container';
import {NG_BINDING_CLASS_SELECTOR, NG_BINDING_CLASS} from './util';
import {Renderer, RenderProtoViewRef, RenderViewRef} from '../api';
// TODO(tbosch): use an OpaqueToken here once our transpiler supports
// const expressions!
export const DOCUMENT_TOKEN = 'DocumentToken';
var _DOCUMENT_SELECTOR_REGEX = RegExpWrapper.create('\\:document(.+)');
@Injectable()
export class DomRenderer extends Renderer {
_eventManager:EventManager;
_shadowDomStrategy:ShadowDomStrategy;
_document;
constructor(eventManager:EventManager, shadowDomStrategy:ShadowDomStrategy, @Inject(DOCUMENT_TOKEN) document) {
super();
this._eventManager = eventManager;
this._shadowDomStrategy = shadowDomStrategy;
this._document = document;
}
createInPlaceHostView(parentHostViewRef:RenderViewRef, hostElementSelector:string, hostProtoViewRef:RenderProtoViewRef):RenderViewRef {
var containerNode;
var documentSelectorMatch = RegExpWrapper.firstMatch(_DOCUMENT_SELECTOR_REGEX, hostElementSelector);
if (isPresent(documentSelectorMatch)) {
containerNode = this._document;
hostElementSelector = documentSelectorMatch[1];
} else if (isPresent(parentHostViewRef)) {
var parentHostView = resolveInternalDomView(parentHostViewRef);
containerNode = parentHostView.shadowRoot;
} else {
containerNode = this._document;
}
var element = DOM.querySelector(containerNode, hostElementSelector);
if (isBlank(element)) {
throw new BaseException(`The selector "${hostElementSelector}" did not match any elements`);
}
var hostProtoView = resolveInternalDomProtoView(hostProtoViewRef);
return new DomViewRef(this._createView(hostProtoView, element));
}
destroyInPlaceHostView(parentHostViewRef:RenderViewRef, hostViewRef:RenderViewRef) {
var hostView = resolveInternalDomView(hostViewRef);
this._removeViewNodes(hostView);
}
createView(protoViewRef:RenderProtoViewRef):RenderViewRef {
var protoView = resolveInternalDomProtoView(protoViewRef);
return new DomViewRef(this._createView(protoView, null));
}
destroyView(view:RenderViewRef) {
// noop for now
}
attachComponentView(hostViewRef:RenderViewRef, elementIndex:number, componentViewRef:RenderViewRef) {
var hostView = resolveInternalDomView(hostViewRef);
var componentView = resolveInternalDomView(componentViewRef);
var element = hostView.boundElements[elementIndex];
var lightDom = hostView.lightDoms[elementIndex];
if (isPresent(lightDom)) {
lightDom.attachShadowDomView(componentView);
}
var shadowRoot = this._shadowDomStrategy.prepareShadowRoot(element);
this._moveViewNodesIntoParent(shadowRoot, componentView);
componentView.hostLightDom = lightDom;
componentView.shadowRoot = shadowRoot;
}
setComponentViewRootNodes(componentViewRef:RenderViewRef, rootNodes:List) {
var componentView = resolveInternalDomView(componentViewRef);
this._removeViewNodes(componentView);
componentView.rootNodes = rootNodes;
this._moveViewNodesIntoParent(componentView.shadowRoot, componentView);
}
detachComponentView(hostViewRef:RenderViewRef, boundElementIndex:number, componentViewRef:RenderViewRef) {
var hostView = resolveInternalDomView(hostViewRef);
var componentView = resolveInternalDomView(componentViewRef);
this._removeViewNodes(componentView);
var lightDom = hostView.lightDoms[boundElementIndex];
if (isPresent(lightDom)) {
lightDom.detachShadowDomView();
}
componentView.hostLightDom = null;
componentView.shadowRoot = null;
}
attachViewInContainer(parentViewRef:RenderViewRef, boundElementIndex:number, atIndex:number, viewRef:RenderViewRef) {
var parentView = resolveInternalDomView(parentViewRef);
var view = resolveInternalDomView(viewRef);
var viewContainer = this._getOrCreateViewContainer(parentView, boundElementIndex);
ListWrapper.insert(viewContainer.views, atIndex, view);
view.hostLightDom = parentView.hostLightDom;
var directParentLightDom = parentView.getDirectParentLightDom(boundElementIndex);
if (isBlank(directParentLightDom)) {
var siblingToInsertAfter;
if (atIndex == 0) {
siblingToInsertAfter = parentView.boundElements[boundElementIndex];
} else {
siblingToInsertAfter = ListWrapper.last(viewContainer.views[atIndex - 1].rootNodes);
}
this._moveViewNodesAfterSibling(siblingToInsertAfter, view);
} else {
directParentLightDom.redistribute();
}
// new content tags might have appeared, we need to redistribute.
if (isPresent(parentView.hostLightDom)) {
parentView.hostLightDom.redistribute();
}
}
detachViewInContainer(parentViewRef:RenderViewRef, boundElementIndex:number, atIndex:number, viewRef:RenderViewRef) {
var parentView = resolveInternalDomView(parentViewRef);
var view = resolveInternalDomView(viewRef);
var viewContainer = parentView.viewContainers[boundElementIndex];
var detachedView = viewContainer.views[atIndex];
ListWrapper.removeAt(viewContainer.views, atIndex);
var directParentLightDom = parentView.getDirectParentLightDom(boundElementIndex);
if (isBlank(directParentLightDom)) {
this._removeViewNodes(detachedView);
} else {
directParentLightDom.redistribute();
}
view.hostLightDom = null;
// content tags might have disappeared we need to do redistribution.
if (isPresent(parentView.hostLightDom)) {
parentView.hostLightDom.redistribute();
}
}
hydrateView(viewRef:RenderViewRef) {
var view = resolveInternalDomView(viewRef);
if (view.hydrated) throw new BaseException('The view is already hydrated.');
view.hydrated = true;
for (var i = 0; i < view.lightDoms.length; ++i) {
var lightDom = view.lightDoms[i];
if (isPresent(lightDom)) {
lightDom.redistribute();
}
}
//add global events
view.eventHandlerRemovers = ListWrapper.create();
var binders = view.proto.elementBinders;
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
var binder = binders[binderIdx];
if (isPresent(binder.globalEvents)) {
for (var i = 0; i < binder.globalEvents.length; i++) {
var globalEvent = binder.globalEvents[i];
var remover = this._createGlobalEventListener(view, binderIdx, globalEvent.name, globalEvent.target, globalEvent.fullName);
ListWrapper.push(view.eventHandlerRemovers, remover);
}
}
}
if (isPresent(view.hostLightDom)) {
view.hostLightDom.redistribute();
}
}
dehydrateView(viewRef:RenderViewRef) {
var view = resolveInternalDomView(viewRef);
//remove global events
for (var i = 0; i < view.eventHandlerRemovers.length; i++) {
view.eventHandlerRemovers[i]();
}
view.eventHandlerRemovers = null;
view.hydrated = false;
}
setElementProperty(viewRef:RenderViewRef, elementIndex:number, propertyName:string, propertyValue:any):void {
var view = resolveInternalDomView(viewRef);
view.setElementProperty(elementIndex, propertyName, propertyValue);
}
setText(viewRef:RenderViewRef, textNodeIndex:number, text:string):void {
var view = resolveInternalDomView(viewRef);
DOM.setText(view.boundTextNodes[textNodeIndex], text);
}
setEventDispatcher(viewRef:RenderViewRef, dispatcher:any/*api.EventDispatcher*/):void {
var view = resolveInternalDomView(viewRef);
view.eventDispatcher = dispatcher;
}
_createView(protoView:DomProtoView, inplaceElement): DomView {
var rootElementClone = isPresent(inplaceElement) ? inplaceElement : DOM.importIntoDoc(protoView.element);
var elementsWithBindingsDynamic;
if (protoView.isTemplateElement) {
elementsWithBindingsDynamic = DOM.querySelectorAll(DOM.content(rootElementClone), NG_BINDING_CLASS_SELECTOR);
} else {
elementsWithBindingsDynamic = DOM.getElementsByClassName(rootElementClone, NG_BINDING_CLASS);
}
var elementsWithBindings = ListWrapper.createFixedSize(elementsWithBindingsDynamic.length);
for (var binderIdx = 0; binderIdx < elementsWithBindingsDynamic.length; ++binderIdx) {
elementsWithBindings[binderIdx] = elementsWithBindingsDynamic[binderIdx];
}
var viewRootNodes;
if (protoView.isTemplateElement) {
var childNode = DOM.firstChild(DOM.content(rootElementClone));
viewRootNodes = []; // TODO(perf): Should be fixed size, since we could pre-compute in in DomProtoView
// Note: An explicit loop is the fastest way to convert a DOM array into a JS array!
while(childNode != null) {
ListWrapper.push(viewRootNodes, childNode);
childNode = DOM.nextSibling(childNode);
}
} else {
viewRootNodes = [rootElementClone];
}
var binders = protoView.elementBinders;
var boundTextNodes = [];
var boundElements = ListWrapper.createFixedSize(binders.length);
var contentTags = ListWrapper.createFixedSize(binders.length);
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
var binder = binders[binderIdx];
var element;
if (binderIdx === 0 && protoView.rootBindingOffset === 1) {
element = rootElementClone;
} else {
element = elementsWithBindings[binderIdx - protoView.rootBindingOffset];
}
boundElements[binderIdx] = element;
// boundTextNodes
var childNodes = DOM.childNodes(DOM.templateAwareRoot(element));
var textNodeIndices = binder.textNodeIndices;
for (var i = 0; i<textNodeIndices.length; i++) {
ListWrapper.push(boundTextNodes, childNodes[textNodeIndices[i]]);
}
// contentTags
var contentTag = null;
if (isPresent(binder.contentTagSelector)) {
contentTag = new Content(element, binder.contentTagSelector);
}
contentTags[binderIdx] = contentTag;
}
var view = new DomView(
protoView, viewRootNodes,
boundTextNodes, boundElements, contentTags
);
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
var binder = binders[binderIdx];
var element = boundElements[binderIdx];
// lightDoms
var lightDom = null;
if (isPresent(binder.componentId)) {
lightDom = this._shadowDomStrategy.constructLightDom(view, boundElements[binderIdx]);
}
view.lightDoms[binderIdx] = lightDom;
// init contentTags
var contentTag = contentTags[binderIdx];
if (isPresent(contentTag)) {
var destLightDom = view.getDirectParentLightDom(binderIdx);
contentTag.init(destLightDom);
}
// events
if (isPresent(binder.eventLocals) && isPresent(binder.localEvents)) {
for (var i = 0; i < binder.localEvents.length; i++) {
this._createEventListener(view, element, binderIdx, binder.localEvents[i].name, binder.eventLocals);
}
}
}
return view;
}
_createEventListener(view, element, elementIndex, eventName, eventLocals) {
this._eventManager.addEventListener(element, eventName, (event) => {
view.dispatchEvent(elementIndex, eventName, event);
});
}
_moveViewNodesAfterSibling(sibling, view) {
for (var i = view.rootNodes.length - 1; i >= 0; --i) {
DOM.insertAfter(sibling, view.rootNodes[i]);
}
}
_moveViewNodesIntoParent(parent, view) {
for (var i = 0; i < view.rootNodes.length; ++i) {
DOM.appendChild(parent, view.rootNodes[i]);
}
}
_removeViewNodes(view) {
var len = view.rootNodes.length;
if (len == 0) return;
var parent = view.rootNodes[0].parentNode;
for (var i = len - 1; i >= 0; --i) {
DOM.removeChild(parent, view.rootNodes[i]);
}
}
_getOrCreateViewContainer(parentView:DomView, boundElementIndex) {
var vc = parentView.viewContainers[boundElementIndex];
if (isBlank(vc)) {
vc = new DomViewContainer();
parentView.viewContainers[boundElementIndex] = vc;
}
return vc;
}
_createGlobalEventListener(view, elementIndex, eventName, eventTarget, fullName): Function {
return this._eventManager.addGlobalEventListener(eventTarget, eventName, (event) => {
view.dispatchEvent(elementIndex, fullName, event);
});
}
}

View File

@ -74,16 +74,12 @@ export class Content {
this._strategy = null;
}
hydrate(destinationLightDom:ldModule.LightDom) {
init(destinationLightDom:ldModule.LightDom) {
this._strategy = isPresent(destinationLightDom) ?
new IntermediateContent(destinationLightDom) :
new RenderedContent(this.contentStartElement);
}
dehydrate() {
this._strategy = null;
}
nodes():List {
return this._strategy.nodes;
}

View File

@ -7,7 +7,6 @@ import * as viewModule from '../view/view';
import {LightDom} from './light_dom';
import {ShadowDomStrategy} from './shadow_dom_strategy';
import {StyleUrlResolver} from './style_url_resolver';
import {moveViewNodesIntoParent} from './util';
import {insertSharedStyleText} from './util';
/**
@ -33,12 +32,12 @@ export class EmulatedUnscopedShadowDomStrategy extends ShadowDomStrategy {
return false;
}
attachTemplate(el, view:viewModule.DomView) {
moveViewNodesIntoParent(el, view);
prepareShadowRoot(el) {
return el;
}
constructLightDom(lightDomView:viewModule.DomView, shadowDomView:viewModule.DomView, el): LightDom {
return new LightDom(lightDomView, shadowDomView, el);
constructLightDom(lightDomView:viewModule.DomView, el): LightDom {
return new LightDom(lightDomView, el);
}
processStyleElement(hostComponentId:string, templateUrl:string, styleEl):Promise {

View File

@ -28,12 +28,20 @@ export class LightDom {
nodes:List;
roots:List<_Root>;
constructor(lightDomView:viewModule.DomView, shadowDomView:viewModule.DomView, element) {
constructor(lightDomView:viewModule.DomView, element) {
this.lightDomView = lightDomView;
this.shadowDomView = shadowDomView;
this.nodes = DOM.childNodesAsList(element);
this.roots = null;
this.shadowDomView = null;
}
attachShadowDomView(shadowDomView:viewModule.DomView) {
this.shadowDomView = shadowDomView;
}
detachShadowDomView() {
this.shadowDomView = null;
}
redistribute() {
@ -41,7 +49,11 @@ export class LightDom {
}
contentTags(): List<Content> {
return this._collectAllContentTags(this.shadowDomView, []);
if (isPresent(this.shadowDomView)) {
return this._collectAllContentTags(this.shadowDomView, []);
} else {
return [];
}
}
// Collects the Content directives from the view and all its child views

View File

@ -2,11 +2,8 @@ import {Promise} from 'angular2/src/facade/async';
import {DOM} from 'angular2/src/dom/dom_adapter';
import * as viewModule from '../view/view';
import {StyleUrlResolver} from './style_url_resolver';
import {ShadowDomStrategy} from './shadow_dom_strategy';
import {moveViewNodesIntoParent} from './util';
/**
* This strategies uses the native Shadow DOM support.
@ -22,8 +19,8 @@ export class NativeShadowDomStrategy extends ShadowDomStrategy {
this.styleUrlResolver = styleUrlResolver;
}
attachTemplate(el, view:viewModule.DomView){
moveViewNodesIntoParent(DOM.createShadowRoot(el), view);
prepareShadowRoot(el) {
return DOM.createShadowRoot(el);
}
processStyleElement(hostComponentId:string, templateUrl:string, styleEl):Promise {

View File

@ -9,9 +9,14 @@ export class ShadowDomStrategy {
return true;
}
attachTemplate(el, view:viewModule.DomView) {}
/**
* Prepares and returns the shadow root for the given element.
*/
prepareShadowRoot(el):any {
return null;
}
constructLightDom(lightDomView:viewModule.DomView, shadowDomView:viewModule.DomView, el): LightDom {
constructLightDom(lightDomView:viewModule.DomView, el): LightDom {
return null;
}

View File

@ -5,12 +5,6 @@ import {DOM} from 'angular2/src/dom/dom_adapter';
import {ShadowCss} from './shadow_css';
export function moveViewNodesIntoParent(parent, view) {
for (var i = 0; i < view.rootNodes.length; ++i) {
DOM.appendChild(parent, view.rootNodes[i]);
}
}
var _componentUIDs: Map<string, int> = MapWrapper.create();
var _nextComponentUID: int = 0;
var _sharedStyleTexts: Map<string, boolean> = MapWrapper.create();

View File

@ -1,4 +1,3 @@
import {isBlank, isPresent} from 'angular2/src/facade/lang';
import {AST} from 'angular2/change_detection';
import {SetterFn} from 'angular2/src/reflection/types';
import {List, ListWrapper} from 'angular2/src/facade/collection';
@ -39,14 +38,6 @@ export class ElementBinder {
this.distanceToParent = distanceToParent;
this.propertySetters = propertySetters;
}
hasStaticComponent() {
return isPresent(this.componentId) && isPresent(this.nestedProtoView);
}
hasDynamicComponent() {
return isPresent(this.componentId) && isBlank(this.nestedProtoView);
}
}
export class Event {

View File

@ -1,43 +1,39 @@
import {isPresent} from 'angular2/src/facade/lang';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {List, Map, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
import {List} from 'angular2/src/facade/collection';
import {ElementBinder} from './element_binder';
import {NG_BINDING_CLASS} from '../util';
import {RenderProtoViewRef} from '../../api';
export function resolveInternalDomProtoView(protoViewRef:RenderProtoViewRef) {
var domProtoViewRef:DomProtoViewRef = protoViewRef;
return domProtoViewRef._protoView;
}
export class DomProtoViewRef extends RenderProtoViewRef {
_protoView:DomProtoView;
constructor(protoView:DomProtoView) {
super();
this._protoView = protoView;
}
}
export class DomProtoView {
element;
elementBinders:List<ElementBinder>;
isTemplateElement:boolean;
rootBindingOffset:int;
imperativeRendererId:string;
constructor({
elementBinders,
element,
imperativeRendererId
element
}) {
this.element = element;
this.elementBinders = elementBinders;
this.imperativeRendererId = imperativeRendererId;
if (isPresent(imperativeRendererId)) {
this.rootBindingOffset = 0;
this.isTemplateElement = false;
} else {
this.isTemplateElement = DOM.isTemplateElement(this.element);
this.rootBindingOffset = (isPresent(this.element) && DOM.hasClass(this.element, NG_BINDING_CLASS)) ? 1 : 0;
}
}
mergeChildComponentProtoViews(componentProtoViews:List<DomProtoView>) {
var componentProtoViewIndex = 0;
for (var i=0; i<this.elementBinders.length; i++) {
var eb = this.elementBinders[i];
if (isPresent(eb.componentId)) {
eb.nestedProtoView = componentProtoViews[componentProtoViewIndex];
componentProtoViewIndex++;
}
}
this.isTemplateElement = DOM.isTemplateElement(this.element);
this.rootBindingOffset = (isPresent(this.element) && DOM.hasClass(this.element, NG_BINDING_CLASS)) ? 1 : 0;
}
}

View File

@ -6,12 +6,11 @@ import {
ASTWithSource, AST, AstTransformer, AccessMember, LiteralArray, ImplicitReceiver
} from 'angular2/change_detection';
import {DomProtoView} from './proto_view';
import {DomProtoView, DomProtoViewRef, resolveInternalDomProtoView} from './proto_view';
import {ElementBinder, Event} from './element_binder';
import {setterFactory} from './property_setter_factory';
import * as api from '../../api';
import * as directDomRenderer from '../direct_dom_renderer';
import {NG_BINDING_CLASS, EVENT_TARGET_SEPARATOR} from '../util';
@ -19,18 +18,11 @@ export class ProtoViewBuilder {
rootElement;
variableBindings: Map<string, string>;
elements:List<ElementBinderBuilder>;
imperativeRendererId:string;
constructor(rootElement) {
this.rootElement = rootElement;
this.elements = [];
this.variableBindings = MapWrapper.create();
this.imperativeRendererId = null;
}
setImperativeRendererId(id:string):ProtoViewBuilder {
this.imperativeRendererId = id;
return this;
}
bindElement(element, description = null):ElementBinderBuilder {
@ -93,7 +85,7 @@ export class ProtoViewBuilder {
contentTagSelector: ebb.contentTagSelector,
parentIndex: parentIndex,
distanceToParent: ebb.distanceToParent,
nestedProtoView: isPresent(nestedProtoView) ? nestedProtoView.render.delegate : null,
nestedProtoView: isPresent(nestedProtoView) ? resolveInternalDomProtoView(nestedProtoView.render) : null,
componentId: ebb.componentId,
eventLocals: new LiteralArray(ebb.eventBuilder.buildEventLocals()),
localEvents: ebb.eventBuilder.buildLocalEvents(),
@ -102,10 +94,9 @@ export class ProtoViewBuilder {
}));
});
return new api.ProtoViewDto({
render: new directDomRenderer.DirectDomProtoViewRef(new DomProtoView({
render: new DomProtoViewRef(new DomProtoView({
element: this.rootElement,
elementBinders: renderElementBinders,
imperativeRendererId: this.imperativeRendererId
elementBinders: renderElementBinders
})),
elementBinders: apiElementBinders,
variableBindings: this.variableBindings

View File

@ -2,13 +2,30 @@ import {DOM} from 'angular2/src/dom/dom_adapter';
import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection';
import {int, isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
import {ViewContainer} from './view_container';
import {DomViewContainer} from './view_container';
import {DomProtoView} from './proto_view';
import {LightDom} from '../shadow_dom/light_dom';
import {Content} from '../shadow_dom/content_tag';
import {RenderViewRef} from '../../api';
// TODO(tbosch): enable this again!
// import {EventDispatcher} from '../../api';
export function resolveInternalDomView(viewRef:RenderViewRef) {
var domViewRef:DomViewRef = viewRef;
return domViewRef._view;
}
export class DomViewRef extends RenderViewRef {
_view:DomView;
constructor(view:DomView) {
super();
this._view = view;
}
}
const NG_BINDING_CLASS = 'ng-binding';
/**
@ -22,18 +39,15 @@ export class DomView {
rootNodes:List;
// TODO(tbosch): move componentChildViews, viewContainers, contentTags, lightDoms into
// a single array with records inside
componentChildViews: List<DomView>;
viewContainers: List<ViewContainer>;
viewContainers: List<DomViewContainer>;
contentTags: List<Content>;
lightDoms: List<LightDom>;
hostLightDom: LightDom;
shadowRoot;
proto: DomProtoView;
hydrated: boolean;
_eventDispatcher: any/*EventDispatcher*/;
eventDispatcher: any/*EventDispatcher*/;
eventHandlerRemovers: List<Function>;
/// Host views that were added by an imperative view.
/// This is a dynamically growing / shrinking array.
imperativeHostViews: List<DomView>;
constructor(
proto:DomProtoView, rootNodes:List,
@ -45,12 +59,11 @@ export class DomView {
this.viewContainers = ListWrapper.createFixedSize(boundElements.length);
this.contentTags = contentTags;
this.lightDoms = ListWrapper.createFixedSize(boundElements.length);
ListWrapper.fill(this.lightDoms, null);
this.componentChildViews = ListWrapper.createFixedSize(boundElements.length);
this.hostLightDom = null;
this.hydrated = false;
this.eventHandlerRemovers = [];
this.imperativeHostViews = [];
this.eventDispatcher = null;
this.shadowRoot = null;
}
getDirectParentLightDom(boundElementIndex:number) {
@ -62,15 +75,6 @@ export class DomView {
return destLightDom;
}
getOrCreateViewContainer(binderIndex) {
var vc = this.viewContainers[binderIndex];
if (isBlank(vc)) {
vc = new ViewContainer(this, binderIndex);
this.viewContainers[binderIndex] = vc;
}
return vc;
}
setElementProperty(elementIndex:number, propertyName:string, value:any) {
var setter = MapWrapper.get(this.proto.elementBinders[elementIndex].propertySetters, propertyName);
setter(this.boundElements[elementIndex], value);
@ -80,24 +84,16 @@ export class DomView {
DOM.setText(this.boundTextNodes[textIndex], value);
}
getViewContainer(index:number):ViewContainer {
return this.viewContainers[index];
}
setEventDispatcher(dispatcher:any/*EventDispatcher*/) {
this._eventDispatcher = dispatcher;
}
dispatchEvent(elementIndex, eventName, event): boolean {
var allowDefaultBehavior = true;
if (isPresent(this._eventDispatcher)) {
if (isPresent(this.eventDispatcher)) {
var evalLocals = MapWrapper.create();
MapWrapper.set(evalLocals, '$event', event);
// TODO(tbosch): reenable this when we are parsing element properties
// out of action expressions
// var localValues = this.proto.elementBinders[elementIndex].eventLocals.eval(null, new Locals(null, evalLocals));
// this._eventDispatcher.dispatchEvent(elementIndex, eventName, localValues);
allowDefaultBehavior = this._eventDispatcher.dispatchEvent(elementIndex, eventName, evalLocals);
// this.eventDispatcher.dispatchEvent(elementIndex, eventName, localValues);
allowDefaultBehavior = this.eventDispatcher.dispatchEvent(elementIndex, eventName, evalLocals);
if (!allowDefaultBehavior) {
event.preventDefault();
}

View File

@ -1,90 +1,15 @@
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
import {ListWrapper, MapWrapper, List} from 'angular2/src/facade/collection';
import {DOM} from 'angular2/src/dom/dom_adapter';
import * as viewModule from './view';
export class ViewContainer {
parentView: viewModule.DomView;
boundElementIndex: number;
export class DomViewContainer {
views: List<viewModule.DomView>;
constructor(parentView: viewModule.DomView, boundElementIndex: number) {
this.parentView = parentView;
this.boundElementIndex = boundElementIndex;
constructor() {
// The order in this list matches the DOM order.
this.views = [];
}
get(index: number): viewModule.DomView {
return this.views[index];
}
size() {
return this.views.length;
}
_siblingToInsertAfter(index: number) {
if (index == 0) return this.parentView.boundElements[this.boundElementIndex];
return ListWrapper.last(this.views[index - 1].rootNodes);
}
_checkHydrated() {
if (!this.parentView.hydrated) throw new BaseException(
'Cannot change dehydrated ViewContainer');
}
_getDirectParentLightDom() {
return this.parentView.getDirectParentLightDom(this.boundElementIndex);
}
clear() {
this._checkHydrated();
for (var i=this.views.length-1; i>=0; i--) {
this.detach(i);
}
if (isPresent(this._getDirectParentLightDom())) {
this._getDirectParentLightDom().redistribute();
}
}
insert(view, atIndex=-1): viewModule.DomView {
this._checkHydrated();
if (atIndex == -1) atIndex = this.views.length;
ListWrapper.insert(this.views, atIndex, view);
if (isBlank(this._getDirectParentLightDom())) {
ViewContainer.moveViewNodesAfterSibling(this._siblingToInsertAfter(atIndex), view);
} else {
this._getDirectParentLightDom().redistribute();
}
// new content tags might have appeared, we need to redistribute.
if (isPresent(this.parentView.hostLightDom)) {
this.parentView.hostLightDom.redistribute();
}
return view;
}
/**
* The method can be used together with insert to implement a view move, i.e.
* moving the dom nodes while the directives in the view stay intact.
*/
detach(atIndex:number) {
this._checkHydrated();
var detachedView = this.get(atIndex);
ListWrapper.removeAt(this.views, atIndex);
if (isBlank(this._getDirectParentLightDom())) {
ViewContainer.removeViewNodes(detachedView);
} else {
this._getDirectParentLightDom().redistribute();
}
// content tags might have disappeared we need to do redistribution.
if (isPresent(this.parentView.hostLightDom)) {
this.parentView.hostLightDom.redistribute();
}
return detachedView;
}
contentTagContainers() {
return this.views;
}
@ -97,18 +22,4 @@ export class ViewContainer {
return r;
}
static moveViewNodesAfterSibling(sibling, view) {
for (var i = view.rootNodes.length - 1; i >= 0; --i) {
DOM.insertAfter(sibling, view.rootNodes[i]);
}
}
static removeViewNodes(view) {
var len = view.rootNodes.length;
if (len == 0) return;
var parent = view.rootNodes[0].parentNode;
for (var i = len - 1; i >= 0; --i) {
DOM.removeChild(parent, view.rootNodes[i]);
}
}
}

View File

@ -1,164 +0,0 @@
import {Inject, Injectable} from 'angular2/src/di/annotations_impl';
import {int, isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {Content} from '../shadow_dom/content_tag';
import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy';
import {EventManager} from 'angular2/src/render/dom/events/event_manager';
import * as pvModule from './proto_view';
import * as viewModule from './view';
import {NG_BINDING_CLASS_SELECTOR, NG_BINDING_CLASS} from '../util';
// TODO(tbosch): Make this an OpaqueToken as soon as our transpiler supports this!
export const VIEW_POOL_CAPACITY = 'render.ViewFactory.viewPoolCapacity';
@Injectable()
export class ViewFactory {
_poolCapacityPerProtoView:number;
_pooledViewsPerProtoView:Map<pvModule.DomProtoView, List<viewModule.DomView>>;
_eventManager:EventManager;
_shadowDomStrategy:ShadowDomStrategy;
constructor(@Inject(VIEW_POOL_CAPACITY) poolCapacityPerProtoView,
eventManager:EventManager, shadowDomStrategy:ShadowDomStrategy) {
this._poolCapacityPerProtoView = poolCapacityPerProtoView;
this._pooledViewsPerProtoView = MapWrapper.create();
this._eventManager = eventManager;
this._shadowDomStrategy = shadowDomStrategy;
}
createInPlaceHostView(hostElementSelector, hostProtoView:pvModule.DomProtoView):viewModule.DomView {
return this._createView(hostProtoView, hostElementSelector);
}
getView(protoView:pvModule.DomProtoView):viewModule.DomView {
var pooledViews = MapWrapper.get(this._pooledViewsPerProtoView, protoView);
if (isPresent(pooledViews) && pooledViews.length > 0) {
return ListWrapper.removeLast(pooledViews);
}
return this._createView(protoView, null);
}
returnView(view:viewModule.DomView) {
if (view.hydrated) {
throw new BaseException('View is still hydrated');
}
var protoView = view.proto;
var pooledViews = MapWrapper.get(this._pooledViewsPerProtoView, protoView);
if (isBlank(pooledViews)) {
pooledViews = [];
MapWrapper.set(this._pooledViewsPerProtoView, protoView, pooledViews);
}
if (pooledViews.length < this._poolCapacityPerProtoView) {
ListWrapper.push(pooledViews, view);
}
}
_createView(protoView:pvModule.DomProtoView, inplaceElement): viewModule.DomView {
if (isPresent(protoView.imperativeRendererId)) {
return new viewModule.DomView(
protoView, [], [], [], []
);
}
var rootElementClone = isPresent(inplaceElement) ? inplaceElement : DOM.importIntoDoc(protoView.element);
var elementsWithBindingsDynamic;
if (protoView.isTemplateElement) {
elementsWithBindingsDynamic = DOM.querySelectorAll(DOM.content(rootElementClone), NG_BINDING_CLASS_SELECTOR);
} else {
elementsWithBindingsDynamic = DOM.getElementsByClassName(rootElementClone, NG_BINDING_CLASS);
}
var elementsWithBindings = ListWrapper.createFixedSize(elementsWithBindingsDynamic.length);
for (var binderIdx = 0; binderIdx < elementsWithBindingsDynamic.length; ++binderIdx) {
elementsWithBindings[binderIdx] = elementsWithBindingsDynamic[binderIdx];
}
var viewRootNodes;
if (protoView.isTemplateElement) {
var childNode = DOM.firstChild(DOM.content(rootElementClone));
viewRootNodes = []; // TODO(perf): Should be fixed size, since we could pre-compute in in pvModule.DomProtoView
// Note: An explicit loop is the fastest way to convert a DOM array into a JS array!
while(childNode != null) {
ListWrapper.push(viewRootNodes, childNode);
childNode = DOM.nextSibling(childNode);
}
} else {
viewRootNodes = [rootElementClone];
}
var binders = protoView.elementBinders;
var boundTextNodes = [];
var boundElements = ListWrapper.createFixedSize(binders.length);
var contentTags = ListWrapper.createFixedSize(binders.length);
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
var binder = binders[binderIdx];
var element;
if (binderIdx === 0 && protoView.rootBindingOffset === 1) {
element = rootElementClone;
} else {
element = elementsWithBindings[binderIdx - protoView.rootBindingOffset];
}
boundElements[binderIdx] = element;
// boundTextNodes
var childNodes = DOM.childNodes(DOM.templateAwareRoot(element));
var textNodeIndices = binder.textNodeIndices;
for (var i = 0; i<textNodeIndices.length; i++) {
ListWrapper.push(boundTextNodes, childNodes[textNodeIndices[i]]);
}
// contentTags
var contentTag = null;
if (isPresent(binder.contentTagSelector)) {
contentTag = new Content(element, binder.contentTagSelector);
}
contentTags[binderIdx] = contentTag;
}
var view = new viewModule.DomView(
protoView, viewRootNodes,
boundTextNodes, boundElements, contentTags
);
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
var binder = binders[binderIdx];
var element = boundElements[binderIdx];
// static child components
if (binder.hasStaticComponent()) {
var childView = this._createView(binder.nestedProtoView, null);
ViewFactory.setComponentView(this._shadowDomStrategy, view, binderIdx, childView);
}
// events
if (isPresent(binder.eventLocals) && isPresent(binder.localEvents)) {
for (var i = 0; i < binder.localEvents.length; i++) {
this._createEventListener(view, element, binderIdx, binder.localEvents[i].name, binder.eventLocals);
}
}
}
return view;
}
_createEventListener(view, element, elementIndex, eventName, eventLocals) {
this._eventManager.addEventListener(element, eventName, (event) => {
view.dispatchEvent(elementIndex, eventName, event);
});
}
// This method is used by the ViewFactory and the ViewHydrator
// TODO(tbosch): change shadow dom emulation so that LightDom
// instances don't need to be recreated by instead hydrated/dehydrated
static setComponentView(shadowDomStrategy:ShadowDomStrategy, hostView:viewModule.DomView, elementIndex:number, componentView:viewModule.DomView) {
var element = hostView.boundElements[elementIndex];
var lightDom = shadowDomStrategy.constructLightDom(hostView, componentView, element);
shadowDomStrategy.attachTemplate(element, componentView);
hostView.lightDoms[elementIndex] = lightDom;
hostView.componentChildViews[elementIndex] = componentView;
}
}

View File

@ -1,190 +0,0 @@
import {Injectable} from 'angular2/src/di/annotations_impl';
import {int, isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection';
import * as ldModule from '../shadow_dom/light_dom';
import {EventManager} from '../events/event_manager';
import {ViewFactory} from './view_factory';
import * as vcModule from './view_container';
import * as viewModule from './view';
import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy';
/**
* A dehydrated view is a state of the view that allows it to be moved around
* the view tree.
*
* A dehydrated view has the following properties:
*
* - all viewcontainers are empty.
*
* A call to hydrate/dehydrate is called whenever a view is attached/detached,
* but it does not do the attach/detach itself.
*/
@Injectable()
export class RenderViewHydrator {
_eventManager:EventManager;
_viewFactory:ViewFactory;
_shadowDomStrategy:ShadowDomStrategy;
constructor(eventManager:EventManager, viewFactory:ViewFactory, shadowDomStrategy:ShadowDomStrategy) {
this._eventManager = eventManager;
this._viewFactory = viewFactory;
this._shadowDomStrategy = shadowDomStrategy;
}
hydrateDynamicComponentView(hostView:viewModule.DomView, boundElementIndex:number, componentView:viewModule.DomView) {
ViewFactory.setComponentView(this._shadowDomStrategy, hostView, boundElementIndex, componentView);
var lightDom = hostView.lightDoms[boundElementIndex];
this._viewHydrateRecurse(componentView, lightDom);
if (isPresent(lightDom)) {
lightDom.redistribute();
}
}
dehydrateDynamicComponentView(parentView:viewModule.DomView, boundElementIndex:number) {
throw new BaseException('Not supported yet');
// Something along these lines:
// var componentView = parentView.componentChildViews[boundElementIndex];
// vcModule.ViewContainer.removeViewNodes(componentView);
// parentView.componentChildViews[boundElementIndex] = null;
// parentView.lightDoms[boundElementIndex] = null;
// this._viewDehydrateRecurse(componentView);
}
hydrateInPlaceHostView(parentView:viewModule.DomView, hostView:viewModule.DomView) {
if (isPresent(parentView)) {
ListWrapper.push(parentView.imperativeHostViews, hostView);
}
this._viewHydrateRecurse(hostView, null);
}
dehydrateInPlaceHostView(parentView:viewModule.DomView, hostView:viewModule.DomView) {
if (isPresent(parentView)) {
ListWrapper.remove(parentView.imperativeHostViews, hostView);
}
vcModule.ViewContainer.removeViewNodes(hostView);
hostView.rootNodes = [];
this._viewDehydrateRecurse(hostView);
}
hydrateViewInViewContainer(viewContainer:vcModule.ViewContainer, view:viewModule.DomView) {
this._viewHydrateRecurse(view, viewContainer.parentView.hostLightDom);
}
dehydrateViewInViewContainer(viewContainer:vcModule.ViewContainer, view:viewModule.DomView) {
this._viewDehydrateRecurse(view);
}
_viewHydrateRecurse(view, hostLightDom: ldModule.LightDom) {
if (view.hydrated) throw new BaseException('The view is already hydrated.');
view.hydrated = true;
view.hostLightDom = hostLightDom;
// content tags
for (var i = 0; i < view.contentTags.length; i++) {
var destLightDom = view.getDirectParentLightDom(i);
var ct = view.contentTags[i];
if (isPresent(ct)) {
ct.hydrate(destLightDom);
}
}
// componentChildViews
for (var i = 0; i < view.componentChildViews.length; i++) {
var cv = view.componentChildViews[i];
if (isPresent(cv)) {
this._viewHydrateRecurse(cv, view.lightDoms[i]);
}
}
for (var i = 0; i < view.lightDoms.length; ++i) {
var lightDom = view.lightDoms[i];
if (isPresent(lightDom)) {
lightDom.redistribute();
}
}
//add global events
view.eventHandlerRemovers = ListWrapper.create();
var binders = view.proto.elementBinders;
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
var binder = binders[binderIdx];
if (isPresent(binder.globalEvents)) {
for (var i = 0; i < binder.globalEvents.length; i++) {
var globalEvent = binder.globalEvents[i];
var remover = this._createGlobalEventListener(view, binderIdx, globalEvent.name, globalEvent.target, globalEvent.fullName);
ListWrapper.push(view.eventHandlerRemovers, remover);
}
}
}
}
_createGlobalEventListener(view, elementIndex, eventName, eventTarget, fullName): Function {
return this._eventManager.addGlobalEventListener(eventTarget, eventName, (event) => {
view.dispatchEvent(elementIndex, fullName, event);
});
}
_viewDehydrateRecurse(view) {
// Note: preserve the opposite order of the hydration process.
// componentChildViews
for (var i = 0; i < view.componentChildViews.length; i++) {
var cv = view.componentChildViews[i];
if (isPresent(cv)) {
this._viewDehydrateRecurse(cv);
if (view.proto.elementBinders[i].hasDynamicComponent()) {
vcModule.ViewContainer.removeViewNodes(cv);
this._viewFactory.returnView(cv);
view.lightDoms[i] = null;
view.componentChildViews[i] = null;
}
}
}
// imperativeHostViews
for (var i = 0; i < view.imperativeHostViews.length; i++) {
var hostView = view.imperativeHostViews[i];
this._viewDehydrateRecurse(hostView);
vcModule.ViewContainer.removeViewNodes(hostView);
hostView.rootNodes = [];
this._viewFactory.returnView(hostView);
}
view.imperativeHostViews = [];
// viewContainers and content tags
if (isPresent(view.viewContainers)) {
for (var i = 0; i < view.viewContainers.length; i++) {
var vc = view.viewContainers[i];
if (isPresent(vc)) {
this._viewContainerDehydrateRecurse(vc);
}
var ct = view.contentTags[i];
if (isPresent(ct)) {
ct.dehydrate();
}
}
}
//remove global events
for (var i = 0; i < view.eventHandlerRemovers.length; i++) {
view.eventHandlerRemovers[i]();
}
view.hostLightDom = null;
view.eventHandlerRemovers = null;
view.setEventDispatcher(null);
view.hydrated = false;
}
_viewContainerDehydrateRecurse(viewContainer) {
for (var i=0; i<viewContainer.views.length; i++) {
var view = viewContainer.views[i];
this._viewDehydrateRecurse(view);
this._viewFactory.returnView(view);
}
viewContainer.clear();
}
}