refactor(view): provide ViewContainers dynamically on any element

This commit is contained in:
Tobias Bosch 2015-04-16 15:38:28 -07:00
parent eac5c88893
commit f830cfca12
26 changed files with 467 additions and 210 deletions

View File

@ -105,8 +105,8 @@ function _injectorBindings(appComponentType): List<Binding> {
// TODO(tbosch): We need an explicit factory here, as // TODO(tbosch): We need an explicit factory here, as
// we are getting errors in dart2js with mirrors... // we are getting errors in dart2js with mirrors...
bind(ViewFactory).toFactory( bind(ViewFactory).toFactory(
(capacity, renderer) => new ViewFactory(capacity, renderer), (capacity, renderer, appViewHydrator) => new ViewFactory(capacity, renderer, appViewHydrator),
[VIEW_POOL_CAPACITY, Renderer] [VIEW_POOL_CAPACITY, Renderer, AppViewHydrator]
), ),
bind(VIEW_POOL_CAPACITY).toValue(10000), bind(VIEW_POOL_CAPACITY).toValue(10000),
AppViewHydrator, AppViewHydrator,

View File

@ -35,6 +35,10 @@ export class ElementRef {
return this.elementInjector._preBuiltObjects.view; return this.elementInjector._preBuiltObjects.view;
} }
get viewContainer() {
return this.hostView.getOrCreateViewContainer(this.boundElementIndex);
}
get injector() { get injector() {
return this.elementInjector._lightDomAppInjector; return this.elementInjector._lightDomAppInjector;
} }
@ -298,13 +302,10 @@ export class DirectiveBinding extends ResolvedBinding {
export class PreBuiltObjects { export class PreBuiltObjects {
view:viewModule.AppView; view:viewModule.AppView;
element:NgElement; element:NgElement;
viewContainer:ViewContainer;
changeDetector:ChangeDetector; changeDetector:ChangeDetector;
constructor(view, element:NgElement, viewContainer:ViewContainer, constructor(view, element:NgElement, changeDetector:ChangeDetector) {
changeDetector:ChangeDetector) {
this.view = view; this.view = view;
this.element = element; this.element = element;
this.viewContainer = viewContainer;
this.changeDetector = changeDetector; this.changeDetector = changeDetector;
} }
} }
@ -929,7 +930,7 @@ export class ElementInjector extends TreeNode {
// TODO: AppView should not be injectable. Remove it. // TODO: AppView should not be injectable. Remove it.
if (keyId === staticKeys.viewId) return this._preBuiltObjects.view; if (keyId === staticKeys.viewId) return this._preBuiltObjects.view;
if (keyId === staticKeys.ngElementId) return this._preBuiltObjects.element; if (keyId === staticKeys.ngElementId) return this._preBuiltObjects.element;
if (keyId === staticKeys.viewContainerId) return this._preBuiltObjects.viewContainer; if (keyId === staticKeys.viewContainerId) return this._preBuiltObjects.view.getOrCreateViewContainer(this._proto.index);
if (keyId === staticKeys.changeDetectorRefId) return this._preBuiltObjects.changeDetector.ref; if (keyId === staticKeys.changeDetectorRefId) return this._preBuiltObjects.changeDetector.ref;
//TODO add other objects as needed //TODO add other objects as needed
@ -984,6 +985,18 @@ export class ElementInjector extends TreeNode {
getExportImplicitName() { getExportImplicitName() {
return this._proto.exportImplicitName; return this._proto.exportImplicitName;
} }
getLightDomAppInjector() {
return this._lightDomAppInjector;
}
getHost() {
return this._host;
}
getBoundElementIndex() {
return this._proto.index;
}
} }
class OutOfBoundsAccess extends Error { class OutOfBoundsAccess extends Error {

View File

@ -8,6 +8,8 @@ import {SetterFn} from 'angular2/src/reflection/types';
import {IMPLEMENTS, int, isPresent, isBlank, BaseException} from 'angular2/src/facade/lang'; import {IMPLEMENTS, int, isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
import {ViewContainer} from './view_container'; import {ViewContainer} from './view_container';
import * as renderApi from 'angular2/src/render/api'; import * as renderApi from 'angular2/src/render/api';
import * as vfModule from './view_factory';
import * as vhModule from './view_hydrator';
/** /**
* Const of making objects: http://jsperf.com/instantiate-size-of-object * Const of making objects: http://jsperf.com/instantiate-size-of-object
@ -17,7 +19,6 @@ import * as renderApi from 'angular2/src/render/api';
// TODO(tbosch): this is not supported in dart2js (no '.' is allowed) // TODO(tbosch): this is not supported in dart2js (no '.' is allowed)
// @IMPLEMENTS(renderApi.EventDispatcher) // @IMPLEMENTS(renderApi.EventDispatcher)
export class AppView { export class AppView {
render:renderApi.ViewRef; render:renderApi.ViewRef;
/// This list matches the _nodes list. It is sparse, since only Elements have ElementInjector /// This list matches the _nodes list. It is sparse, since only Elements have ElementInjector
rootElementInjectors:List<ElementInjector>; rootElementInjectors:List<ElementInjector>;
@ -28,6 +29,8 @@ export class AppView {
preBuiltObjects: List<PreBuiltObjects>; preBuiltObjects: List<PreBuiltObjects>;
proto: AppProtoView; proto: AppProtoView;
renderer: renderApi.Renderer; renderer: renderApi.Renderer;
viewFactory: vfModule.ViewFactory;
viewHydrator: vhModule.AppViewHydrator;
/** /**
* The context against which data-binding expressions in this view are evaluated against. * The context against which data-binding expressions in this view are evaluated against.
@ -43,30 +46,40 @@ export class AppView {
*/ */
locals:Locals; locals:Locals;
constructor(renderer:renderApi.Renderer, proto:AppProtoView, protoLocals:Map) { constructor(renderer:renderApi.Renderer, viewFactory:vfModule.ViewFactory, viewHydrator:vhModule.AppViewHydrator, proto:AppProtoView, protoLocals:Map) {
this.render = null; this.render = null;
this.proto = proto; this.proto = proto;
this.changeDetector = null; this.changeDetector = null;
this.elementInjectors = null; this.elementInjectors = null;
this.rootElementInjectors = null; this.rootElementInjectors = null;
this.componentChildViews = null; this.componentChildViews = null;
this.viewContainers = null; this.viewContainers = ListWrapper.createFixedSize(this.proto.elementBinders.length);
this.preBuiltObjects = null; this.preBuiltObjects = null;
this.context = null; this.context = null;
this.locals = new Locals(null, MapWrapper.clone(protoLocals)); //TODO optimize this this.locals = new Locals(null, MapWrapper.clone(protoLocals)); //TODO optimize this
this.renderer = renderer; this.renderer = renderer;
this.viewFactory = viewFactory;
this.viewHydrator = viewHydrator;
} }
init(changeDetector:ChangeDetector, elementInjectors:List, rootElementInjectors:List, init(changeDetector:ChangeDetector, elementInjectors:List, rootElementInjectors:List,
viewContainers:List, preBuiltObjects:List, componentChildViews:List) { preBuiltObjects:List, componentChildViews:List) {
this.changeDetector = changeDetector; this.changeDetector = changeDetector;
this.elementInjectors = elementInjectors; this.elementInjectors = elementInjectors;
this.rootElementInjectors = rootElementInjectors; this.rootElementInjectors = rootElementInjectors;
this.viewContainers = viewContainers;
this.preBuiltObjects = preBuiltObjects; this.preBuiltObjects = preBuiltObjects;
this.componentChildViews = componentChildViews; this.componentChildViews = componentChildViews;
} }
getOrCreateViewContainer(boundElementIndex:number) {
var viewContainer = this.viewContainers[boundElementIndex];
if (isBlank(viewContainer)) {
viewContainer = new ViewContainer(this, this.proto.elementBinders[boundElementIndex].nestedProtoView, this.elementInjectors[boundElementIndex]);
this.viewContainers[boundElementIndex] = viewContainer;
}
return viewContainer;
}
setLocal(contextName: string, value) { setLocal(contextName: string, value) {
if (!this.hydrated()) throw new BaseException('Cannot set locals on dehydrated view.'); if (!this.hydrated()) throw new BaseException('Cannot set locals on dehydrated view.');
if (!MapWrapper.contains(this.proto.variableBindings, contextName)) { if (!MapWrapper.contains(this.proto.variableBindings, contextName)) {

View File

@ -4,45 +4,31 @@ import {Injector} from 'angular2/di';
import * as eiModule from 'angular2/src/core/compiler/element_injector'; import * as eiModule from 'angular2/src/core/compiler/element_injector';
import {isPresent, isBlank} from 'angular2/src/facade/lang'; import {isPresent, isBlank} from 'angular2/src/facade/lang';
import * as renderApi from 'angular2/src/render/api';
import * as viewModule from './view'; import * as viewModule from './view';
import * as vfModule from './view_factory'; import {ViewContainerRef} from 'angular2/src/render/api';
import * as vhModule from './view_hydrator';
import {Renderer} from 'angular2/src/render/api';
/** /**
* @exportedAs angular2/view * @exportedAs angular2/view
*/ */
export class ViewContainer { export class ViewContainer {
viewFactory: vfModule.ViewFactory;
viewHydrator: vhModule.AppViewHydrator;
renderer: Renderer;
render:renderApi.ViewContainerRef;
parentView: viewModule.AppView; parentView: viewModule.AppView;
defaultProtoView: viewModule.AppProtoView; defaultProtoView: viewModule.AppProtoView;
_views: List<viewModule.AppView>; _views: List<viewModule.AppView>;
elementInjector: eiModule.ElementInjector; elementInjector: eiModule.ElementInjector;
appInjector: Injector;
hostElementInjector: eiModule.ElementInjector;
constructor(viewFactory:vfModule.ViewFactory, constructor(parentView: viewModule.AppView,
renderer: Renderer,
parentView: viewModule.AppView,
defaultProtoView: viewModule.AppProtoView, defaultProtoView: viewModule.AppProtoView,
elementInjector: eiModule.ElementInjector) { elementInjector: eiModule.ElementInjector) {
this.viewFactory = viewFactory;
this.viewHydrator = null;
this.renderer = renderer;
this.render = null;
this.parentView = parentView; this.parentView = parentView;
this.defaultProtoView = defaultProtoView; this.defaultProtoView = defaultProtoView;
this.elementInjector = elementInjector; this.elementInjector = elementInjector;
// The order in this list matches the DOM order. // The order in this list matches the DOM order.
this._views = []; this._views = [];
this.appInjector = null; }
this.hostElementInjector = null;
getRender() {
return new ViewContainerRef(this.parentView.render, this.elementInjector.getBoundElementIndex());
} }
internalClearWithoutRender() { internalClearWithoutRender() {
@ -71,22 +57,22 @@ export class ViewContainer {
} }
hydrated() { hydrated() {
return isPresent(this.appInjector); return this.parentView.hydrated();
} }
// TODO(rado): profile and decide whether bounds checks should be added // TODO(rado): profile and decide whether bounds checks should be added
// to the methods below. // to the methods below.
create(atIndex=-1, protoView:viewModule.AppProtoView = null): viewModule.AppView { create(atIndex=-1, protoView:viewModule.AppProtoView = null, injector:Injector = null): viewModule.AppView {
if (atIndex == -1) atIndex = this._views.length; if (atIndex == -1) atIndex = this._views.length;
if (!this.hydrated()) throw new BaseException( if (!this.hydrated()) throw new BaseException(
'Cannot create views on a dehydrated ViewContainer'); 'Cannot create views on a dehydrated ViewContainer');
if (isBlank(protoView)) { if (isBlank(protoView)) {
protoView = this.defaultProtoView; protoView = this.defaultProtoView;
} }
var newView = this.viewFactory.getView(protoView); var newView = this.parentView.viewFactory.getView(protoView);
// insertion must come before hydration so that element injector trees are attached. // insertion must come before hydration so that element injector trees are attached.
this._insertInjectors(newView, atIndex); this._insertInjectors(newView, atIndex);
this.viewHydrator.hydrateViewInViewContainer(this, atIndex, newView); this.parentView.viewHydrator.hydrateViewInViewContainer(this, atIndex, newView, injector);
return newView; return newView;
} }
@ -95,7 +81,7 @@ export class ViewContainer {
if (atIndex == -1) atIndex = this._views.length; if (atIndex == -1) atIndex = this._views.length;
this._insertInjectors(view, atIndex); this._insertInjectors(view, atIndex);
this.parentView.changeDetector.addChild(view.changeDetector); this.parentView.changeDetector.addChild(view.changeDetector);
this.renderer.insertViewIntoContainer(this.render, atIndex, view.render); this.parentView.renderer.insertViewIntoContainer(this.getRender(), atIndex, view.render);
return view; return view;
} }
@ -110,9 +96,9 @@ export class ViewContainer {
if (atIndex == -1) atIndex = this._views.length - 1; if (atIndex == -1) atIndex = this._views.length - 1;
var view = this._views[atIndex]; var view = this._views[atIndex];
// opposite order as in create // opposite order as in create
this.viewHydrator.dehydrateViewInViewContainer(this, atIndex, view); this.parentView.viewHydrator.dehydrateViewInViewContainer(this, atIndex, view);
this._detachInjectors(atIndex); this._detachInjectors(atIndex);
this.viewFactory.returnView(view); this.parentView.viewFactory.returnView(view);
// view is intentionally not returned to the client. // view is intentionally not returned to the client.
} }
@ -124,7 +110,7 @@ export class ViewContainer {
if (atIndex == -1) atIndex = this._views.length - 1; if (atIndex == -1) atIndex = this._views.length - 1;
var detachedView = this._detachInjectors(atIndex); var detachedView = this._detachInjectors(atIndex);
detachedView.changeDetector.remove(); detachedView.changeDetector.remove();
this.renderer.detachViewFromContainer(this.render, atIndex); this.parentView.renderer.detachViewFromContainer(this.getRender(), atIndex);
return detachedView; return detachedView;
} }

View File

@ -3,9 +3,9 @@ import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src
import * as eli from './element_injector'; import * as eli from './element_injector';
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang'; import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
import {NgElement} from 'angular2/src/core/compiler/ng_element'; import {NgElement} from 'angular2/src/core/compiler/ng_element';
import * as vcModule from './view_container';
import * as viewModule from './view'; import * as viewModule from './view';
import {Renderer} from 'angular2/src/render/api'; import {Renderer} from 'angular2/src/render/api';
import {AppViewHydrator} from './view_hydrator';
// TODO(tbosch): Make this an OpaqueToken as soon as our transpiler supports this! // TODO(tbosch): Make this an OpaqueToken as soon as our transpiler supports this!
export const VIEW_POOL_CAPACITY = 'ViewFactory.viewPoolCapacity'; export const VIEW_POOL_CAPACITY = 'ViewFactory.viewPoolCapacity';
@ -15,11 +15,13 @@ export class ViewFactory {
_poolCapacityPerProtoView:number; _poolCapacityPerProtoView:number;
_pooledViewsPerProtoView:Map<viewModule.AppProtoView, List<viewModule.AppView>>; _pooledViewsPerProtoView:Map<viewModule.AppProtoView, List<viewModule.AppView>>;
_renderer:Renderer; _renderer:Renderer;
_viewHydrator:AppViewHydrator;
constructor(@Inject(VIEW_POOL_CAPACITY) poolCapacityPerProtoView, renderer:Renderer) { constructor(@Inject(VIEW_POOL_CAPACITY) poolCapacityPerProtoView, renderer:Renderer, viewHydrator:AppViewHydrator) {
this._poolCapacityPerProtoView = poolCapacityPerProtoView; this._poolCapacityPerProtoView = poolCapacityPerProtoView;
this._pooledViewsPerProtoView = MapWrapper.create(); this._pooledViewsPerProtoView = MapWrapper.create();
this._renderer = renderer; this._renderer = renderer;
this._viewHydrator = viewHydrator;
} }
getView(protoView:viewModule.AppProtoView):viewModule.AppView { getView(protoView:viewModule.AppProtoView):viewModule.AppView {
@ -50,7 +52,7 @@ export class ViewFactory {
} }
_createView(protoView:viewModule.AppProtoView): viewModule.AppView { _createView(protoView:viewModule.AppProtoView): viewModule.AppView {
var view = new viewModule.AppView(this._renderer, protoView, protoView.protoLocals); var view = new viewModule.AppView(this._renderer, this, this._viewHydrator, protoView, protoView.protoLocals);
var changeDetector = protoView.protoChangeDetector.instantiate(view, protoView.bindings, var changeDetector = protoView.protoChangeDetector.instantiate(view, protoView.bindings,
protoView.getVariableBindings(), protoView.getdirectiveRecords()); protoView.getVariableBindings(), protoView.getdirectiveRecords());
@ -58,7 +60,6 @@ export class ViewFactory {
var elementInjectors = ListWrapper.createFixedSize(binders.length); var elementInjectors = ListWrapper.createFixedSize(binders.length);
var rootElementInjectors = []; var rootElementInjectors = [];
var preBuiltObjects = ListWrapper.createFixedSize(binders.length); var preBuiltObjects = ListWrapper.createFixedSize(binders.length);
var viewContainers = ListWrapper.createFixedSize(binders.length);
var componentChildViews = ListWrapper.createFixedSize(binders.length); var componentChildViews = ListWrapper.createFixedSize(binders.length);
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) { for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
@ -88,22 +89,14 @@ export class ViewFactory {
componentChildViews[binderIdx] = childView; componentChildViews[binderIdx] = childView;
} }
// viewContainers
var viewContainer = null;
if (isPresent(binder.viewportDirective)) {
viewContainer = new vcModule.ViewContainer(this, this._renderer, view, binder.nestedProtoView, elementInjector);
}
viewContainers[binderIdx] = viewContainer;
// preBuiltObjects // preBuiltObjects
if (isPresent(elementInjector)) { if (isPresent(elementInjector)) {
preBuiltObjects[binderIdx] = new eli.PreBuiltObjects(view, new NgElement(view, binderIdx), viewContainer, preBuiltObjects[binderIdx] = new eli.PreBuiltObjects(view, new NgElement(view, binderIdx), childChangeDetector);
childChangeDetector);
} }
} }
view.init(changeDetector, elementInjectors, rootElementInjectors, view.init(changeDetector, elementInjectors, rootElementInjectors,
viewContainers, preBuiltObjects, componentChildViews); preBuiltObjects, componentChildViews);
return view; return view;
} }

View File

@ -44,8 +44,7 @@ export class AppViewHydrator {
} }
var hostElementInjector = hostView.elementInjectors[boundElementIndex]; var hostElementInjector = hostView.elementInjectors[boundElementIndex];
if (isBlank(injector)) { if (isBlank(injector)) {
// TODO: We should have another way of accesing the app injector at hostView place. injector = hostElementInjector.getLightDomAppInjector();
injector = new eli.ElementRef(hostElementInjector).injector;
} }
// shadowDomAppInjector // shadowDomAppInjector
@ -114,19 +113,22 @@ export class AppViewHydrator {
this._renderer.destroyInPlaceHostView(parentRenderViewRef, render); this._renderer.destroyInPlaceHostView(parentRenderViewRef, render);
} }
hydrateViewInViewContainer(viewContainer:vcModule.ViewContainer, atIndex:number, view:viewModule.AppView) { hydrateViewInViewContainer(viewContainer:vcModule.ViewContainer, atIndex:number, view:viewModule.AppView, injector:Injector = null) {
if (!viewContainer.hydrated()) throw new BaseException( if (!viewContainer.hydrated()) throw new BaseException(
'Cannot create views on a dehydrated ViewContainer'); 'Cannot create views on a dehydrated ViewContainer');
var renderViewRefs = this._renderer.createViewInContainer(viewContainer.render, atIndex, view.proto.render); if (isBlank(injector)) {
injector = viewContainer.elementInjector.getLightDomAppInjector();
}
var renderViewRefs = this._renderer.createViewInContainer(viewContainer.getRender(), atIndex, view.proto.render);
viewContainer.parentView.changeDetector.addChild(view.changeDetector); viewContainer.parentView.changeDetector.addChild(view.changeDetector);
this._viewHydrateRecurse(view, renderViewRefs, 0, viewContainer.appInjector, viewContainer.hostElementInjector, this._viewHydrateRecurse(view, renderViewRefs, 0, injector, viewContainer.elementInjector.getHost(),
viewContainer.parentView.context, viewContainer.parentView.locals); viewContainer.parentView.context, viewContainer.parentView.locals);
} }
dehydrateViewInViewContainer(viewContainer:vcModule.ViewContainer, atIndex:number, view:viewModule.AppView) { dehydrateViewInViewContainer(viewContainer:vcModule.ViewContainer, atIndex:number, view:viewModule.AppView) {
view.changeDetector.remove(); view.changeDetector.remove();
this._viewDehydrateRecurse(view); this._viewDehydrateRecurse(view);
this._renderer.destroyViewInContainer(viewContainer.render, atIndex); this._renderer.destroyViewInContainer(viewContainer.getRender(), atIndex);
} }
_viewHydrateRecurse( _viewHydrateRecurse(
@ -142,14 +144,6 @@ export class AppViewHydrator {
view.context = context; view.context = context;
view.locals.parent = locals; view.locals.parent = locals;
// viewContainers
for (var i = 0; i < view.viewContainers.length; i++) {
var vc = view.viewContainers[i];
if (isPresent(vc)) {
this._viewContainerHydrateRecurse(vc, new renderApi.ViewContainerRef(view.render, i), appInjector, hostElementInjector);
}
}
var binders = view.proto.elementBinders; var binders = view.proto.elementBinders;
for (var i = 0; i < binders.length; ++i) { for (var i = 0; i < binders.length; ++i) {
var componentDirective = binders[i].componentDirective; var componentDirective = binders[i].componentDirective;
@ -273,16 +267,6 @@ export class AppViewHydrator {
return shadowDomAppInjector; return shadowDomAppInjector;
} }
/**
* This should only be called by View or ViewContainer.
*/
_viewContainerHydrateRecurse(viewContainer:vcModule.ViewContainer, render:renderApi.ViewContainerRef, appInjector: Injector, hostElementInjector: eli.ElementInjector) {
viewContainer.viewHydrator = this;
viewContainer.render = render;
viewContainer.appInjector = appInjector;
viewContainer.hostElementInjector = hostElementInjector;
}
/** /**
* This should only be called by View or ViewContainer. * This should only be called by View or ViewContainer.
*/ */
@ -296,10 +280,6 @@ export class AppViewHydrator {
// as we don't want to change the render side // as we don't want to change the render side
// as the render side does its own recursion. // as the render side does its own recursion.
viewContainer.internalClearWithoutRender(); viewContainer.internalClearWithoutRender();
viewContainer.viewHydrator = null;
viewContainer.appInjector = null;
viewContainer.hostElementInjector = null;
viewContainer.render = null;
} }
} }

View File

@ -14,7 +14,7 @@ import {ProtoViewBuilder} from './view/proto_view_builder';
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
function _resolveViewContainer(vc:api.ViewContainerRef) { function _resolveViewContainer(vc:api.ViewContainerRef) {
return _resolveView(vc.view).viewContainers[vc.elementIndex]; return _resolveView(vc.view).getOrCreateViewContainer(vc.elementIndex);
} }
function _resolveView(viewRef:DirectDomViewRef) { function _resolveView(viewRef:DirectDomViewRef) {

View File

@ -9,13 +9,11 @@ export class DestinationLightDom {}
class _Root { class _Root {
node; node;
viewContainer; boundElementIndex:number;
content;
constructor(node, viewContainer, content) { constructor(node, boundElementIndex) {
this.node = node; this.node = node;
this.viewContainer = viewContainer; this.boundElementIndex = boundElementIndex;
this.content = content;
} }
} }
@ -79,11 +77,16 @@ export class LightDom {
for (var i = 0; i < roots.length; ++i) { for (var i = 0; i < roots.length; ++i) {
var root = roots[i]; var root = roots[i];
if (isPresent(root.boundElementIndex)) {
if (isPresent(root.viewContainer)) { var vc = this.lightDomView.viewContainers[root.boundElementIndex];
res = ListWrapper.concat(res, root.viewContainer.nodes()); var content = this.lightDomView.contentTags[root.boundElementIndex];
} else if (isPresent(root.content)) { if (isPresent(vc)) {
res = ListWrapper.concat(res, root.content.nodes()); res = ListWrapper.concat(res, vc.nodes());
} else if (isPresent(content)) {
res = ListWrapper.concat(res, content.nodes());
} else {
ListWrapper.push(res, root.node);
}
} else { } else {
ListWrapper.push(res, root.node); ListWrapper.push(res, root.node);
} }
@ -92,27 +95,22 @@ export class LightDom {
} }
// Returns a list of Roots for all the nodes of the light DOM. // Returns a list of Roots for all the nodes of the light DOM.
// The Root object contains the DOM node and its corresponding injector (could be null). // The Root object contains the DOM node and its corresponding boundElementIndex
_roots() { _roots() {
if (isPresent(this.roots)) return this.roots; if (isPresent(this.roots)) return this.roots;
var viewContainers = this.lightDomView.viewContainers; var boundElements = this.lightDomView.boundElements;
var contentTags = this.lightDomView.contentTags;
this.roots = ListWrapper.map(this.nodes, (n) => { this.roots = ListWrapper.map(this.nodes, (n) => {
var foundVc = null; var boundElementIndex = null;
var foundContentTag = null; for (var i=0; i<boundElements.length; i++) {
for (var i=0; i<viewContainers.length; i++) { var boundEl = boundElements[i];
var vc = viewContainers[i]; if (isPresent(boundEl) && boundEl === n) {
var contentTag = contentTags[i]; boundElementIndex = i;
if (isPresent(vc) && vc.templateElement === n) { break;
foundVc = vc;
}
if (isPresent(contentTag) && contentTag.contentStartElement === n) {
foundContentTag = contentTag;
} }
} }
return new _Root(n, foundVc, foundContentTag); return new _Root(n, boundElementIndex);
}); });
return this.roots; return this.roots;

View File

@ -27,7 +27,7 @@ export class ElementBinder {
parentIndex, parentIndex,
distanceToParent, distanceToParent,
propertySetters propertySetters
}) { } = {}) {
this.textNodeIndices = textNodeIndices; this.textNodeIndices = textNodeIndices;
this.contentTagSelector = contentTagSelector; this.contentTagSelector = contentTagSelector;
this.nestedProtoView = nestedProtoView; this.nestedProtoView = nestedProtoView;

View File

@ -26,6 +26,7 @@ export class RenderView {
viewContainers: List<ViewContainer>; viewContainers: List<ViewContainer>;
contentTags: List<Content>; contentTags: List<Content>;
lightDoms: List<LightDom>; lightDoms: List<LightDom>;
hostLightDom: LightDom;
proto: RenderProtoView; proto: RenderProtoView;
hydrated: boolean; hydrated: boolean;
_eventDispatcher: any/*EventDispatcher*/; _eventDispatcher: any/*EventDispatcher*/;
@ -33,20 +34,39 @@ export class RenderView {
constructor( constructor(
proto:RenderProtoView, rootNodes:List, proto:RenderProtoView, rootNodes:List,
boundTextNodes: List, boundElements:List, viewContainers:List, contentTags:List) { boundTextNodes: List, boundElements:List, contentTags:List) {
this.proto = proto; this.proto = proto;
this.rootNodes = rootNodes; this.rootNodes = rootNodes;
this.boundTextNodes = boundTextNodes; this.boundTextNodes = boundTextNodes;
this.boundElements = boundElements; this.boundElements = boundElements;
this.viewContainers = viewContainers; this.viewContainers = ListWrapper.createFixedSize(boundElements.length);
this.contentTags = contentTags; this.contentTags = contentTags;
this.lightDoms = ListWrapper.createFixedSize(boundElements.length); this.lightDoms = ListWrapper.createFixedSize(boundElements.length);
ListWrapper.fill(this.lightDoms, null); ListWrapper.fill(this.lightDoms, null);
this.componentChildViews = ListWrapper.createFixedSize(boundElements.length); this.componentChildViews = ListWrapper.createFixedSize(boundElements.length);
this.hostLightDom = null;
this.hydrated = false; this.hydrated = false;
this.eventHandlerRemovers = null; this.eventHandlerRemovers = null;
} }
getDirectParentLightDom(boundElementIndex:number) {
var binder = this.proto.elementBinders[boundElementIndex];
var destLightDom = null;
if (binder.parentIndex !== -1 && binder.distanceToParent === 1) {
destLightDom = this.lightDoms[binder.parentIndex];
}
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) { setElementProperty(elementIndex:number, propertyName:string, value:any) {
var setter = MapWrapper.get(this.proto.elementBinders[elementIndex].propertySetters, propertyName); var setter = MapWrapper.get(this.proto.elementBinders[elementIndex].propertySetters, propertyName);
setter(this.boundElements[elementIndex], value); setter(this.boundElements[elementIndex], value);

View File

@ -3,22 +3,17 @@ import {ListWrapper, MapWrapper, List} from 'angular2/src/facade/collection';
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
import * as viewModule from './view'; import * as viewModule from './view';
import * as ldModule from '../shadow_dom/light_dom';
export class ViewContainer { export class ViewContainer {
templateElement; parentView: viewModule.RenderView;
boundElementIndex: number;
views: List<viewModule.RenderView>; views: List<viewModule.RenderView>;
lightDom: ldModule.LightDom;
hostLightDom: ldModule.LightDom;
hydrated: boolean;
constructor(templateElement) {
this.templateElement = templateElement;
constructor(parentView: viewModule.RenderView, boundElementIndex: number) {
this.parentView = parentView;
this.boundElementIndex = boundElementIndex;
// The order in this list matches the DOM order. // The order in this list matches the DOM order.
this.views = []; this.views = [];
this.hostLightDom = null;
this.hydrated = false;
} }
get(index: number): viewModule.RenderView { get(index: number): viewModule.RenderView {
@ -30,22 +25,26 @@ export class ViewContainer {
} }
_siblingToInsertAfter(index: number) { _siblingToInsertAfter(index: number) {
if (index == 0) return this.templateElement; if (index == 0) return this.parentView.boundElements[this.boundElementIndex];
return ListWrapper.last(this.views[index - 1].rootNodes); return ListWrapper.last(this.views[index - 1].rootNodes);
} }
_checkHydrated() { _checkHydrated() {
if (!this.hydrated) throw new BaseException( if (!this.parentView.hydrated) throw new BaseException(
'Cannot change dehydrated ViewContainer'); 'Cannot change dehydrated ViewContainer');
} }
_getDirectParentLightDom() {
return this.parentView.getDirectParentLightDom(this.boundElementIndex);
}
clear() { clear() {
this._checkHydrated(); this._checkHydrated();
for (var i=this.views.length-1; i>=0; i--) { for (var i=this.views.length-1; i>=0; i--) {
this.detach(i); this.detach(i);
} }
if (isPresent(this.lightDom)) { if (isPresent(this._getDirectParentLightDom())) {
this.lightDom.redistribute(); this._getDirectParentLightDom().redistribute();
} }
} }
@ -54,14 +53,14 @@ export class ViewContainer {
if (atIndex == -1) atIndex = this.views.length; if (atIndex == -1) atIndex = this.views.length;
ListWrapper.insert(this.views, atIndex, view); ListWrapper.insert(this.views, atIndex, view);
if (isBlank(this.lightDom)) { if (isBlank(this._getDirectParentLightDom())) {
ViewContainer.moveViewNodesAfterSibling(this._siblingToInsertAfter(atIndex), view); ViewContainer.moveViewNodesAfterSibling(this._siblingToInsertAfter(atIndex), view);
} else { } else {
this.lightDom.redistribute(); this._getDirectParentLightDom().redistribute();
} }
// new content tags might have appeared, we need to redistribute. // new content tags might have appeared, we need to redistribute.
if (isPresent(this.hostLightDom)) { if (isPresent(this.parentView.hostLightDom)) {
this.hostLightDom.redistribute(); this.parentView.hostLightDom.redistribute();
} }
return view; return view;
} }
@ -74,14 +73,14 @@ export class ViewContainer {
this._checkHydrated(); this._checkHydrated();
var detachedView = this.get(atIndex); var detachedView = this.get(atIndex);
ListWrapper.removeAt(this.views, atIndex); ListWrapper.removeAt(this.views, atIndex);
if (isBlank(this.lightDom)) { if (isBlank(this._getDirectParentLightDom())) {
ViewContainer.removeViewNodes(detachedView); ViewContainer.removeViewNodes(detachedView);
} else { } else {
this.lightDom.redistribute(); this._getDirectParentLightDom().redistribute();
} }
// content tags might have disappeared we need to do redistribution. // content tags might have disappeared we need to do redistribution.
if (isPresent(this.hostLightDom)) { if (isPresent(this.parentView.hostLightDom)) {
this.hostLightDom.redistribute(); this.parentView.hostLightDom.redistribute();
} }
return detachedView; return detachedView;
} }

View File

@ -8,7 +8,6 @@ import {Content} from '../shadow_dom/content_tag';
import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy'; import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy';
import {EventManager} from 'angular2/src/render/dom/events/event_manager'; import {EventManager} from 'angular2/src/render/dom/events/event_manager';
import * as vcModule from './view_container';
import * as pvModule from './proto_view'; import * as pvModule from './proto_view';
import * as viewModule from './view'; import * as viewModule from './view';
import {NG_BINDING_CLASS_SELECTOR, NG_BINDING_CLASS} from '../util'; import {NG_BINDING_CLASS_SELECTOR, NG_BINDING_CLASS} from '../util';
@ -91,7 +90,6 @@ export class ViewFactory {
var binders = protoView.elementBinders; var binders = protoView.elementBinders;
var boundTextNodes = []; var boundTextNodes = [];
var boundElements = ListWrapper.createFixedSize(binders.length); var boundElements = ListWrapper.createFixedSize(binders.length);
var viewContainers = ListWrapper.createFixedSize(binders.length);
var contentTags = ListWrapper.createFixedSize(binders.length); var contentTags = ListWrapper.createFixedSize(binders.length);
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) { for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
@ -111,13 +109,6 @@ export class ViewFactory {
ListWrapper.push(boundTextNodes, childNodes[textNodeIndices[i]]); ListWrapper.push(boundTextNodes, childNodes[textNodeIndices[i]]);
} }
// viewContainers
var viewContainer = null;
if (isBlank(binder.componentId) && isPresent(binder.nestedProtoView)) {
viewContainer = new vcModule.ViewContainer(element);
}
viewContainers[binderIdx] = viewContainer;
// contentTags // contentTags
var contentTag = null; var contentTag = null;
if (isPresent(binder.contentTagSelector)) { if (isPresent(binder.contentTagSelector)) {
@ -128,7 +119,7 @@ export class ViewFactory {
var view = new viewModule.RenderView( var view = new viewModule.RenderView(
protoView, viewRootNodes, protoView, viewRootNodes,
boundTextNodes, boundElements, viewContainers, contentTags boundTextNodes, boundElements, contentTags
); );
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) { for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {

View File

@ -63,33 +63,21 @@ export class RenderViewHydrator {
} }
hydrateViewInViewContainer(viewContainer:vcModule.ViewContainer, view:viewModule.RenderView) { hydrateViewInViewContainer(viewContainer:vcModule.ViewContainer, view:viewModule.RenderView) {
this._viewHydrateRecurse(view, viewContainer.hostLightDom); this._viewHydrateRecurse(view, viewContainer.parentView.hostLightDom);
} }
dehydrateViewInViewContainer(viewContainer:vcModule.ViewContainer, view:viewModule.RenderView) { dehydrateViewInViewContainer(viewContainer:vcModule.ViewContainer, view:viewModule.RenderView) {
this._viewDehydrateRecurse(view); 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) { _viewHydrateRecurse(view, hostLightDom: ldModule.LightDom) {
if (view.hydrated) throw new BaseException('The view is already hydrated.'); if (view.hydrated) throw new BaseException('The view is already hydrated.');
view.hydrated = true; view.hydrated = true;
view.hostLightDom = hostLightDom;
// viewContainers and content tags // content tags
for (var i = 0; i < view.viewContainers.length; i++) { for (var i = 0; i < view.contentTags.length; i++) {
var vc = view.viewContainers[i]; var destLightDom = view.getDirectParentLightDom(i);
var destLightDom = this._getViewDestLightDom(view, i);
if (isPresent(vc)) {
this._viewContainerHydrateRecurse(vc, destLightDom, hostLightDom);
}
var ct = view.contentTags[i]; var ct = view.contentTags[i];
if (isPresent(ct)) { if (isPresent(ct)) {
ct.hydrate(destLightDom); ct.hydrate(destLightDom);
@ -167,26 +155,17 @@ export class RenderViewHydrator {
view.eventHandlerRemovers[i](); view.eventHandlerRemovers[i]();
} }
view.hostLightDom = null;
view.eventHandlerRemovers = null; view.eventHandlerRemovers = null;
view.setEventDispatcher(null); view.setEventDispatcher(null);
view.hydrated = false; view.hydrated = false;
} }
_viewContainerHydrateRecurse(viewContainer, destLightDom: ldModule.LightDom, hostLightDom: ldModule.LightDom) {
viewContainer.hydrated = true;
viewContainer.hostLightDom = hostLightDom;
viewContainer.lightDom = destLightDom;
}
_viewContainerDehydrateRecurse(viewContainer) { _viewContainerDehydrateRecurse(viewContainer) {
for (var i=0; i<viewContainer.views.length; i++) { for (var i=0; i<viewContainer.views.length; i++) {
this._viewDehydrateRecurse(viewContainer.views[i]); this._viewDehydrateRecurse(viewContainer.views[i]);
} }
viewContainer.clear(); viewContainer.clear();
viewContainer.hostLightDom = null;
viewContainer.lightDom = null;
viewContainer.hydrated = false;
} }
} }

View File

@ -47,15 +47,15 @@ export function main() {
} }
function createEmptyView() { function createEmptyView() {
var view = new AppView(null, createProtoView(), MapWrapper.create()); var view = new AppView(null, null, null, createProtoView(), MapWrapper.create());
view.init(null, [], [], [], [], []); view.init(null, [], [], [], []);
return view; return view;
} }
function createElementRef(view, boundElementIndex) { function createElementRef(view, boundElementIndex) {
var peli = new ProtoElementInjector(null, boundElementIndex, []); var peli = new ProtoElementInjector(null, boundElementIndex, []);
var eli = new ElementInjector(peli, null); var eli = new ElementInjector(peli, null);
var preBuiltObjects = new PreBuiltObjects(view, null, null, null); var preBuiltObjects = new PreBuiltObjects(view, null, null);
eli.instantiateDirectives(null, null, null, preBuiltObjects); eli.instantiateDirectives(null, null, null, preBuiltObjects);
return new ElementRef(eli); return new ElementRef(eli);
} }

View File

@ -21,7 +21,7 @@ class DummyDirective extends Directive {
@proxy @proxy
@IMPLEMENTS(AppView) @IMPLEMENTS(AppView)
class DummyView extends SpyObject {noSuchMethod(m){super.noSuchMethod(m)}} class DummyView extends SpyObject {noSuchMethod(m){return super.noSuchMethod(m)}}
class SimpleDirective { class SimpleDirective {
@ -203,7 +203,7 @@ class TestNode extends TreeNode {
} }
export function main() { export function main() {
var defaultPreBuiltObjects = new PreBuiltObjects(null, null, null, null); var defaultPreBuiltObjects = new PreBuiltObjects(null, null, null);
var appInjector = Injector.resolveAndCreate([]); var appInjector = Injector.resolveAndCreate([]);
function humanize(tree, names:List) { function humanize(tree, names:List) {
@ -476,7 +476,7 @@ export function main() {
it("should instantiate directives that depend on pre built objects", function () { it("should instantiate directives that depend on pre built objects", function () {
var view = new DummyView(); var view = new DummyView();
var inj = injector([NeedsView], null, null, new PreBuiltObjects(view, null, null, null)); var inj = injector([NeedsView], null, null, new PreBuiltObjects(view, null, null));
expect(inj.get(NeedsView).view).toBe(view); expect(inj.get(NeedsView).view).toBe(view);
}); });
@ -602,28 +602,32 @@ export function main() {
describe("pre built objects", function () { describe("pre built objects", function () {
it("should return view", function () { it("should return view", function () {
var view = new DummyView(); var view = new DummyView();
var inj = injector([], null, null, new PreBuiltObjects(view, null, null, null)); var inj = injector([], null, null, new PreBuiltObjects(view, null, null));
expect(inj.get(AppView)).toEqual(view); expect(inj.get(AppView)).toEqual(view);
}); });
it("should return element", function () { it("should return element", function () {
var element = new NgElement(null, null); var element = new NgElement(null, null);
var inj = injector([], null, null, new PreBuiltObjects(null, element, null, null)); var inj = injector([], null, null, new PreBuiltObjects(null, element, null));
expect(inj.get(NgElement)).toEqual(element); expect(inj.get(NgElement)).toEqual(element);
}); });
it('should return viewContainer', function () { it('should return viewContainer', function () {
var viewContainer = new ViewContainer(null, null, null, null, null); var viewContainer = new ViewContainer(null, null, null);
var inj = injector([], null, null, new PreBuiltObjects(null, null, viewContainer, null)); var view = new DummyView();
view.spy('getOrCreateViewContainer').andCallFake( (index) => {
return viewContainer;
});
var inj = injector([], null, null, new PreBuiltObjects(view, null, null));
expect(inj.get(ViewContainer)).toEqual(viewContainer); expect(inj.get(ViewContainer)).toEqual(viewContainer);
}); });
it('should return changeDetectorRef', function () { it('should return changeDetectorRef', function () {
var cd = new DynamicChangeDetector(null, null, null, [], []); var cd = new DynamicChangeDetector(null, null, null, [], []);
var inj = injector([], null, null, new PreBuiltObjects(null, null, null, cd)); var inj = injector([], null, null, new PreBuiltObjects(null, null, cd));
expect(inj.get(ChangeDetectorRef)).toBe(cd.ref); expect(inj.get(ChangeDetectorRef)).toBe(cd.ref);
}); });
@ -710,12 +714,12 @@ export function main() {
beforeEach( () => { beforeEach( () => {
renderer = new FakeRenderer(); renderer = new FakeRenderer();
var protoView = new AppProtoView(null, null); var protoView = new AppProtoView(null, null);
view = new AppView(renderer, protoView, MapWrapper.create()); view = new AppView(renderer, null, null, protoView, MapWrapper.create());
view.render = new ViewRef(); view.render = new ViewRef();
}); });
it('should be injectable and callable', () => { it('should be injectable and callable', () => {
var preBuildObject = new PreBuiltObjects(view, null, null, null); var preBuildObject = new PreBuiltObjects(view, null, null);
var inj = injector([NeedsPropertySetter], null, null, preBuildObject); var inj = injector([NeedsPropertySetter], null, null, preBuildObject);
var component = inj.get(NeedsPropertySetter); var component = inj.get(NeedsPropertySetter);
component.setProp('foobar'); component.setProp('foobar');
@ -734,7 +738,7 @@ export function main() {
}); });
it('should be injectable and callable without specifying param type annotation', () => { it('should be injectable and callable without specifying param type annotation', () => {
var preBuildObject = new PreBuiltObjects(view, null, null, null); var preBuildObject = new PreBuiltObjects(view, null, null);
var inj = injector([NeedsPropertySetterNoType], null, null, preBuildObject); var inj = injector([NeedsPropertySetterNoType], null, null, preBuildObject);
var component = inj.get(NeedsPropertySetterNoType); var component = inj.get(NeedsPropertySetterNoType);
component.setProp('foobar'); component.setProp('foobar');
@ -773,6 +777,16 @@ export function main() {
var inj = injector([NeedsElementRef]); var inj = injector([NeedsElementRef]);
expect(inj.get(NeedsElementRef).elementRef).toBeAnInstanceOf(ElementRef); expect(inj.get(NeedsElementRef).elementRef).toBeAnInstanceOf(ElementRef);
}); });
it('should return the viewContainer from the view', () => {
var viewContainer = new ViewContainer(null, null, null);
var view = new DummyView();
view.spy('getOrCreateViewContainer').andCallFake( (index) => {
return viewContainer;
});
var inj = injector([NeedsElementRef], null, null, new PreBuiltObjects(view, null, null));
expect(inj.get(NeedsElementRef).elementRef.viewContainer).toBe(viewContainer);
});
}); });
describe('directive queries', () => { describe('directive queries', () => {

View File

@ -34,6 +34,7 @@ import {ElementRef} from 'angular2/src/core/compiler/element_injector';
import {If} from 'angular2/src/directives/if'; import {If} from 'angular2/src/directives/if';
import {ViewContainer} from 'angular2/src/core/compiler/view_container'; import {ViewContainer} from 'angular2/src/core/compiler/view_container';
import {Compiler} from 'angular2/src/core/compiler/compiler';
export function main() { export function main() {
describe('integration tests', function() { describe('integration tests', function() {
@ -694,6 +695,27 @@ export function main() {
})); }));
}); });
describe('dynamic ViewContainers', () => {
it('should allow to create a ViewContainer at any bound location',
inject([TestBed, AsyncTestCompleter, Compiler], (tb, async, compiler) => {
tb.overrideView(MyComp, new View({
template: '<div><dynamic-vp #dynamic></dynamic-vp></div>',
directives: [DynamicViewport]
}));
tb.createView(MyComp).then((view) => {
var dynamicVp = view.rawView.elementInjectors[0].get(DynamicViewport);
dynamicVp.done.then( (_) => {
view.detectChanges();
expect(view.rootNodes).toHaveText('dynamic greet');
async.done();
});
});
}));
});
it('should support static attributes', inject([TestBed, AsyncTestCompleter], (tb, async) => { it('should support static attributes', inject([TestBed, AsyncTestCompleter], (tb, async) => {
tb.overrideView(MyComp, new View({ tb.overrideView(MyComp, new View({
template: '<input static type="text" title>', template: '<input static type="text" title>',
@ -774,9 +796,22 @@ export function main() {
}); });
} }
class DynamicallyCreatedComponentService { @Decorator({
selector: 'dynamic-vp'
})
class DynamicViewport {
done;
constructor(vc:ViewContainer, inj:Injector, compiler:Compiler) {
var myService = new MyService();
myService.greeting = 'dynamic greet';
this.done = compiler.compileInHost(ChildCompUsingService).then( (hostPv) => {
vc.create(0, hostPv, inj.createChildFromResolved(Injector.resolve([bind(MyService).toValue(myService)])))
});
}
} }
class DynamicallyCreatedComponentService {}
@DynamicComponent({ @DynamicComponent({
selector: 'dynamic-comp' selector: 'dynamic-comp'
}) })
@ -909,6 +944,19 @@ class ChildComp {
} }
} }
@Component({
selector: 'child-cmp-svc'
})
@View({
template: '{{ctxProp}}'
})
class ChildCompUsingService {
ctxProp:string;
constructor(service: MyService) {
this.ctxProp = service.greeting;
}
}
@Decorator({ @Decorator({
selector: 'some-directive' selector: 'some-directive'
}) })

View File

@ -35,7 +35,7 @@ export function main() {
}); });
function createViewFactory({capacity}):ViewFactory { function createViewFactory({capacity}):ViewFactory {
return new ViewFactory(capacity, renderer); return new ViewFactory(capacity, renderer, null);
} }
function createProtoChangeDetector() { function createProtoChangeDetector() {

View File

@ -80,19 +80,19 @@ export function main() {
} }
function createEmptyView() { function createEmptyView() {
var view = new AppView(renderer, createProtoView(), MapWrapper.create()); var view = new AppView(renderer, null, null, createProtoView(), MapWrapper.create());
var changeDetector = new SpyChangeDetector(); var changeDetector = new SpyChangeDetector();
view.init(changeDetector, [], [], [], [], []); view.init(changeDetector, [], [], [], []);
return view; return view;
} }
function createHostView(pv, shadowView, componentInstance) { function createHostView(pv, shadowView, componentInstance) {
var view = new AppView(renderer, pv, MapWrapper.create()); var view = new AppView(renderer, null, null, pv, MapWrapper.create());
var changeDetector = new SpyChangeDetector(); var changeDetector = new SpyChangeDetector();
var eij = createElementInjector(); var eij = createElementInjector();
eij.spy('getComponent').andCallFake( () => componentInstance ); eij.spy('getComponent').andCallFake( () => componentInstance );
view.init(changeDetector, [eij], [eij], view.init(changeDetector, [eij], [eij],
[null], [null], [shadowView]); [null], [shadowView]);
return view; return view;
} }

View File

@ -0,0 +1,115 @@
import {
AsyncTestCompleter,
beforeEach,
ddescribe,
xdescribe,
describe,
el,
dispatchEvent,
expect,
iit,
inject,
beforeEachBindings,
it,
xit,
SpyObject, proxy
} from 'angular2/test_lib';
import {IMPLEMENTS, isBlank} from 'angular2/src/facade/lang';
import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import {AppProtoView, AppView} from 'angular2/src/core/compiler/view';
import {ViewContainer} from 'angular2/src/core/compiler/view_container';
import {Renderer} from 'angular2/src/render/api';
import {ChangeDetector} from 'angular2/change_detection';
import {ElementBinder} from 'angular2/src/core/compiler/element_binder';
import {ElementInjector} from 'angular2/src/core/compiler/element_injector';
export function main() {
describe('AppView', () => {
var renderer;
beforeEach( () => {
renderer = new SpyRenderer();
});
function createElementInjector() {
return new SpyElementInjector();
}
function createEmptyElBinder() {
return new ElementBinder(0, null, 0, null, null, null);
}
function createEmbeddedProtoViewElBinder(nestedProtoView) {
var binder = new ElementBinder(0, null, 0, null, null, null);
binder.nestedProtoView = nestedProtoView;
return binder;
}
function createProtoView(binders = null) {
if (isBlank(binders)) {
binders = [];
}
var res = new AppProtoView(null, null);
res.elementBinders = binders;
return res;
}
function createViewWithOneBoundElement(pv) {
var view = new AppView(renderer, null, null, pv, MapWrapper.create());
var changeDetector = new SpyChangeDetector();
var eij = createElementInjector();
view.init(changeDetector, [eij], [eij],
[null], [null]);
return view;
}
describe('getOrCreateViewContainer()', () => {
it('should create a new container', () => {
var pv = createProtoView([createEmptyElBinder()]);
var view = createViewWithOneBoundElement(pv);
expect(view.getOrCreateViewContainer(0) instanceof ViewContainer).toBe(true);
});
it('should return an existing container', () => {
var pv = createProtoView([createEmptyElBinder()]);
var view = createViewWithOneBoundElement(pv);
var vc = view.getOrCreateViewContainer(0);
expect(view.getOrCreateViewContainer(0)).toBe(vc);
});
it('should store an existing nestedProtoView in the container', () => {
var defaultProtoView = createProtoView();
var pv = createProtoView([createEmbeddedProtoViewElBinder(defaultProtoView)]);
var view = createViewWithOneBoundElement(pv);
var vc = view.getOrCreateViewContainer(0);
expect(vc.defaultProtoView).toBe(defaultProtoView);
});
});
});
}
@proxy
@IMPLEMENTS(Renderer)
class SpyRenderer extends SpyObject {
constructor(){super(Renderer);}
noSuchMethod(m){return super.noSuchMethod(m)}
}
@proxy
@IMPLEMENTS(ChangeDetector)
class SpyChangeDetector extends SpyObject {
constructor(){super(ChangeDetector);}
noSuchMethod(m){return super.noSuchMethod(m)}
}
@proxy
@IMPLEMENTS(ElementInjector)
class SpyElementInjector extends SpyObject {
constructor(){super(ElementInjector);}
noSuchMethod(m){return super.noSuchMethod(m)}
}

View File

@ -47,7 +47,7 @@ export function main() {
it('should attach the view nodes as child of the host element', () => { it('should attach the view nodes as child of the host element', () => {
var host = el('<div><span>original content</span></div>'); var host = el('<div><span>original content</span></div>');
var nodes = el('<div>view</div>'); var nodes = el('<div>view</div>');
var view = new RenderView(null, [nodes], [], [], [], []); var view = new RenderView(null, [nodes], [], [], []);
strategy.attachTemplate(host, view); strategy.attachTemplate(host, view);
var firstChild = DOM.firstChild(host); var firstChild = DOM.firstChild(host);

View File

@ -42,7 +42,7 @@ export function main() {
it('should attach the view nodes as child of the host element', () => { it('should attach the view nodes as child of the host element', () => {
var host = el('<div><span>original content</span></div>'); var host = el('<div><span>original content</span></div>');
var nodes = el('<div>view</div>'); var nodes = el('<div>view</div>');
var view = new RenderView(null, [nodes], [], [], [], []); var view = new RenderView(null, [nodes], [], [], []);
strategy.attachTemplate(host, view); strategy.attachTemplate(host, view);
var firstChild = DOM.firstChild(host); var firstChild = DOM.firstChild(host);

View File

@ -10,24 +10,30 @@ import {ViewContainer} from 'angular2/src/render/dom/view/view_container';
@proxy @proxy
@IMPLEMENTS(RenderView) @IMPLEMENTS(RenderView)
class FakeView { class FakeView {
boundElements;
contentTags; contentTags;
viewContainers; viewContainers;
constructor(containers = null) { constructor(containers = null) {
this.boundElements = [];
this.contentTags = []; this.contentTags = [];
this.viewContainers = []; this.viewContainers = [];
if (isPresent(containers)) { if (isPresent(containers)) {
ListWrapper.forEach(containers, (c) => { ListWrapper.forEach(containers, (c) => {
var boundElement = null;
var contentTag = null;
var vc = null;
if (c instanceof FakeContentTag) { if (c instanceof FakeContentTag) {
ListWrapper.push(this.contentTags, c); contentTag = c;
} else { boundElement = c.contentStartElement;
ListWrapper.push(this.contentTags, null);
} }
if (c instanceof FakeViewContainer) { if (c instanceof FakeViewContainer) {
ListWrapper.push(this.viewContainers, c); vc = c;
} else { boundElement = c.templateElement;
ListWrapper.push(this.viewContainers, null);
} }
ListWrapper.push(this.contentTags, contentTag);
ListWrapper.push(this.viewContainers, vc);
ListWrapper.push(this.boundElements, boundElement);
}); });
} }
} }
@ -40,9 +46,9 @@ class FakeView {
@proxy @proxy
@IMPLEMENTS(ViewContainer) @IMPLEMENTS(ViewContainer)
class FakeViewContainer { class FakeViewContainer {
templateElement;
_nodes; _nodes;
_contentTagContainers; _contentTagContainers;
templateElement;
constructor(templateEl, nodes = null, views = null) { constructor(templateEl, nodes = null, views = null) {
this.templateElement = templateEl; this.templateElement = templateEl;

View File

@ -35,7 +35,7 @@ export function main() {
it('should attach the view nodes to the shadow root', () => { it('should attach the view nodes to the shadow root', () => {
var host = el('<div><span>original content</span></div>'); var host = el('<div><span>original content</span></div>');
var nodes = el('<div>view</div>'); var nodes = el('<div>view</div>');
var view = new RenderView(null, [nodes], [], [], [], []); var view = new RenderView(null, [nodes], [], [], []);
strategy.attachTemplate(host, view); strategy.attachTemplate(host, view);
var shadowRoot = DOM.getShadowRoot(host); var shadowRoot = DOM.getShadowRoot(host);

View File

@ -70,12 +70,12 @@ export function main() {
function createEmptyView() { function createEmptyView() {
var root = el('<div><div></div></div>'); var root = el('<div><div></div></div>');
return new RenderView(createProtoView(), [DOM.childNodes(root)[0]], return new RenderView(createProtoView(), [DOM.childNodes(root)[0]],
[], [], [], []); [], [], []);
} }
function createHostView(pv, shadowDomView) { function createHostView(pv, shadowDomView) {
var view = new RenderView(pv, [el('<div></div>')], var view = new RenderView(pv, [el('<div></div>')],
[], [el('<div></div>')], [], []); [], [el('<div></div>')], [null]);
viewFactory.setComponentView(view, 0, shadowDomView); viewFactory.setComponentView(view, 0, shadowDomView);
return view; return view;
} }

View File

@ -0,0 +1,109 @@
import {
AsyncTestCompleter,
beforeEach,
ddescribe,
xdescribe,
describe,
el,
dispatchEvent,
expect,
iit,
inject,
beforeEachBindings,
it,
xit,
SpyObject, proxy
} from 'angular2/test_lib';
import {IMPLEMENTS, isBlank} from 'angular2/src/facade/lang';
import {ListWrapper} from 'angular2/src/facade/collection';
import {RenderProtoView} from 'angular2/src/render/dom/view/proto_view';
import {ElementBinder} from 'angular2/src/render/dom/view/element_binder';
import {RenderView} from 'angular2/src/render/dom/view/view';
import {ViewContainer} from 'angular2/src/render/dom/view/view_container';
import {LightDom} from 'angular2/src/render/dom/shadow_dom/light_dom';
import {DOM} from 'angular2/src/dom/dom_adapter';
export function main() {
describe('RenderView', () => {
function createProtoView(binders = null) {
if (isBlank(binders)) {
binders = [];
}
var rootEl = el('<div></div>');
return new RenderProtoView({
element: rootEl,
elementBinders: binders
});
}
function createView(pv=null, boundElementCount=0) {
if (isBlank(pv)) {
pv = createProtoView();
}
var root = el('<div><div></div></div>');
var boundElements = [];
for (var i=0; i<boundElementCount; i++) {
ListWrapper.push(boundElements, el('<span></span'));
}
return new RenderView(pv, [DOM.childNodes(root)[0]],
[], boundElements, []);
}
describe('getDirectParentLightDom', () => {
it('should return the LightDom of the direct parent', () => {
var pv = createProtoView(
[new ElementBinder(), new ElementBinder({
parentIndex: 0,
distanceToParent: 1
})]
);
var view = createView(pv, 2);
view.lightDoms[0] = new SpyLightDom();
view.lightDoms[1] = new SpyLightDom();
expect(view.getDirectParentLightDom(1)).toBe(view.lightDoms[0]);
});
it('should return null if the direct parent is not bound', () => {
var pv = createProtoView(
[new ElementBinder(), new ElementBinder(), new ElementBinder({
parentIndex: 0,
distanceToParent: 2
})]
);
var view = createView(pv, 3);
view.lightDoms[0] = new SpyLightDom();
view.lightDoms[1] = new SpyLightDom();
view.lightDoms[2] = new SpyLightDom();
expect(view.getDirectParentLightDom(2)).toBe(null);
});
});
describe('getOrCreateViewContainer', () => {
it('should create a new container', () => {
var pv = createProtoView([new ElementBinder()]);
var view = createView(pv, 1);
expect(view.getOrCreateViewContainer(0) instanceof ViewContainer).toBe(true);
});
it('should return an existing container', () => {
var pv = createProtoView([new ElementBinder()]);
var view = createView(pv, 1);
var vc = view.getOrCreateViewContainer(0);
expect(view.getOrCreateViewContainer(0)).toBe(vc);
});
});
});
}
@proxy
@IMPLEMENTS(LightDom)
class SpyLightDom extends SpyObject {
constructor(){super(LightDom);}
noSuchMethod(m){return super.noSuchMethod(m)}
}

View File

@ -218,8 +218,8 @@ function setup() {
reflector.registerType(ViewFactory, { reflector.registerType(ViewFactory, {
"factory": (capacity, renderer) => "factory": (capacity, renderer) =>
new ViewFactory(capacity, renderer), new ViewFactory(capacity, renderer, appViewHydrator),
"parameters": [[new Inject(VIEW_POOL_CAPACITY)],[Renderer]], "parameters": [[new Inject(VIEW_POOL_CAPACITY)],[Renderer],[AppViewHydrator]],
"annotations": [] "annotations": []
}); });
@ -281,13 +281,6 @@ function setup() {
"annotations": [] "annotations": []
}); });
reflector.registerType(ViewFactory, {
"factory": (capacity, renderer) =>
new ViewFactory(capacity, renderer),
"parameters": [[new Inject(VIEW_POOL_CAPACITY)],[Renderer]],
"annotations": []
});
reflector.registerType(VIEW_POOL_CAPACITY, { reflector.registerType(VIEW_POOL_CAPACITY, {
"factory": () => 10000, "factory": () => 10000,
"parameters": [], "parameters": [],