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

@ -14,7 +14,7 @@ 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];
return _resolveView(vc.view).getOrCreateViewContainer(vc.elementIndex);
}
function _resolveView(viewRef:DirectDomViewRef) {

View File

@ -9,13 +9,11 @@ export class DestinationLightDom {}
class _Root {
node;
viewContainer;
content;
boundElementIndex:number;
constructor(node, viewContainer, content) {
constructor(node, boundElementIndex) {
this.node = node;
this.viewContainer = viewContainer;
this.content = content;
this.boundElementIndex = boundElementIndex;
}
}
@ -79,11 +77,16 @@ export class LightDom {
for (var i = 0; i < roots.length; ++i) {
var root = roots[i];
if (isPresent(root.viewContainer)) {
res = ListWrapper.concat(res, root.viewContainer.nodes());
} else if (isPresent(root.content)) {
res = ListWrapper.concat(res, root.content.nodes());
if (isPresent(root.boundElementIndex)) {
var vc = this.lightDomView.viewContainers[root.boundElementIndex];
var content = this.lightDomView.contentTags[root.boundElementIndex];
if (isPresent(vc)) {
res = ListWrapper.concat(res, vc.nodes());
} else if (isPresent(content)) {
res = ListWrapper.concat(res, content.nodes());
} else {
ListWrapper.push(res, root.node);
}
} else {
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.
// 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() {
if (isPresent(this.roots)) return this.roots;
var viewContainers = this.lightDomView.viewContainers;
var contentTags = this.lightDomView.contentTags;
var boundElements = this.lightDomView.boundElements;
this.roots = ListWrapper.map(this.nodes, (n) => {
var foundVc = null;
var foundContentTag = null;
for (var i=0; i<viewContainers.length; i++) {
var vc = viewContainers[i];
var contentTag = contentTags[i];
if (isPresent(vc) && vc.templateElement === n) {
foundVc = vc;
}
if (isPresent(contentTag) && contentTag.contentStartElement === n) {
foundContentTag = contentTag;
var boundElementIndex = null;
for (var i=0; i<boundElements.length; i++) {
var boundEl = boundElements[i];
if (isPresent(boundEl) && boundEl === n) {
boundElementIndex = i;
break;
}
}
return new _Root(n, foundVc, foundContentTag);
return new _Root(n, boundElementIndex);
});
return this.roots;

View File

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

View File

@ -26,6 +26,7 @@ export class RenderView {
viewContainers: List<ViewContainer>;
contentTags: List<Content>;
lightDoms: List<LightDom>;
hostLightDom: LightDom;
proto: RenderProtoView;
hydrated: boolean;
_eventDispatcher: any/*EventDispatcher*/;
@ -33,20 +34,39 @@ export class RenderView {
constructor(
proto:RenderProtoView, rootNodes:List,
boundTextNodes: List, boundElements:List, viewContainers:List, contentTags:List) {
boundTextNodes: List, boundElements:List, contentTags:List) {
this.proto = proto;
this.rootNodes = rootNodes;
this.boundTextNodes = boundTextNodes;
this.boundElements = boundElements;
this.viewContainers = viewContainers;
this.viewContainers = ListWrapper.createFixedSize(boundElements.length);
this.contentTags = contentTags;
this.lightDoms = ListWrapper.createFixedSize(boundElements.length);
ListWrapper.fill(this.lightDoms, null);
this.componentChildViews = ListWrapper.createFixedSize(boundElements.length);
this.hostLightDom = null;
this.hydrated = false;
this.eventHandlerRemovers = 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) {
var setter = MapWrapper.get(this.proto.elementBinders[elementIndex].propertySetters, propertyName);
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 * as viewModule from './view';
import * as ldModule from '../shadow_dom/light_dom';
export class ViewContainer {
templateElement;
parentView: viewModule.RenderView;
boundElementIndex: number;
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.
this.views = [];
this.hostLightDom = null;
this.hydrated = false;
}
get(index: number): viewModule.RenderView {
@ -30,22 +25,26 @@ export class ViewContainer {
}
_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);
}
_checkHydrated() {
if (!this.hydrated) throw new BaseException(
if (!this.parentView.hydrated) throw new BaseException(
'Cannot change dehydrated ViewContainer');
}
_getDirectParentLightDom() {
return this.parentView.getDirectParentLightDom(this.boundElementIndex);
}
clear() {
this._checkHydrated();
for (var i=this.views.length-1; i>=0; i--) {
this.detach(i);
}
if (isPresent(this.lightDom)) {
this.lightDom.redistribute();
if (isPresent(this._getDirectParentLightDom())) {
this._getDirectParentLightDom().redistribute();
}
}
@ -54,14 +53,14 @@ export class ViewContainer {
if (atIndex == -1) atIndex = this.views.length;
ListWrapper.insert(this.views, atIndex, view);
if (isBlank(this.lightDom)) {
if (isBlank(this._getDirectParentLightDom())) {
ViewContainer.moveViewNodesAfterSibling(this._siblingToInsertAfter(atIndex), view);
} else {
this.lightDom.redistribute();
this._getDirectParentLightDom().redistribute();
}
// new content tags might have appeared, we need to redistribute.
if (isPresent(this.hostLightDom)) {
this.hostLightDom.redistribute();
if (isPresent(this.parentView.hostLightDom)) {
this.parentView.hostLightDom.redistribute();
}
return view;
}
@ -74,14 +73,14 @@ export class ViewContainer {
this._checkHydrated();
var detachedView = this.get(atIndex);
ListWrapper.removeAt(this.views, atIndex);
if (isBlank(this.lightDom)) {
if (isBlank(this._getDirectParentLightDom())) {
ViewContainer.removeViewNodes(detachedView);
} else {
this.lightDom.redistribute();
this._getDirectParentLightDom().redistribute();
}
// content tags might have disappeared we need to do redistribution.
if (isPresent(this.hostLightDom)) {
this.hostLightDom.redistribute();
if (isPresent(this.parentView.hostLightDom)) {
this.parentView.hostLightDom.redistribute();
}
return detachedView;
}

View File

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

View File

@ -63,33 +63,21 @@ export class RenderViewHydrator {
}
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) {
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;
view.hostLightDom = hostLightDom;
// 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);
}
// content tags
for (var i = 0; i < view.contentTags.length; i++) {
var destLightDom = view.getDirectParentLightDom(i);
var ct = view.contentTags[i];
if (isPresent(ct)) {
ct.hydrate(destLightDom);
@ -167,26 +155,17 @@ export class RenderViewHydrator {
view.eventHandlerRemovers[i]();
}
view.hostLightDom = null;
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;
}
}