feat(view): add imperative views

This commit is contained in:
Tobias Bosch
2015-04-20 11:34:53 -07:00
parent 817c79ca77
commit ada1e642c5
28 changed files with 458 additions and 120 deletions

View File

@ -165,6 +165,14 @@ export class Renderer {
*/
createHostProtoView(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 RenderProtoView. Non recursive so that
* we don't need to serialize all possible components over the wire,

View File

@ -12,6 +12,7 @@ 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';
import {ViewContainer} from './view/view_container';
function _resolveViewContainer(vc:api.ViewContainerRef) {
return _resolveView(vc.view).getOrCreateViewContainer(vc.elementIndex);
@ -90,6 +91,12 @@ export class DirectDomRenderer extends api.Renderer {
return PromiseWrapper.resolve(hostProtoViewBuilder.build());
}
createImperativeComponentProtoView(rendererId):Promise<api.ProtoViewDto> {
var protoViewBuilder = new ProtoViewBuilder(null);
protoViewBuilder.setImperativeRendererId(rendererId);
return PromiseWrapper.resolve(protoViewBuilder.build());
}
compile(template:api.ViewDefinition):Promise<api.ProtoViewDto> {
// Note: compiler already uses a DirectDomProtoViewRef, so we don't
// need to do anything here
@ -156,6 +163,21 @@ export class DirectDomRenderer extends api.Renderer {
this._viewHydrator.dehydrateInPlaceHostView(parentView, hostView);
}
setImperativeComponentRootNodes(parentViewRef:api.ViewRef, 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.ViewRef, elementIndex:number, propertyName:string, propertyValue:any):void {
_resolveView(viewRef).setElementProperty(elementIndex, propertyName, propertyValue);
}

View File

@ -11,15 +11,23 @@ export class RenderProtoView {
elementBinders:List<ElementBinder>;
isTemplateElement:boolean;
rootBindingOffset:int;
imperativeRendererId:string;
constructor({
elementBinders,
element
element,
imperativeRendererId
}) {
this.element = element;
this.elementBinders = elementBinders;
this.isTemplateElement = DOM.isTemplateElement(this.element);
this.rootBindingOffset = (isPresent(this.element) && DOM.hasClass(this.element, NG_BINDING_CLASS)) ? 1 : 0;
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<RenderProtoView>) {

View File

@ -20,12 +20,18 @@ export class ProtoViewBuilder {
rootElement;
variableBindings: Map<string, string>;
elements:List<ElementBinderBuilder>;
isRootView:boolean;
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 {
@ -90,7 +96,8 @@ export class ProtoViewBuilder {
return new api.ProtoViewDto({
render: new directDomRenderer.DirectDomProtoViewRef(new RenderProtoView({
element: this.rootElement,
elementBinders: renderElementBinders
elementBinders: renderElementBinders,
imperativeRendererId: this.imperativeRendererId
})),
elementBinders: apiElementBinders,
variableBindings: this.variableBindings

View File

@ -31,6 +31,9 @@ export class RenderView {
hydrated: boolean;
_eventDispatcher: any/*EventDispatcher*/;
eventHandlerRemovers: List<Function>;
/// Host views that were added by an imperative view.
/// This is a dynamically growing / shrinking array.
imperativeHostViews: List<RenderView>;
constructor(
proto:RenderProtoView, rootNodes:List,
@ -46,7 +49,8 @@ export class RenderView {
this.componentChildViews = ListWrapper.createFixedSize(boundElements.length);
this.hostLightDom = null;
this.hydrated = false;
this.eventHandlerRemovers = null;
this.eventHandlerRemovers = [];
this.imperativeHostViews = [];
}
getDirectParentLightDom(boundElementIndex:number) {

View File

@ -58,6 +58,12 @@ export class ViewFactory {
}
_createView(protoView:pvModule.RenderProtoView, inplaceElement): viewModule.RenderView {
if (isPresent(protoView.imperativeRendererId)) {
return new viewModule.RenderView(
protoView, [], [], [], []
);
}
var rootElementClone = isPresent(inplaceElement) ? inplaceElement : DOM.importIntoDoc(protoView.element);
var elementsWithBindingsDynamic;
if (protoView.isTemplateElement) {
@ -125,7 +131,7 @@ export class ViewFactory {
// static child components
if (binder.hasStaticComponent()) {
var childView = this._createView(binder.nestedProtoView, null);
this.setComponentView(view, binderIdx, childView);
ViewFactory.setComponentView(this._shadowDomStrategy, view, binderIdx, childView);
}
// events
@ -148,10 +154,10 @@ export class ViewFactory {
// 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) {
static setComponentView(shadowDomStrategy:ShadowDomStrategy, 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);
var lightDom = shadowDomStrategy.constructLightDom(hostView, componentView, element);
shadowDomStrategy.attachTemplate(element, componentView);
hostView.lightDoms[elementIndex] = lightDom;
hostView.componentChildViews[elementIndex] = componentView;
}

View File

@ -7,6 +7,7 @@ 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
@ -23,14 +24,16 @@ import * as viewModule from './view';
export class RenderViewHydrator {
_eventManager:EventManager;
_viewFactory:ViewFactory;
_shadowDomStrategy:ShadowDomStrategy;
constructor(eventManager:EventManager, viewFactory:ViewFactory) {
constructor(eventManager:EventManager, viewFactory:ViewFactory, shadowDomStrategy:ShadowDomStrategy) {
this._eventManager = eventManager;
this._viewFactory = viewFactory;
this._shadowDomStrategy = shadowDomStrategy;
}
hydrateDynamicComponentView(hostView:viewModule.RenderView, boundElementIndex:number, componentView:viewModule.RenderView) {
this._viewFactory.setComponentView(hostView, boundElementIndex, componentView);
ViewFactory.setComponentView(this._shadowDomStrategy, hostView, boundElementIndex, componentView);
var lightDom = hostView.lightDoms[boundElementIndex];
this._viewHydrateRecurse(componentView, lightDom);
if (isPresent(lightDom)) {
@ -50,15 +53,17 @@ export class RenderViewHydrator {
hydrateInPlaceHostView(parentView:viewModule.RenderView, hostView:viewModule.RenderView) {
if (isPresent(parentView)) {
throw new BaseException('Not supported yet');
ListWrapper.push(parentView.imperativeHostViews, hostView);
}
this._viewHydrateRecurse(hostView, null);
}
dehydrateInPlaceHostView(parentView:viewModule.RenderView, hostView:viewModule.RenderView) {
if (isPresent(parentView)) {
throw new BaseException('Not supported yet');
ListWrapper.remove(parentView.imperativeHostViews, hostView);
}
vcModule.ViewContainer.removeViewNodes(hostView);
hostView.rootNodes = [];
this._viewDehydrateRecurse(hostView);
}
@ -130,12 +135,24 @@ export class RenderViewHydrator {
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++) {