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:
89
modules/angular2/src/render/api.js
vendored
89
modules/angular2/src/render/api.js
vendored
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
147
modules/angular2/src/render/dom/view/view.js
vendored
147
modules/angular2/src/render/dom/view/view.js
vendored
@ -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;
|
||||
}
|
||||
|
@ -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]);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
192
modules/angular2/src/render/dom/view/view_hydrator.js
vendored
Normal file
192
modules/angular2/src/render/dom/view/view_hydrator.js
vendored
Normal 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;
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user