refactor(views): clean up creating views in place and extract view_hydrator

Major changes:
- `compiler.compileRoot(el, type)`
  -> `compiler.compileInHost(type) + viewHydrator.hydrateHostViewInPlace(el, view)`
- move all `hydrate`/`dehydrate` methods out of `View` and `ViewContainer` into
  a standalone class `view_hydrator` as private methods and provide new public
  methods dedicated to the individual use cases.

Note: This PR does not change the current functionality, only moves it
into different places.

See design discussion in #1351, in preparation for imperative views.
This commit is contained in:
Tobias Bosch
2015-04-15 21:51:30 -07:00
parent 97fc248e00
commit 923d90bce8
35 changed files with 1013 additions and 763 deletions

View File

@ -5,6 +5,10 @@ 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)
@ -17,7 +21,7 @@ import {ASTWithSource} from 'angular2/change_detection';
*/
export class EventBinding {
fullName: string; // name/target:name, e.g "click", "window:resize"
source: ASTWithSource;
source: ASTWithSource;
constructor(fullName :string, source: ASTWithSource) {
this.fullName = fullName;
@ -78,14 +82,27 @@ export class DirectiveBinder {
}
export class ProtoViewDto {
// A view that contains the host element with bound
// component directive.
// Contains a view of type #COMPONENT_VIEW_TYPE.
static get HOST_VIEW_TYPE() { return 0; }
// The view of the component
// Can contain 0 to n views of type #EMBEDDED_VIEW_TYPE
static get COMPONENT_VIEW_TYPE() { return 1; }
// A view that is included via a Viewport directive
// inside of a component view
static get EMBEDDED_VIEW_TYPE() { return 1; }
render: ProtoViewRef;
elementBinders:List<ElementBinder>;
variableBindings: Map<string, string>;
type: number;
constructor({render, elementBinders, variableBindings}={}) {
constructor({render, elementBinders, variableBindings, type}={}) {
this.render = render;
this.elementBinders = elementBinders;
this.variableBindings = variableBindings;
this.type = type;
}
}
@ -143,6 +160,11 @@ export class ViewDefinition {
}
export class Renderer {
/**
* Creats a ProtoViewDto that contains a single nested component with the given componentId.
*/
createHostProtoView(componentId):Promise<ProtoViewDto> { return null; }
/**
* Compiles a single RenderProtoView. Non recursive so that
* we don't need to serialize all possible components over the wire,
@ -160,35 +182,62 @@ export class Renderer {
mergeChildComponentProtoViews(protoViewRef:ProtoViewRef, componentProtoViewRefs:List<ProtoViewRef>) { return null; }
/**
* Creats a RenderProtoView that will create a root view for the given element,
* i.e. it will not clone the element but only attach other proto views to it.
* Contains a single nested component with the given componentId.
* Creates a view and inserts it into a ViewContainer.
* @param {ViewContainerRef} viewContainerRef
* @param {ProtoViewRef} protoViewRef A ProtoViewRef of type ProtoViewDto.HOST_VIEW_TYPE or ProtoViewDto.EMBEDDED_VIEW_TYPE
* @param {number} atIndex
* @return {List<ViewRef>} the view and all of its nested child component views
*/
createRootProtoView(selectorOrElement, componentId):Promise<ProtoViewDto> { return null; }
createViewInContainer(vcRef:ViewContainerRef, atIndex:number, protoViewRef:ProtoViewRef):List<ViewRef> { return null; }
/**
* Creates a view and all of its nested child components.
* @return {List<ViewRef>} depth first list of nested child components
* Destroys the view in the given ViewContainer
*/
createView(protoView:ProtoViewRef):List<ViewRef> { return null; }
/**
* Destroys a view and returns it back into the pool.
*/
destroyView(view:ViewRef):void {}
destroyViewInContainer(vcRef:ViewContainerRef, atIndex:number):void {}
/**
* Inserts a detached view into a viewContainer.
*/
insertViewIntoContainer(vcRef:ViewContainerRef, view:ViewRef, atIndex):void {}
insertViewIntoContainer(vcRef:ViewContainerRef, atIndex:number, view:ViewRef):void {}
/**
* Detaches a view from a container so that it can be inserted later on
* Note: We are not return the ViewRef as this can't be done in sync,
* so we assume that the caller knows which view is in which spot...
*/
detachViewFromContainer(vcRef:ViewContainerRef, 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 {ViewRef} hostView
* @param {number} elementIndex
* @param {ProtoViewRef} componentProtoViewRef A ProtoViewRef of type ProtoViewDto.COMPONENT_VIEW_TYPE
* @return {List<ViewRef>} the view and all of its nested child component views
*/
createDynamicComponentView(hostViewRef:ViewRef, elementIndex:number, componentProtoViewRef:ProtoViewRef):List<ViewRef> { 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:ViewRef, elementIndex:number):void {}
/**
* Creates a host view that includes the given element.
* @param {ViewRef} parentViewRef (might be null)
* @param {any} hostElementSelector element or css selector for the host element
* @param {ProtoViewRef} hostProtoView a ProtoViewRef of type ProtoViewDto.HOST_VIEW_TYPE
* @return {List<ViewRef>} the view and all of its nested child component views
*/
createInPlaceHostView(parentViewRef:ViewRef, hostElementSelector, hostProtoViewRef:ProtoViewRef):List<ViewRef> { return null; }
/**
* Destroys the given host view in the given parent view.
*/
destroyInPlaceHostView(parentViewRef:ViewRef, hostViewRef:ViewRef):void {}
/**
* Sets a property on an element.
* Note: This will fail if the property was not mentioned previously as a propertySetter
@ -196,12 +245,6 @@ export class Renderer {
*/
setElementProperty(view:ViewRef, elementIndex:number, propertyName:string, propertyValue:any):void {}
/**
* Installs a nested component in another view.
* Note: only allowed if there is a dynamic component directive
*/
setDynamicComponentView(view:ViewRef, elementIndex:number, nestedViewRef:ViewRef):void {}
/**
* This will set the value for a text node.
* Note: This needs to be separate from setElementProperty as we don't have ElementBinders

View File

@ -1,15 +1,17 @@
import {Injectable} from 'angular2/di';
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
import {List, ListWrapper} from 'angular2/src/facade/collection';
import {isBlank, isPresent} from 'angular2/src/facade/lang';
import {isBlank, isPresent, BaseException} from 'angular2/src/facade/lang';
import * as api from '../api';
import {RenderView} from './view/view';
import {RenderProtoView} from './view/proto_view';
import {ViewFactory} from './view/view_factory';
import {RenderViewHydrator} from './view/view_hydrator';
import {Compiler} from './compiler/compiler';
import {ShadowDomStrategy} from './shadow_dom/shadow_dom_strategy';
import {ProtoViewBuilder} from './view/proto_view_builder';
import {DOM} from 'angular2/src/dom/dom_adapter';
function _resolveViewContainer(vc:api.ViewContainerRef) {
return _resolveView(vc.view).viewContainers[vc.elementIndex];
@ -65,16 +67,29 @@ export class DirectDomViewRef extends api.ViewRef {
export class DirectDomRenderer extends api.Renderer {
_compiler: Compiler;
_viewFactory: ViewFactory;
_viewHydrator: RenderViewHydrator;
_shadowDomStrategy: ShadowDomStrategy;
constructor(
compiler: Compiler, viewFactory: ViewFactory, shadowDomStrategy: ShadowDomStrategy) {
compiler: Compiler, viewFactory: ViewFactory, viewHydrator: RenderViewHydrator, shadowDomStrategy: ShadowDomStrategy) {
super();
this._compiler = compiler;
this._viewFactory = viewFactory;
this._viewHydrator = viewHydrator;
this._shadowDomStrategy = shadowDomStrategy;
}
createHostProtoView(componentId):Promise<api.ProtoViewDto> {
var rootElement = DOM.createElement('div');
var hostProtoViewBuilder = new ProtoViewBuilder(rootElement);
var elBinder = hostProtoViewBuilder.bindElement(rootElement, 'root element');
elBinder.setComponentId(componentId);
elBinder.bindDirective(0);
this._shadowDomStrategy.processElement(null, componentId, rootElement);
return PromiseWrapper.resolve(hostProtoViewBuilder.build());
}
compile(template:api.ViewDefinition):Promise<api.ProtoViewDto> {
// Note: compiler already uses a DirectDomProtoViewRef, so we don't
// need to do anything here
@ -87,29 +102,22 @@ export class DirectDomRenderer extends api.Renderer {
);
}
createRootProtoView(selectorOrElement, componentId):Promise<api.ProtoViewDto> {
var element = selectorOrElement; // TODO: select the element if it is not a real element...
var rootProtoViewBuilder = new ProtoViewBuilder(element);
rootProtoViewBuilder.setIsRootView(true);
var elBinder = rootProtoViewBuilder.bindElement(element, 'root element');
elBinder.setComponentId(componentId);
elBinder.bindDirective(0);
this._shadowDomStrategy.processElement(null, componentId, element);
return PromiseWrapper.resolve(rootProtoViewBuilder.build());
createViewInContainer(vcRef:api.ViewContainerRef, atIndex:number, protoViewRef:api.ProtoViewRef):List<api.ViewRef> {
var view = this._viewFactory.getView(_resolveProtoView(protoViewRef));
var vc = _resolveViewContainer(vcRef);
this._viewHydrator.hydrateViewInViewContainer(vc, view);
vc.insert(view, atIndex);
return _collectComponentChildViewRefs(view);
}
createView(protoViewRef:api.ProtoViewRef):List<api.ViewRef> {
return _collectComponentChildViewRefs(
this._viewFactory.getView(_resolveProtoView(protoViewRef))
);
destroyViewInContainer(vcRef:api.ViewContainerRef, atIndex:number):void {
var vc = _resolveViewContainer(vcRef);
var view = vc.detach(atIndex);
this._viewHydrator.dehydrateViewInViewContainer(vc, view);
this._viewFactory.returnView(view);
}
destroyView(viewRef:api.ViewRef) {
this._viewFactory.returnView(_resolveView(viewRef));
}
insertViewIntoContainer(vcRef:api.ViewContainerRef, viewRef:api.ViewRef, atIndex=-1):void {
insertViewIntoContainer(vcRef:api.ViewContainerRef, atIndex=-1, viewRef:api.ViewRef):void {
_resolveViewContainer(vcRef).insert(_resolveView(viewRef), atIndex);
}
@ -117,16 +125,39 @@ export class DirectDomRenderer extends api.Renderer {
_resolveViewContainer(vcRef).detach(atIndex);
}
setElementProperty(viewRef:api.ViewRef, elementIndex:number, propertyName:string, propertyValue:any):void {
_resolveView(viewRef).setElementProperty(elementIndex, propertyName, propertyValue);
createDynamicComponentView(hostViewRef:api.ViewRef, elementIndex:number, componentViewRef:api.ProtoViewRef):List<api.ViewRef> {
var hostView = _resolveView(hostViewRef);
var componentView = this._viewFactory.getView(_resolveProtoView(componentViewRef));
this._viewHydrator.hydrateDynamicComponentView(hostView, elementIndex, componentView);
return _collectComponentChildViewRefs(componentView);
}
setDynamicComponentView(viewRef:api.ViewRef, elementIndex:number, nestedViewRef:api.ViewRef):void {
_resolveView(viewRef).setComponentView(
this._shadowDomStrategy,
elementIndex,
_resolveView(nestedViewRef)
);
destroyDynamicComponentView(hostViewRef:api.ViewRef, 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.ViewRef, hostElementSelector, hostProtoViewRef:api.ProtoViewRef):List<api.ViewRef> {
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.ViewRef, hostViewRef:api.ViewRef):void {
var parentView = _resolveView(parentViewRef);
var hostView = _resolveView(hostViewRef);
this._viewHydrator.dehydrateInPlaceHostView(parentView, hostView);
}
setElementProperty(viewRef:api.ViewRef, elementIndex:number, propertyName:string, propertyValue:any):void {
_resolveView(viewRef).setElementProperty(elementIndex, propertyName, propertyValue);
}
setText(viewRef:api.ViewRef, textNodeIndex:number, text:string):void {

View File

@ -10,18 +10,15 @@ export class RenderProtoView {
element;
elementBinders:List<ElementBinder>;
isTemplateElement:boolean;
isRootView:boolean;
rootBindingOffset:int;
constructor({
elementBinders,
element,
isRootView
element
}) {
this.element = element;
this.elementBinders = elementBinders;
this.isTemplateElement = DOM.isTemplateElement(this.element);
this.isRootView = isRootView;
this.rootBindingOffset = (isPresent(this.element) && DOM.hasClass(this.element, NG_BINDING_CLASS)) ? 1 : 0;
}

View File

@ -25,7 +25,6 @@ export class ProtoViewBuilder {
constructor(rootElement) {
this.rootElement = rootElement;
this.elements = [];
this.isRootView = false;
this.variableBindings = MapWrapper.create();
}
@ -46,10 +45,6 @@ export class ProtoViewBuilder {
MapWrapper.set(this.variableBindings, value, name);
}
setIsRootView(value) {
this.isRootView = value;
}
build():api.ProtoViewDto {
var renderElementBinders = [];
@ -95,8 +90,7 @@ export class ProtoViewBuilder {
return new api.ProtoViewDto({
render: new directDomRenderer.DirectDomProtoViewRef(new RenderProtoView({
element: this.rootElement,
elementBinders: renderElementBinders,
isRootView: this.isRootView
elementBinders: renderElementBinders
})),
elementBinders: apiElementBinders,
variableBindings: this.variableBindings
@ -257,9 +251,9 @@ export class EventBuilder extends AstTransformer {
var result = new api.EventBinding(fullName, new ASTWithSource(adjustedAst, source.source, ''));
var event = new Event(name, target, fullName);
if (isBlank(target)) {
ListWrapper.push(this.localEvents, event);
ListWrapper.push(this.localEvents, event);
} else {
ListWrapper.push(this.globalEvents, event);
ListWrapper.push(this.globalEvents, event);
}
return result;
}

View File

@ -6,9 +6,6 @@ import {ViewContainer} from './view_container';
import {RenderProtoView} from './proto_view';
import {LightDom} from '../shadow_dom/light_dom';
import {Content} from '../shadow_dom/content_tag';
import {EventManager} from 'angular2/src/render/dom/events/event_manager';
import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy';
// import {EventDispatcher} from '../../api';
@ -30,14 +27,13 @@ export class RenderView {
contentTags: List<Content>;
lightDoms: List<LightDom>;
proto: RenderProtoView;
eventManager: EventManager;
_hydrated: boolean;
hydrated: boolean;
_eventDispatcher: any/*EventDispatcher*/;
_eventHandlerRemovers: List<Function>;
eventHandlerRemovers: List<Function>;
constructor(
proto:RenderProtoView, rootNodes:List,
boundTextNodes: List, boundElements:List, viewContainers:List, contentTags:List, eventManager: EventManager) {
boundTextNodes: List, boundElements:List, viewContainers:List, contentTags:List) {
this.proto = proto;
this.rootNodes = rootNodes;
this.boundTextNodes = boundTextNodes;
@ -45,15 +41,10 @@ export class RenderView {
this.viewContainers = viewContainers;
this.contentTags = contentTags;
this.lightDoms = ListWrapper.createFixedSize(boundElements.length);
this.eventManager = eventManager;
ListWrapper.fill(this.lightDoms, null);
this.componentChildViews = ListWrapper.createFixedSize(boundElements.length);
this._hydrated = false;
this._eventHandlerRemovers = null;
}
hydrated() {
return this._hydrated;
this.hydrated = false;
this.eventHandlerRemovers = null;
}
setElementProperty(elementIndex:number, propertyName:string, value:any) {
@ -65,138 +56,10 @@ export class RenderView {
DOM.setText(this.boundTextNodes[textIndex], value);
}
setComponentView(strategy: ShadowDomStrategy,
elementIndex:number, childView:RenderView) {
var element = this.boundElements[elementIndex];
var lightDom = strategy.constructLightDom(this, childView, element);
strategy.attachTemplate(element, childView);
this.lightDoms[elementIndex] = lightDom;
this.componentChildViews[elementIndex] = childView;
if (this._hydrated) {
childView.hydrate(lightDom);
if (isPresent(lightDom)) {
lightDom.redistribute();
}
}
}
getViewContainer(index:number):ViewContainer {
return this.viewContainers[index];
}
_getDestLightDom(binderIndex) {
var binder = this.proto.elementBinders[binderIndex];
var destLightDom = null;
if (binder.parentIndex !== -1 && binder.distanceToParent === 1) {
destLightDom = this.lightDoms[binder.parentIndex];
}
return destLightDom;
}
/**
* 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 does not attach/detach the view from the view
* tree.
*/
hydrate(hostLightDom: LightDom) {
if (this._hydrated) throw new BaseException('The view is already hydrated.');
this._hydrated = true;
// viewContainers and content tags
for (var i = 0; i < this.viewContainers.length; i++) {
var vc = this.viewContainers[i];
var destLightDom = this._getDestLightDom(i);
if (isPresent(vc)) {
vc.hydrate(destLightDom, hostLightDom);
}
var ct = this.contentTags[i];
if (isPresent(ct)) {
ct.hydrate(destLightDom);
}
}
// componentChildViews
for (var i = 0; i < this.componentChildViews.length; i++) {
var cv = this.componentChildViews[i];
if (isPresent(cv)) {
cv.hydrate(this.lightDoms[i]);
}
}
for (var i = 0; i < this.lightDoms.length; ++i) {
var lightDom = this.lightDoms[i];
if (isPresent(lightDom)) {
lightDom.redistribute();
}
}
//add global events
this._eventHandlerRemovers = ListWrapper.create();
var binders = this.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(binderIdx, globalEvent.name, globalEvent.target, globalEvent.fullName);
ListWrapper.push(this._eventHandlerRemovers, remover);
}
}
}
}
_createGlobalEventListener(elementIndex, eventName, eventTarget, fullName): Function {
return this.eventManager.addGlobalEventListener(eventTarget, eventName, (event) => {
this.dispatchEvent(elementIndex, fullName, event);
});
}
dehydrate() {
// Note: preserve the opposite order of the hydration process.
// componentChildViews
for (var i = 0; i < this.componentChildViews.length; i++) {
var cv = this.componentChildViews[i];
if (isPresent(cv)) {
cv.dehydrate();
if (this.proto.elementBinders[i].hasDynamicComponent()) {
ViewContainer.removeViewNodes(cv);
this.lightDoms[i] = null;
this.componentChildViews[i] = null;
}
}
}
// viewContainers and content tags
if (isPresent(this.viewContainers)) {
for (var i = 0; i < this.viewContainers.length; i++) {
var vc = this.viewContainers[i];
if (isPresent(vc)) {
vc.dehydrate();
}
var ct = this.contentTags[i];
if (isPresent(ct)) {
ct.dehydrate();
}
}
}
//remove global events
for (var i = 0; i < this._eventHandlerRemovers.length; i++) {
this._eventHandlerRemovers[i]();
}
this._eventHandlerRemovers = null;
this._eventDispatcher = null;
this._hydrated = false;
}
setEventDispatcher(dispatcher:any/*EventDispatcher*/) {
this._eventDispatcher = dispatcher;
}

View File

@ -4,88 +4,64 @@ import {DOM} from 'angular2/src/dom/dom_adapter';
import * as viewModule from './view';
import * as ldModule from '../shadow_dom/light_dom';
import * as vfModule from './view_factory';
export class ViewContainer {
_viewFactory: vfModule.ViewFactory;
templateElement;
_views: List<viewModule.RenderView>;
_lightDom: ldModule.LightDom;
_hostLightDom: ldModule.LightDom;
_hydrated: boolean;
views: List<viewModule.RenderView>;
lightDom: ldModule.LightDom;
hostLightDom: ldModule.LightDom;
hydrated: boolean;
constructor(viewFactory: vfModule.ViewFactory,
templateElement) {
this._viewFactory = viewFactory;
constructor(templateElement) {
this.templateElement = templateElement;
// The order in this list matches the DOM order.
this._views = [];
this._hostLightDom = null;
this._hydrated = false;
}
hydrate(destLightDom: ldModule.LightDom, hostLightDom: ldModule.LightDom) {
this._hydrated = true;
this._hostLightDom = hostLightDom;
this._lightDom = destLightDom;
}
dehydrate() {
if (isBlank(this._lightDom)) {
for (var i = this._views.length - 1; i >= 0; i--) {
var view = this._views[i];
ViewContainer.removeViewNodes(view);
this._viewFactory.returnView(view);
}
this._views = [];
} else {
for (var i=0; i<this._views.length; i++) {
var view = this._views[i];
this._viewFactory.returnView(view);
}
this._views = [];
this._lightDom.redistribute();
}
this._hostLightDom = null;
this._lightDom = null;
this._hydrated = false;
this.views = [];
this.hostLightDom = null;
this.hydrated = false;
}
get(index: number): viewModule.RenderView {
return this._views[index];
return this.views[index];
}
size() {
return this._views.length;
return this.views.length;
}
_siblingToInsertAfter(index: number) {
if (index == 0) return this.templateElement;
return ListWrapper.last(this._views[index - 1].rootNodes);
return ListWrapper.last(this.views[index - 1].rootNodes);
}
_checkHydrated() {
if (!this._hydrated) throw new BaseException(
if (!this.hydrated) throw new BaseException(
'Cannot change dehydrated ViewContainer');
}
insert(view, atIndex=-1): viewModule.RenderView {
if (!view.hydrated()) {
view.hydrate(this._hostLightDom);
clear() {
this._checkHydrated();
for (var i=this.views.length-1; i>=0; i--) {
this.detach(i);
}
if (atIndex == -1) atIndex = this._views.length;
ListWrapper.insert(this._views, atIndex, view);
if (isPresent(this.lightDom)) {
this.lightDom.redistribute();
}
}
if (isBlank(this._lightDom)) {
insert(view, atIndex=-1): viewModule.RenderView {
this._checkHydrated();
if (atIndex == -1) atIndex = this.views.length;
ListWrapper.insert(this.views, atIndex, view);
if (isBlank(this.lightDom)) {
ViewContainer.moveViewNodesAfterSibling(this._siblingToInsertAfter(atIndex), view);
} else {
this._lightDom.redistribute();
this.lightDom.redistribute();
}
// new content tags might have appeared, we need to redistribute.
if (isPresent(this._hostLightDom)) {
this._hostLightDom.redistribute();
if (isPresent(this.hostLightDom)) {
this.hostLightDom.redistribute();
}
return view;
}
@ -97,32 +73,31 @@ export class ViewContainer {
detach(atIndex:number) {
this._checkHydrated();
var detachedView = this.get(atIndex);
ListWrapper.removeAt(this._views, atIndex);
if (isBlank(this._lightDom)) {
ListWrapper.removeAt(this.views, atIndex);
if (isBlank(this.lightDom)) {
ViewContainer.removeViewNodes(detachedView);
} else {
this._lightDom.redistribute();
this.lightDom.redistribute();
}
// content tags might have disappeared we need to do redistribution.
if (isPresent(this._hostLightDom)) {
this._hostLightDom.redistribute();
if (isPresent(this.hostLightDom)) {
this.hostLightDom.redistribute();
}
return detachedView;
}
contentTagContainers() {
return this._views;
return this.views;
}
nodes():List {
var r = [];
for (var i = 0; i < this._views.length; ++i) {
r = ListWrapper.concat(r, this._views[i].rootNodes);
for (var i = 0; i < this.views.length; ++i) {
r = ListWrapper.concat(r, this.views[i].rootNodes);
}
return r;
}
static moveViewNodesAfterSibling(sibling, view) {
for (var i = view.rootNodes.length - 1; i >= 0; --i) {
DOM.insertAfter(sibling, view.rootNodes[i]);

View File

@ -31,6 +31,10 @@ export class ViewFactory {
this._shadowDomStrategy = shadowDomStrategy;
}
createInPlaceHostView(hostElementSelector, hostProtoView:pvModule.RenderProtoView):viewModule.RenderView {
return this._createView(hostProtoView, hostElementSelector);
}
getView(protoView:pvModule.RenderProtoView):viewModule.RenderView {
var pooledViews = MapWrapper.get(this._pooledViewsPerProtoView, protoView);
if (isPresent(pooledViews)) {
@ -40,12 +44,12 @@ export class ViewFactory {
}
return result;
}
return this._createView(protoView);
return this._createView(protoView, null);
}
returnView(view:viewModule.RenderView) {
if (view.hydrated()) {
view.dehydrate();
if (view.hydrated) {
throw new BaseException('View is still hydrated');
}
var protoView = view.proto;
var pooledViews = MapWrapper.get(this._pooledViewsPerProtoView, protoView);
@ -58,8 +62,8 @@ export class ViewFactory {
}
}
_createView(protoView:pvModule.RenderProtoView): viewModule.RenderView {
var rootElementClone = protoView.isRootView ? protoView.element : DOM.importIntoDoc(protoView.element);
_createView(protoView:pvModule.RenderProtoView, inplaceElement): viewModule.RenderView {
var rootElementClone = isPresent(inplaceElement) ? inplaceElement : DOM.importIntoDoc(protoView.element);
var elementsWithBindingsDynamic;
if (protoView.isTemplateElement) {
elementsWithBindingsDynamic = DOM.querySelectorAll(DOM.content(rootElementClone), NG_BINDING_CLASS_SELECTOR);
@ -110,7 +114,7 @@ export class ViewFactory {
// viewContainers
var viewContainer = null;
if (isBlank(binder.componentId) && isPresent(binder.nestedProtoView)) {
viewContainer = new vcModule.ViewContainer(this, element);
viewContainer = new vcModule.ViewContainer(element);
}
viewContainers[binderIdx] = viewContainer;
@ -124,7 +128,7 @@ export class ViewFactory {
var view = new viewModule.RenderView(
protoView, viewRootNodes,
boundTextNodes, boundElements, viewContainers, contentTags, this._eventManager
boundTextNodes, boundElements, viewContainers, contentTags
);
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
@ -133,8 +137,8 @@ export class ViewFactory {
// static child components
if (binder.hasStaticComponent()) {
var childView = this._createView(binder.nestedProtoView);
view.setComponentView(this._shadowDomStrategy, binderIdx, childView);
var childView = this._createView(binder.nestedProtoView, null);
this.setComponentView(view, binderIdx, childView);
}
// events
@ -145,10 +149,6 @@ export class ViewFactory {
}
}
if (protoView.isRootView) {
view.hydrate(null);
}
return view;
}
@ -157,4 +157,15 @@ export class ViewFactory {
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
setComponentView(hostView:viewModule.RenderView, elementIndex:number, componentView:viewModule.RenderView) {
var element = hostView.boundElements[elementIndex];
var lightDom = this._shadowDomStrategy.constructLightDom(hostView, componentView, element);
this._shadowDomStrategy.attachTemplate(element, componentView);
hostView.lightDoms[elementIndex] = lightDom;
hostView.componentChildViews[elementIndex] = componentView;
}
}

View File

@ -0,0 +1,192 @@
import {Injectable} from 'angular2/di';
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';
/**
* 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;
constructor(eventManager:EventManager, viewFactory:ViewFactory) {
this._eventManager = eventManager;
this._viewFactory = viewFactory;
}
hydrateDynamicComponentView(hostView:viewModule.RenderView, boundElementIndex:number, componentView:viewModule.RenderView) {
this._viewFactory.setComponentView(hostView, boundElementIndex, componentView);
var lightDom = hostView.lightDoms[boundElementIndex];
this._viewHydrateRecurse(componentView, lightDom);
if (isPresent(lightDom)) {
lightDom.redistribute();
}
}
dehydrateDynamicComponentView(parentView:viewModule.RenderView, 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.RenderView, hostView:viewModule.RenderView) {
if (isPresent(parentView)) {
throw new BaseException('Not supported yet');
}
this._viewHydrateRecurse(hostView, null);
}
dehydrateInPlaceHostView(parentView:viewModule.RenderView, hostView:viewModule.RenderView) {
if (isPresent(parentView)) {
throw new BaseException('Not supported yet');
}
this._viewDehydrateRecurse(hostView);
}
hydrateViewInViewContainer(viewContainer:vcModule.ViewContainer, view:viewModule.RenderView) {
this._viewHydrateRecurse(view, viewContainer.hostLightDom);
}
dehydrateViewInViewContainer(viewContainer:vcModule.ViewContainer, view:viewModule.RenderView) {
this._viewDehydrateRecurse(view);
}
_getViewDestLightDom(view, binderIndex) {
var binder = view.proto.elementBinders[binderIndex];
var destLightDom = null;
if (binder.parentIndex !== -1 && binder.distanceToParent === 1) {
destLightDom = view.lightDoms[binder.parentIndex];
}
return destLightDom;
}
_viewHydrateRecurse(view, hostLightDom: ldModule.LightDom) {
if (view.hydrated) throw new BaseException('The view is already hydrated.');
view.hydrated = true;
// viewContainers and content tags
for (var i = 0; i < view.viewContainers.length; i++) {
var vc = view.viewContainers[i];
var destLightDom = this._getViewDestLightDom(view, i);
if (isPresent(vc)) {
this._viewContainerHydrateRecurse(vc, destLightDom, hostLightDom);
}
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);
view.lightDoms[i] = null;
view.componentChildViews[i] = null;
}
}
}
// 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.eventHandlerRemovers = null;
view.setEventDispatcher(null);
view.hydrated = false;
}
_viewContainerHydrateRecurse(viewContainer, destLightDom: ldModule.LightDom, hostLightDom: ldModule.LightDom) {
viewContainer.hydrated = true;
viewContainer.hostLightDom = hostLightDom;
viewContainer.lightDom = destLightDom;
}
_viewContainerDehydrateRecurse(viewContainer) {
for (var i=0; i<viewContainer.views.length; i++) {
this._viewDehydrateRecurse(viewContainer.views[i]);
}
viewContainer.clear();
viewContainer.hostLightDom = null;
viewContainer.lightDom = null;
viewContainer.hydrated = false;
}
}