feat(compiler): attach components and project light dom during compilation.
Closes #2529 BREAKING CHANGES: - shadow dom emulation no longer supports the `<content>` tag. Use the new `<ng-content>` instead (works with all shadow dom strategies). - removed `DomRenderer.setViewRootNodes` and `AppViewManager.getComponentView` -> use `DomRenderer.getNativeElementSync(elementRef)` and change shadow dom directly - the `Renderer` interface has changed: * `createView` now also has to support sub views * the notion of a container has been removed. Instead, the renderer has to implement methods to attach views next to elements or other views. * a RenderView now contains multiple RenderFragments. Fragments are used to move DOM nodes around. Internal changes / design changes: - Introduce notion of view fragments on render side - DomProtoViews and DomViews on render side are merged, AppProtoViews are not merged, AppViews are partially merged (they share arrays with the other merged AppViews but we keep individual AppView instances for now). - DomProtoViews always have a `<template>` element as root * needed for storing subviews * we have less chunks of DOM to clone now - remove fake ElementBinder / Bound element for root text bindings and model them explicitly. This removes a lot of special cases we had! - AppView shares data with nested component views - some methods in AppViewManager (create, hydrate, dehydrate) are iterative now * now possible as we have all child AppViews / ElementRefs already in an array!
This commit is contained in:
@ -15,8 +15,7 @@ import {List, ListWrapper, Map, MapWrapper} from 'angular2/src/facade/collection
|
||||
|
||||
import {DirectiveResolver} from './directive_resolver';
|
||||
|
||||
import {AppProtoView} from './view';
|
||||
import {ElementBinder} from './element_binder';
|
||||
import {AppProtoView, AppProtoViewMergeMapping} from './view';
|
||||
import {ProtoViewRef} from './view_ref';
|
||||
import {DirectiveBinding} from './element_injector';
|
||||
import {ViewResolver} from './view_resolver';
|
||||
@ -91,6 +90,7 @@ export class Compiler {
|
||||
private _appUrl: string;
|
||||
private _render: renderApi.RenderCompiler;
|
||||
private _protoViewFactory: ProtoViewFactory;
|
||||
private _unmergedCyclicEmbeddedProtoViews: RecursiveEmbeddedProtoView[] = [];
|
||||
|
||||
/**
|
||||
* @private
|
||||
@ -137,13 +137,17 @@ export class Compiler {
|
||||
Compiler._assertTypeIsComponent(componentBinding);
|
||||
|
||||
var directiveMetadata = componentBinding.metadata;
|
||||
hostPvPromise = this._render.compileHost(directiveMetadata)
|
||||
.then((hostRenderPv) => {
|
||||
return this._compileNestedProtoViews(componentBinding, hostRenderPv,
|
||||
[componentBinding]);
|
||||
});
|
||||
hostPvPromise =
|
||||
this._render.compileHost(directiveMetadata)
|
||||
.then((hostRenderPv) => {
|
||||
var protoView = this._protoViewFactory.createAppProtoViews(
|
||||
componentBinding, hostRenderPv, [componentBinding]);
|
||||
this._compilerCache.setHost(componentType, protoView);
|
||||
return this._compileNestedProtoViews(hostRenderPv, protoView, componentType);
|
||||
});
|
||||
}
|
||||
return hostPvPromise.then((hostAppProtoView) => { return new ProtoViewRef(hostAppProtoView); });
|
||||
return hostPvPromise.then(hostAppProtoView => this._mergeCyclicEmbeddedProtoViews().then(
|
||||
_ => new ProtoViewRef(hostAppProtoView)));
|
||||
}
|
||||
|
||||
private _compile(componentBinding: DirectiveBinding): Promise<AppProtoView>| AppProtoView {
|
||||
@ -156,12 +160,12 @@ export class Compiler {
|
||||
return protoView;
|
||||
}
|
||||
|
||||
var pvPromise = this._compiling.get(component);
|
||||
if (isPresent(pvPromise)) {
|
||||
var resultPromise = this._compiling.get(component);
|
||||
if (isPresent(resultPromise)) {
|
||||
// The component is already being compiled, attach to the existing Promise
|
||||
// instead of re-compiling the component.
|
||||
// It happens when a template references a component multiple times.
|
||||
return pvPromise;
|
||||
return resultPromise;
|
||||
}
|
||||
var view = this._viewResolver.resolve(component);
|
||||
|
||||
@ -178,14 +182,19 @@ export class Compiler {
|
||||
ListWrapper.map(directives, (directive) => this._bindDirective(directive)));
|
||||
|
||||
var renderTemplate = this._buildRenderTemplate(component, view, boundDirectives);
|
||||
pvPromise =
|
||||
this._render.compile(renderTemplate)
|
||||
.then((renderPv) => {
|
||||
return this._compileNestedProtoViews(componentBinding, renderPv, boundDirectives);
|
||||
});
|
||||
resultPromise = this._render.compile(renderTemplate)
|
||||
.then((renderPv) => {
|
||||
var protoView = this._protoViewFactory.createAppProtoViews(
|
||||
componentBinding, renderPv, boundDirectives);
|
||||
// Populate the cache before compiling the nested components,
|
||||
// so that components can reference themselves in their template.
|
||||
this._compilerCache.set(component, protoView);
|
||||
MapWrapper.delete(this._compiling, component);
|
||||
|
||||
this._compiling.set(component, pvPromise);
|
||||
return pvPromise;
|
||||
return this._compileNestedProtoViews(renderPv, protoView, component);
|
||||
});
|
||||
this._compiling.set(component, resultPromise);
|
||||
return resultPromise;
|
||||
}
|
||||
|
||||
private _removeDuplicatedDirectives(directives: List<DirectiveBinding>): List<DirectiveBinding> {
|
||||
@ -194,24 +203,11 @@ export class Compiler {
|
||||
return MapWrapper.values(directivesMap);
|
||||
}
|
||||
|
||||
private _compileNestedProtoViews(componentBinding, renderPv, directives): Promise<AppProtoView>|
|
||||
AppProtoView {
|
||||
var protoViews =
|
||||
this._protoViewFactory.createAppProtoViews(componentBinding, renderPv, directives);
|
||||
var protoView = protoViews[0];
|
||||
if (isPresent(componentBinding)) {
|
||||
var component = componentBinding.key.token;
|
||||
if (renderPv.type === renderApi.ViewType.COMPONENT) {
|
||||
// Populate the cache before compiling the nested components,
|
||||
// so that components can reference themselves in their template.
|
||||
this._compilerCache.set(component, protoView);
|
||||
MapWrapper.delete(this._compiling, component);
|
||||
} else {
|
||||
this._compilerCache.setHost(component, protoView);
|
||||
}
|
||||
}
|
||||
private _compileNestedProtoViews(renderProtoView: renderApi.ProtoViewDto,
|
||||
appProtoView: AppProtoView,
|
||||
componentType: Type): Promise<AppProtoView> {
|
||||
var nestedPVPromises = [];
|
||||
ListWrapper.forEach(this._collectComponentElementBinders(protoViews), (elementBinder) => {
|
||||
this._loopComponentElementBinders(appProtoView, (parentPv, elementBinder) => {
|
||||
var nestedComponent = elementBinder.componentDirective;
|
||||
var elementBinderDone =
|
||||
(nestedPv: AppProtoView) => { elementBinder.nestedProtoView = nestedPv; };
|
||||
@ -222,24 +218,85 @@ export class Compiler {
|
||||
elementBinderDone(<AppProtoView>nestedCall);
|
||||
}
|
||||
});
|
||||
|
||||
if (nestedPVPromises.length > 0) {
|
||||
return PromiseWrapper.all(nestedPVPromises).then((_) => protoView);
|
||||
} else {
|
||||
return protoView;
|
||||
}
|
||||
return PromiseWrapper.all(nestedPVPromises)
|
||||
.then((_) => {
|
||||
var appProtoViewsToMergeInto = [];
|
||||
var mergeRenderProtoViews = this._collectMergeRenderProtoViewsRecurse(
|
||||
renderProtoView, appProtoView, appProtoViewsToMergeInto);
|
||||
if (isBlank(mergeRenderProtoViews)) {
|
||||
throw new BaseException(`Unconditional component cycle in ${stringify(componentType)}`);
|
||||
}
|
||||
return this._mergeProtoViews(appProtoViewsToMergeInto, mergeRenderProtoViews);
|
||||
});
|
||||
}
|
||||
|
||||
private _collectComponentElementBinders(protoViews: List<AppProtoView>): List<ElementBinder> {
|
||||
var componentElementBinders = [];
|
||||
ListWrapper.forEach(protoViews, (protoView) => {
|
||||
ListWrapper.forEach(protoView.elementBinders, (elementBinder) => {
|
||||
if (isPresent(elementBinder.componentDirective)) {
|
||||
componentElementBinders.push(elementBinder);
|
||||
private _mergeProtoViews(
|
||||
appProtoViewsToMergeInto: AppProtoView[],
|
||||
mergeRenderProtoViews:
|
||||
List<renderApi.RenderProtoViewRef | List<any>>): Promise<AppProtoView> {
|
||||
return this._render.mergeProtoViewsRecursively(mergeRenderProtoViews)
|
||||
.then((mergeResults: List<renderApi.RenderProtoViewMergeMapping>) => {
|
||||
// Note: We don't need to check for nulls here as we filtered them out before!
|
||||
// (in RenderCompiler.mergeProtoViewsRecursively and
|
||||
// _collectMergeRenderProtoViewsRecurse).
|
||||
for (var i = 0; i < mergeResults.length; i++) {
|
||||
appProtoViewsToMergeInto[i].mergeMapping =
|
||||
new AppProtoViewMergeMapping(mergeResults[i]);
|
||||
}
|
||||
return appProtoViewsToMergeInto[0];
|
||||
});
|
||||
}
|
||||
|
||||
private _loopComponentElementBinders(appProtoView: AppProtoView, callback: Function) {
|
||||
appProtoView.elementBinders.forEach((elementBinder) => {
|
||||
if (isPresent(elementBinder.componentDirective)) {
|
||||
callback(appProtoView, elementBinder);
|
||||
} else if (isPresent(elementBinder.nestedProtoView)) {
|
||||
this._loopComponentElementBinders(elementBinder.nestedProtoView, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _collectMergeRenderProtoViewsRecurse(
|
||||
renderProtoView: renderApi.ProtoViewDto, appProtoView: AppProtoView,
|
||||
targetAppProtoViews: AppProtoView[]): List<renderApi.RenderProtoViewRef | List<any>> {
|
||||
targetAppProtoViews.push(appProtoView);
|
||||
var result = [renderProtoView.render];
|
||||
for (var i = 0; i < appProtoView.elementBinders.length; i++) {
|
||||
var binder = appProtoView.elementBinders[i];
|
||||
if (binder.hasStaticComponent()) {
|
||||
if (isBlank(binder.nestedProtoView.mergeMapping)) {
|
||||
// cycle via an embedded ProtoView. store the AppProtoView and ProtoViewDto for later.
|
||||
this._unmergedCyclicEmbeddedProtoViews.push(
|
||||
new RecursiveEmbeddedProtoView(appProtoView, renderProtoView));
|
||||
return null;
|
||||
}
|
||||
result.push(binder.nestedProtoView.mergeMapping.renderProtoViewRef);
|
||||
} else if (binder.hasEmbeddedProtoView()) {
|
||||
result.push(this._collectMergeRenderProtoViewsRecurse(
|
||||
renderProtoView.elementBinders[i].nestedProtoView, binder.nestedProtoView,
|
||||
targetAppProtoViews));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private _mergeCyclicEmbeddedProtoViews() {
|
||||
var pvs = this._unmergedCyclicEmbeddedProtoViews;
|
||||
this._unmergedCyclicEmbeddedProtoViews = [];
|
||||
var promises = pvs.map(entry => {
|
||||
var appProtoView = entry.appProtoView;
|
||||
var mergeRenderProtoViews = [entry.renderProtoView.render];
|
||||
appProtoView.elementBinders.forEach((binder) => {
|
||||
if (binder.hasStaticComponent()) {
|
||||
mergeRenderProtoViews.push(binder.nestedProtoView.mergeMapping.renderProtoViewRef);
|
||||
} else if (binder.hasEmbeddedProtoView()) {
|
||||
mergeRenderProtoViews.push(null);
|
||||
}
|
||||
});
|
||||
return this._mergeProtoViews([appProtoView], mergeRenderProtoViews);
|
||||
});
|
||||
return componentElementBinders;
|
||||
return PromiseWrapper.all(promises);
|
||||
}
|
||||
|
||||
private _buildRenderTemplate(component, view, directives): renderApi.ViewDefinition {
|
||||
@ -299,3 +356,7 @@ export class Compiler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RecursiveEmbeddedProtoView {
|
||||
constructor(public appProtoView: AppProtoView, public renderProtoView: renderApi.ProtoViewDto) {}
|
||||
}
|
||||
|
@ -278,7 +278,7 @@ export class DirectiveBinding extends ResolvedBinding {
|
||||
// TODO(rado): benchmark and consider rolling in as ElementInjector fields.
|
||||
export class PreBuiltObjects {
|
||||
constructor(public viewManager: avmModule.AppViewManager, public view: viewModule.AppView,
|
||||
public protoView: viewModule.AppProtoView) {}
|
||||
public elementRef: ElementRef, public protoView: viewModule.AppProtoView) {}
|
||||
}
|
||||
|
||||
export class EventEmitterAccessor {
|
||||
@ -575,7 +575,7 @@ export class ElementInjector extends TreeNode<ElementInjector> implements Depend
|
||||
|
||||
getComponent(): any { return this._strategy.getComponent(); }
|
||||
|
||||
getElementRef(): ElementRef { return this._preBuiltObjects.view.elementRefs[this._proto.index]; }
|
||||
getElementRef(): ElementRef { return this._preBuiltObjects.elementRef; }
|
||||
|
||||
getViewContainerRef(): ViewContainerRef {
|
||||
return new ViewContainerRef(this._preBuiltObjects.viewManager, this.getElementRef());
|
||||
@ -606,7 +606,8 @@ export class ElementInjector extends TreeNode<ElementInjector> implements Depend
|
||||
// We provide the component's view change detector to components and
|
||||
// the surrounding component's change detector to directives.
|
||||
if (dirBin.metadata.type === DirectiveMetadata.COMPONENT_TYPE) {
|
||||
var componentView = this._preBuiltObjects.view.componentChildViews[this._proto.index];
|
||||
var componentView = this._preBuiltObjects.view.getNestedView(
|
||||
this._preBuiltObjects.elementRef.boundElementIndex);
|
||||
return componentView.changeDetector.ref;
|
||||
} else {
|
||||
return this._preBuiltObjects.view.changeDetector.ref;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {BaseException} from 'angular2/src/facade/lang';
|
||||
import {BaseException, isPresent} from 'angular2/src/facade/lang';
|
||||
import {ViewRef} from './view_ref';
|
||||
import {RenderViewRef, RenderElementRef, Renderer} from 'angular2/src/render/api';
|
||||
|
||||
@ -24,9 +24,18 @@ export class ElementRef implements RenderElementRef {
|
||||
*/
|
||||
boundElementIndex: number;
|
||||
|
||||
constructor(parentView: ViewRef, boundElementIndex: number, private _renderer: Renderer) {
|
||||
/**
|
||||
* Index of the element inside the {@link RenderViewRef}.
|
||||
*
|
||||
* This is used internally by the Angular framework to locate elements.
|
||||
*/
|
||||
renderBoundElementIndex: number;
|
||||
|
||||
constructor(parentView: ViewRef, boundElementIndex: number, renderBoundElementIndex: number,
|
||||
private _renderer: Renderer) {
|
||||
this.parentView = parentView;
|
||||
this.boundElementIndex = boundElementIndex;
|
||||
this.renderBoundElementIndex = renderBoundElementIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -11,7 +11,8 @@ import {
|
||||
DirectiveRecord,
|
||||
ProtoChangeDetector,
|
||||
DEFAULT,
|
||||
ChangeDetectorDefinition
|
||||
ChangeDetectorDefinition,
|
||||
ASTWithSource
|
||||
} from 'angular2/change_detection';
|
||||
|
||||
import * as renderApi from 'angular2/src/render/api';
|
||||
@ -21,16 +22,16 @@ import {ProtoElementInjector, DirectiveBinding} from './element_injector';
|
||||
|
||||
class BindingRecordsCreator {
|
||||
_directiveRecordsMap: Map<number, DirectiveRecord> = new Map();
|
||||
_textNodeIndex: number = 0;
|
||||
|
||||
getBindingRecords(elementBinders: List<renderApi.ElementBinder>,
|
||||
getBindingRecords(textBindings: List<ASTWithSource>,
|
||||
elementBinders: List<renderApi.ElementBinder>,
|
||||
allDirectiveMetadatas: List<renderApi.DirectiveMetadata>): List<BindingRecord> {
|
||||
var bindings = [];
|
||||
|
||||
this._createTextNodeRecords(bindings, textBindings);
|
||||
for (var boundElementIndex = 0; boundElementIndex < elementBinders.length;
|
||||
boundElementIndex++) {
|
||||
var renderElementBinder = elementBinders[boundElementIndex];
|
||||
this._createTextNodeRecords(bindings, renderElementBinder);
|
||||
this._createElementPropertyRecords(bindings, boundElementIndex, renderElementBinder);
|
||||
this._createDirectiveRecords(bindings, boundElementIndex, renderElementBinder.directives,
|
||||
allDirectiveMetadatas);
|
||||
@ -55,13 +56,10 @@ class BindingRecordsCreator {
|
||||
return directiveRecords;
|
||||
}
|
||||
|
||||
_createTextNodeRecords(bindings: List<BindingRecord>,
|
||||
renderElementBinder: renderApi.ElementBinder) {
|
||||
if (isBlank(renderElementBinder.textBindings)) return;
|
||||
|
||||
ListWrapper.forEach(renderElementBinder.textBindings, (b) => {
|
||||
bindings.push(BindingRecord.createForTextNode(b, this._textNodeIndex++));
|
||||
});
|
||||
_createTextNodeRecords(bindings: List<BindingRecord>, textBindings: List<ASTWithSource>) {
|
||||
for (var i = 0; i < textBindings.length; i++) {
|
||||
bindings.push(BindingRecord.createForTextNode(textBindings[i], i));
|
||||
}
|
||||
}
|
||||
|
||||
_createElementPropertyRecords(bindings: List<BindingRecord>, boundElementIndex: number,
|
||||
@ -162,7 +160,7 @@ export class ProtoViewFactory {
|
||||
|
||||
createAppProtoViews(hostComponentBinding: DirectiveBinding,
|
||||
rootRenderProtoView: renderApi.ProtoViewDto,
|
||||
allDirectives: List<DirectiveBinding>): List<AppProtoView> {
|
||||
allDirectives: List<DirectiveBinding>): AppProtoView {
|
||||
var allRenderDirectiveMetadata =
|
||||
ListWrapper.map(allDirectives, directiveBinding => directiveBinding.metadata);
|
||||
var nestedPvsWithIndex = _collectNestedProtoViews(rootRenderProtoView);
|
||||
@ -176,7 +174,7 @@ export class ProtoViewFactory {
|
||||
changeDetectorDefs,
|
||||
changeDetectorDef => this._changeDetection.createProtoChangeDetector(changeDetectorDef));
|
||||
var appProtoViews = ListWrapper.createFixedSize(nestedPvsWithIndex.length);
|
||||
ListWrapper.forEach(nestedPvsWithIndex, (pvWithIndex) => {
|
||||
ListWrapper.forEach(nestedPvsWithIndex, (pvWithIndex: RenderProtoViewWithIndex) => {
|
||||
var appProtoView =
|
||||
_createAppProtoView(pvWithIndex.renderProtoView, protoChangeDetectors[pvWithIndex.index],
|
||||
nestedPvVariableBindings[pvWithIndex.index], allDirectives);
|
||||
@ -186,7 +184,7 @@ export class ProtoViewFactory {
|
||||
}
|
||||
appProtoViews[pvWithIndex.index] = appProtoView;
|
||||
});
|
||||
return appProtoViews;
|
||||
return appProtoViews[0];
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,6 +207,7 @@ function _collectNestedProtoViews(
|
||||
if (isBlank(result)) {
|
||||
result = [];
|
||||
}
|
||||
// reserve the place in the array
|
||||
result.push(
|
||||
new RenderProtoViewWithIndex(renderProtoView, result.length, parentIndex, boundElementIndex));
|
||||
var currentIndex = result.length - 1;
|
||||
@ -230,8 +229,8 @@ function _getChangeDetectorDefinitions(
|
||||
return ListWrapper.map(nestedPvsWithIndex, (pvWithIndex) => {
|
||||
var elementBinders = pvWithIndex.renderProtoView.elementBinders;
|
||||
var bindingRecordsCreator = new BindingRecordsCreator();
|
||||
var bindingRecords =
|
||||
bindingRecordsCreator.getBindingRecords(elementBinders, allRenderDirectiveMetadata);
|
||||
var bindingRecords = bindingRecordsCreator.getBindingRecords(
|
||||
pvWithIndex.renderProtoView.textBindings, elementBinders, allRenderDirectiveMetadata);
|
||||
var directiveRecords =
|
||||
bindingRecordsCreator.getDirectiveRecords(elementBinders, allRenderDirectiveMetadata);
|
||||
var strategyName = DEFAULT;
|
||||
@ -255,8 +254,9 @@ function _createAppProtoView(
|
||||
renderProtoView: renderApi.ProtoViewDto, protoChangeDetector: ProtoChangeDetector,
|
||||
variableBindings: Map<string, string>, allDirectives: List<DirectiveBinding>): AppProtoView {
|
||||
var elementBinders = renderProtoView.elementBinders;
|
||||
var protoView = new AppProtoView(renderProtoView.render, protoChangeDetector, variableBindings,
|
||||
createVariableLocations(elementBinders));
|
||||
var protoView = new AppProtoView(renderProtoView.type, protoChangeDetector, variableBindings,
|
||||
createVariableLocations(elementBinders),
|
||||
renderProtoView.textBindings.length);
|
||||
_createElementBinders(protoView, elementBinders, allDirectives);
|
||||
_bindDirectiveEvents(protoView, elementBinders);
|
||||
|
||||
|
@ -20,10 +20,46 @@ import {
|
||||
import {ElementBinder} from './element_binder';
|
||||
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
|
||||
import * as renderApi from 'angular2/src/render/api';
|
||||
import {EventDispatcher} from 'angular2/src/render/api';
|
||||
import {ViewRef} from './view_ref';
|
||||
import {RenderEventDispatcher} from 'angular2/src/render/api';
|
||||
import {ViewRef, internalView} from './view_ref';
|
||||
import {ElementRef} from './element_ref';
|
||||
|
||||
export class AppProtoViewMergeMapping {
|
||||
renderProtoViewRef: renderApi.RenderProtoViewRef;
|
||||
renderFragmentCount: number;
|
||||
renderElementIndices: number[];
|
||||
renderInverseElementIndices: number[];
|
||||
renderTextIndices: number[];
|
||||
nestedViewIndicesByElementIndex: number[];
|
||||
hostElementIndicesByViewIndex: number[];
|
||||
constructor(renderProtoViewMergeMapping: renderApi.RenderProtoViewMergeMapping) {
|
||||
this.renderProtoViewRef = renderProtoViewMergeMapping.mergedProtoViewRef;
|
||||
this.renderFragmentCount = renderProtoViewMergeMapping.fragmentCount;
|
||||
this.renderElementIndices = renderProtoViewMergeMapping.mappedElementIndices;
|
||||
this.renderInverseElementIndices =
|
||||
inverseIndexMapping(this.renderElementIndices, this.renderElementIndices.length);
|
||||
this.renderTextIndices = renderProtoViewMergeMapping.mappedTextIndices;
|
||||
this.hostElementIndicesByViewIndex = renderProtoViewMergeMapping.hostElementIndicesByViewIndex;
|
||||
this.nestedViewIndicesByElementIndex =
|
||||
inverseIndexMapping(this.hostElementIndicesByViewIndex, this.renderElementIndices.length);
|
||||
}
|
||||
|
||||
get viewCount() { return this.hostElementIndicesByViewIndex.length; }
|
||||
|
||||
get elementCount() { return this.renderElementIndices.length; }
|
||||
}
|
||||
|
||||
function inverseIndexMapping(input: number[], resultLength: number): number[] {
|
||||
var result = ListWrapper.createFixedSize(resultLength);
|
||||
for (var i = 0; i < input.length; i++) {
|
||||
var value = input[i];
|
||||
if (isPresent(value)) {
|
||||
result[input[i]] = i;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export class AppViewContainer {
|
||||
// The order in this list matches the DOM order.
|
||||
views: List<AppView> = [];
|
||||
@ -33,17 +69,34 @@ export class AppViewContainer {
|
||||
* Cost of making objects: http://jsperf.com/instantiate-size-of-object
|
||||
*
|
||||
*/
|
||||
export class AppView implements ChangeDispatcher, EventDispatcher {
|
||||
render: renderApi.RenderViewRef = null;
|
||||
/// This list matches the _nodes list. It is sparse, since only Elements have ElementInjector
|
||||
export class AppView implements ChangeDispatcher, RenderEventDispatcher {
|
||||
// AppViews that have been merged in depth first order.
|
||||
// This list is shared between all merged views. Use this.elementOffset to get the local
|
||||
// entries.
|
||||
views: List<AppView> = null;
|
||||
// root elementInjectors of this AppView
|
||||
// This list is local to this AppView and not shared with other Views.
|
||||
rootElementInjectors: List<ElementInjector>;
|
||||
// ElementInjectors of all AppViews in views grouped by view.
|
||||
// This list is shared between all merged views. Use this.elementOffset to get the local
|
||||
// entries.
|
||||
elementInjectors: List<ElementInjector> = null;
|
||||
changeDetector: ChangeDetector = null;
|
||||
componentChildViews: List<AppView> = null;
|
||||
viewContainers: List<AppViewContainer>;
|
||||
// ViewContainers of all AppViews in views grouped by view.
|
||||
// This list is shared between all merged views. Use this.elementOffset to get the local
|
||||
// entries.
|
||||
viewContainers: List<AppViewContainer> = null;
|
||||
// PreBuiltObjects of all AppViews in views grouped by view.
|
||||
// This list is shared between all merged views. Use this.elementOffset to get the local
|
||||
// entries.
|
||||
preBuiltObjects: List<PreBuiltObjects> = null;
|
||||
// ElementRef of all AppViews in views grouped by view.
|
||||
// This list is shared between all merged views. Use this.elementOffset to get the local
|
||||
// entries.
|
||||
elementRefs: List<ElementRef>;
|
||||
|
||||
ref: ViewRef;
|
||||
changeDetector: ChangeDetector = null;
|
||||
|
||||
|
||||
/**
|
||||
* The context against which data-binding expressions in this view are evaluated against.
|
||||
@ -60,24 +113,26 @@ export class AppView implements ChangeDispatcher, EventDispatcher {
|
||||
locals: Locals;
|
||||
|
||||
constructor(public renderer: renderApi.Renderer, public proto: AppProtoView,
|
||||
protoLocals: Map<string, any>) {
|
||||
this.viewContainers = ListWrapper.createFixedSize(this.proto.elementBinders.length);
|
||||
this.elementRefs = ListWrapper.createFixedSize(this.proto.elementBinders.length);
|
||||
public mainMergeMapping: AppProtoViewMergeMapping, public viewOffset: number,
|
||||
public elementOffset: number, public textOffset: number,
|
||||
protoLocals: Map<string, any>, public render: renderApi.RenderViewRef,
|
||||
public renderFragment: renderApi.RenderFragmentRef) {
|
||||
this.ref = new ViewRef(this);
|
||||
for (var i = 0; i < this.elementRefs.length; i++) {
|
||||
this.elementRefs[i] = new ElementRef(this.ref, i, renderer);
|
||||
}
|
||||
|
||||
this.locals = new Locals(null, MapWrapper.clone(protoLocals)); // TODO optimize this
|
||||
}
|
||||
|
||||
init(changeDetector: ChangeDetector, elementInjectors: List<ElementInjector>,
|
||||
rootElementInjectors: List<ElementInjector>, preBuiltObjects: List<PreBuiltObjects>,
|
||||
componentChildViews: List<AppView>) {
|
||||
views: List<AppView>, elementRefs: List<ElementRef>,
|
||||
viewContainers: List<AppViewContainer>) {
|
||||
this.changeDetector = changeDetector;
|
||||
this.elementInjectors = elementInjectors;
|
||||
this.rootElementInjectors = rootElementInjectors;
|
||||
this.preBuiltObjects = preBuiltObjects;
|
||||
this.componentChildViews = componentChildViews;
|
||||
this.views = views;
|
||||
this.elementRefs = elementRefs;
|
||||
this.viewContainers = viewContainers;
|
||||
}
|
||||
|
||||
setLocal(contextName: string, value): void {
|
||||
@ -98,49 +153,57 @@ export class AppView implements ChangeDispatcher, EventDispatcher {
|
||||
*
|
||||
* @param {string} eventName
|
||||
* @param {*} eventObj
|
||||
* @param {int} binderIndex
|
||||
* @param {int} boundElementIndex
|
||||
*/
|
||||
triggerEventHandlers(eventName: string, eventObj, binderIndex: int): void {
|
||||
triggerEventHandlers(eventName: string, eventObj, boundElementIndex: int): void {
|
||||
var locals = new Map();
|
||||
locals.set('$event', eventObj);
|
||||
this.dispatchEvent(binderIndex, eventName, locals);
|
||||
this.dispatchEvent(boundElementIndex, eventName, locals);
|
||||
}
|
||||
|
||||
// dispatch to element injector or text nodes based on context
|
||||
notifyOnBinding(b: BindingRecord, currentValue: any): void {
|
||||
if (b.isElementProperty()) {
|
||||
this.renderer.setElementProperty(this.elementRefs[b.elementIndex], b.propertyName,
|
||||
currentValue);
|
||||
} else if (b.isElementAttribute()) {
|
||||
this.renderer.setElementAttribute(this.elementRefs[b.elementIndex], b.propertyName,
|
||||
currentValue);
|
||||
} else if (b.isElementClass()) {
|
||||
this.renderer.setElementClass(this.elementRefs[b.elementIndex], b.propertyName, currentValue);
|
||||
} else if (b.isElementStyle()) {
|
||||
var unit = isPresent(b.propertyUnit) ? b.propertyUnit : '';
|
||||
this.renderer.setElementStyle(this.elementRefs[b.elementIndex], b.propertyName,
|
||||
`${currentValue}${unit}`);
|
||||
} else if (b.isTextNode()) {
|
||||
this.renderer.setText(this.render, b.elementIndex, currentValue);
|
||||
if (b.isTextNode()) {
|
||||
this.renderer.setText(
|
||||
this.render, this.mainMergeMapping.renderTextIndices[b.elementIndex + this.textOffset],
|
||||
currentValue);
|
||||
} else {
|
||||
throw new BaseException('Unsupported directive record');
|
||||
var elementRef = this.elementRefs[this.elementOffset + b.elementIndex];
|
||||
if (b.isElementProperty()) {
|
||||
this.renderer.setElementProperty(elementRef, b.propertyName, currentValue);
|
||||
} else if (b.isElementAttribute()) {
|
||||
this.renderer.setElementAttribute(elementRef, b.propertyName, currentValue);
|
||||
} else if (b.isElementClass()) {
|
||||
this.renderer.setElementClass(elementRef, b.propertyName, currentValue);
|
||||
} else if (b.isElementStyle()) {
|
||||
var unit = isPresent(b.propertyUnit) ? b.propertyUnit : '';
|
||||
this.renderer.setElementStyle(elementRef, b.propertyName, `${currentValue}${unit}`);
|
||||
} else {
|
||||
throw new BaseException('Unsupported directive record');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notifyOnAllChangesDone(): void {
|
||||
var eiCount = this.proto.elementBinders.length;
|
||||
var ei = this.elementInjectors;
|
||||
for (var i = ei.length - 1; i >= 0; i--) {
|
||||
if (isPresent(ei[i])) ei[i].onAllChangesDone();
|
||||
for (var i = eiCount - 1; i >= 0; i--) {
|
||||
if (isPresent(ei[i + this.elementOffset])) ei[i + this.elementOffset].onAllChangesDone();
|
||||
}
|
||||
}
|
||||
|
||||
getDirectiveFor(directive: DirectiveIndex): any {
|
||||
var elementInjector = this.elementInjectors[directive.elementIndex];
|
||||
var elementInjector = this.elementInjectors[this.elementOffset + directive.elementIndex];
|
||||
return elementInjector.getDirectiveAtIndex(directive.directiveIndex);
|
||||
}
|
||||
|
||||
getNestedView(boundElementIndex: number): AppView {
|
||||
var viewIndex = this.mainMergeMapping.nestedViewIndicesByElementIndex[boundElementIndex];
|
||||
return isPresent(viewIndex) ? this.views[viewIndex] : null;
|
||||
}
|
||||
|
||||
getDetectorFor(directive: DirectiveIndex): any {
|
||||
var childView = this.componentChildViews[directive.elementIndex];
|
||||
var childView = this.getNestedView(this.elementOffset + directive.elementIndex);
|
||||
return isPresent(childView) ? childView.changeDetector : null;
|
||||
}
|
||||
|
||||
@ -148,15 +211,24 @@ export class AppView implements ChangeDispatcher, EventDispatcher {
|
||||
this.renderer.invokeElementMethod(this.elementRefs[elementIndex], methodName, args);
|
||||
}
|
||||
|
||||
// implementation of EventDispatcher#dispatchEvent
|
||||
// implementation of RenderEventDispatcher#dispatchRenderEvent
|
||||
dispatchRenderEvent(renderElementIndex: number, eventName: string,
|
||||
locals: Map<string, any>): boolean {
|
||||
var elementRef =
|
||||
this.elementRefs[this.proto.mergeMapping.renderInverseElementIndices[renderElementIndex]];
|
||||
var view = internalView(elementRef.parentView);
|
||||
return view.dispatchEvent(elementRef.boundElementIndex, eventName, locals);
|
||||
}
|
||||
|
||||
|
||||
// returns false if preventDefault must be applied to the DOM event
|
||||
dispatchEvent(elementIndex: number, eventName: string, locals: Map<string, any>): boolean {
|
||||
dispatchEvent(boundElementIndex: number, eventName: string, locals: Map<string, any>): boolean {
|
||||
// Most of the time the event will be fired only when the view is in the live document.
|
||||
// However, in a rare circumstance the view might get dehydrated, in between the event
|
||||
// queuing up and firing.
|
||||
var allowDefaultBehavior = true;
|
||||
if (this.hydrated()) {
|
||||
var elBinder = this.proto.elementBinders[elementIndex];
|
||||
var elBinder = this.proto.elementBinders[boundElementIndex - this.elementOffset];
|
||||
if (isBlank(elBinder.hostListeners)) return allowDefaultBehavior;
|
||||
var eventMap = elBinder.hostListeners[eventName];
|
||||
if (isBlank(eventMap)) return allowDefaultBehavior;
|
||||
@ -165,7 +237,7 @@ export class AppView implements ChangeDispatcher, EventDispatcher {
|
||||
if (directiveIndex === -1) {
|
||||
context = this.context;
|
||||
} else {
|
||||
context = this.elementInjectors[elementIndex].getDirectiveAtIndex(directiveIndex);
|
||||
context = this.elementInjectors[boundElementIndex].getDirectiveAtIndex(directiveIndex);
|
||||
}
|
||||
var result = expr.eval(context, new Locals(this.locals, locals));
|
||||
if (isPresent(result)) {
|
||||
@ -183,11 +255,11 @@ export class AppView implements ChangeDispatcher, EventDispatcher {
|
||||
export class AppProtoView {
|
||||
elementBinders: List<ElementBinder> = [];
|
||||
protoLocals: Map<string, any> = new Map();
|
||||
mergeMapping: AppProtoViewMergeMapping;
|
||||
|
||||
constructor(public render: renderApi.RenderProtoViewRef,
|
||||
public protoChangeDetector: ProtoChangeDetector,
|
||||
constructor(public type: renderApi.ViewType, public protoChangeDetector: ProtoChangeDetector,
|
||||
public variableBindings: Map<string, string>,
|
||||
public variableLocations: Map<string, number>) {
|
||||
public variableLocations: Map<string, number>, public textBindingCount: number) {
|
||||
if (isPresent(variableBindings)) {
|
||||
MapWrapper.forEach(variableBindings,
|
||||
(templateName, _) => { this.protoLocals.set(templateName, null); });
|
||||
|
@ -4,7 +4,13 @@ import * as viewModule from './view';
|
||||
import {ElementRef} from './element_ref';
|
||||
import {ProtoViewRef, ViewRef, internalView, internalProtoView} from './view_ref';
|
||||
import {ViewContainerRef} from './view_container_ref';
|
||||
import {Renderer, RenderViewRef} from 'angular2/src/render/api';
|
||||
import {
|
||||
Renderer,
|
||||
RenderViewRef,
|
||||
RenderFragmentRef,
|
||||
RenderViewWithFragments,
|
||||
ViewType
|
||||
} from 'angular2/src/render/api';
|
||||
import {AppViewManagerUtils} from './view_manager_utils';
|
||||
import {AppViewPool} from './view_pool';
|
||||
import {AppViewListener} from './view_listener';
|
||||
@ -22,18 +28,6 @@ export class AppViewManager {
|
||||
constructor(private _viewPool: AppViewPool, private _viewListener: AppViewListener,
|
||||
private _utils: AppViewManagerUtils, private _renderer: Renderer) {}
|
||||
|
||||
/**
|
||||
* Returns associated Component {@link ViewRef} from {@link ElementRef}.
|
||||
*
|
||||
* If an {@link ElementRef} is from an element which has a component, this method returns
|
||||
* the component's {@link ViewRef}.
|
||||
*/
|
||||
getComponentView(hostLocation: ElementRef): ViewRef {
|
||||
var hostView: viewModule.AppView = internalView(hostLocation.parentView);
|
||||
var boundElementIndex = hostLocation.boundElementIndex;
|
||||
return hostView.componentChildViews[boundElementIndex].ref;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link ViewContainerRef} at the {@link ElementRef} location.
|
||||
*/
|
||||
@ -47,7 +41,8 @@ export class AppViewManager {
|
||||
*/
|
||||
// TODO(misko): remove https://github.com/angular/angular/issues/2891
|
||||
getHostElement(hostViewRef: ViewRef): ElementRef {
|
||||
return internalView(hostViewRef).elementRefs[0];
|
||||
var hostView = internalView(hostViewRef);
|
||||
return hostView.elementRefs[hostView.elementOffset];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -62,15 +57,15 @@ export class AppViewManager {
|
||||
getNamedElementInComponentView(hostLocation: ElementRef, variableName: string): ElementRef {
|
||||
var hostView = internalView(hostLocation.parentView);
|
||||
var boundElementIndex = hostLocation.boundElementIndex;
|
||||
var componentView = hostView.componentChildViews[boundElementIndex];
|
||||
var componentView = hostView.getNestedView(boundElementIndex);
|
||||
if (isBlank(componentView)) {
|
||||
throw new BaseException(`There is no component directive at element ${boundElementIndex}`);
|
||||
}
|
||||
var elementIndex = componentView.proto.variableLocations.get(variableName);
|
||||
if (isBlank(elementIndex)) {
|
||||
var binderIdx = componentView.proto.variableLocations.get(variableName);
|
||||
if (isBlank(binderIdx)) {
|
||||
throw new BaseException(`Could not find variable ${variableName}`);
|
||||
}
|
||||
return componentView.elementRefs[elementIndex];
|
||||
return componentView.elementRefs[componentView.elementOffset + binderIdx];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -146,14 +141,13 @@ export class AppViewManager {
|
||||
if (isBlank(hostElementSelector)) {
|
||||
hostElementSelector = hostProtoView.elementBinders[0].componentDirective.metadata.selector;
|
||||
}
|
||||
var renderView = this._renderer.createRootHostView(hostProtoView.render, hostElementSelector);
|
||||
var hostView = this._utils.createView(hostProtoView, renderView, this, this._renderer);
|
||||
this._renderer.setEventDispatcher(hostView.render, hostView);
|
||||
this._createViewRecurse(hostView);
|
||||
this._viewListener.viewCreated(hostView);
|
||||
var renderViewWithFragments = this._renderer.createRootHostView(
|
||||
hostProtoView.mergeMapping.renderProtoViewRef,
|
||||
hostProtoView.mergeMapping.renderFragmentCount, hostElementSelector);
|
||||
var hostView = this._createMainView(hostProtoView, renderViewWithFragments);
|
||||
|
||||
this._renderer.hydrateView(hostView.render);
|
||||
this._utils.hydrateRootHostView(hostView, injector);
|
||||
this._viewHydrateRecurse(hostView);
|
||||
|
||||
return hostView.ref;
|
||||
}
|
||||
@ -162,14 +156,14 @@ export class AppViewManager {
|
||||
* Remove the View created with {@link AppViewManager#createRootHostView}.
|
||||
*/
|
||||
destroyRootHostView(hostViewRef: ViewRef) {
|
||||
// Note: Don't detach the hostView as we want to leave the
|
||||
// root element in place. Also don't put the hostView into the view pool
|
||||
// Note: Don't put the hostView into the view pool
|
||||
// as it is depending on the element for which it was created.
|
||||
var hostView = internalView(hostViewRef);
|
||||
// We do want to destroy the component view though.
|
||||
this._viewDehydrateRecurse(hostView, true);
|
||||
this._renderer.destroyView(hostView.render);
|
||||
this._renderer.detachFragment(hostView.renderFragment);
|
||||
this._renderer.dehydrateView(hostView.render);
|
||||
this._viewDehydrateRecurse(hostView);
|
||||
this._viewListener.viewDestroyed(hostView);
|
||||
this._renderer.destroyView(hostView.render);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -187,19 +181,42 @@ export class AppViewManager {
|
||||
if (isPresent(context)) {
|
||||
contextView = internalView(context.parentView);
|
||||
contextBoundElementIndex = context.boundElementIndex;
|
||||
} else {
|
||||
contextView = parentView;
|
||||
contextBoundElementIndex = boundElementIndex;
|
||||
}
|
||||
|
||||
var view = this._createPooledView(protoView);
|
||||
|
||||
this._renderer.attachViewInContainer(viewContainerLocation, atIndex, view.render);
|
||||
var embeddedFragmentView = contextView.getNestedView(contextBoundElementIndex);
|
||||
var view;
|
||||
if (isPresent(embeddedFragmentView) && !embeddedFragmentView.hydrated()) {
|
||||
// Case 1: instantiate the first view of a template that has been merged into a parent
|
||||
view = embeddedFragmentView;
|
||||
this._attachRenderView(parentView, boundElementIndex, atIndex, view);
|
||||
} else {
|
||||
// Case 2: instantiate another copy of the template. This is a separate case
|
||||
// as we only inline one copy of the template into the parent view.
|
||||
view = this._createPooledView(protoView);
|
||||
this._attachRenderView(parentView, boundElementIndex, atIndex, view);
|
||||
this._renderer.hydrateView(view.render);
|
||||
}
|
||||
this._utils.attachViewInContainer(parentView, boundElementIndex, contextView,
|
||||
contextBoundElementIndex, atIndex, view);
|
||||
this._utils.hydrateViewInContainer(parentView, boundElementIndex, contextView,
|
||||
contextBoundElementIndex, atIndex, bindings);
|
||||
this._viewHydrateRecurse(view);
|
||||
return view.ref;
|
||||
}
|
||||
|
||||
_attachRenderView(parentView: viewModule.AppView, boundElementIndex: number, atIndex: number,
|
||||
view: viewModule.AppView) {
|
||||
var elementRef = parentView.elementRefs[boundElementIndex];
|
||||
if (atIndex === 0) {
|
||||
this._renderer.attachFragmentAfterElement(elementRef, view.renderFragment);
|
||||
} else {
|
||||
var prevView = parentView.viewContainers[boundElementIndex].views[atIndex - 1];
|
||||
this._renderer.attachFragmentAfterFragment(prevView.renderFragment, view.renderFragment);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* See {@link AppViewManager#createViewInContainer}.
|
||||
@ -226,7 +243,7 @@ export class AppViewManager {
|
||||
// Right now we are destroying any special
|
||||
// context view that might have been used.
|
||||
this._utils.attachViewInContainer(parentView, boundElementIndex, null, null, atIndex, view);
|
||||
this._renderer.attachViewInContainer(viewContainerLocation, atIndex, view.render);
|
||||
this._attachRenderView(parentView, boundElementIndex, atIndex, view);
|
||||
return viewRef;
|
||||
}
|
||||
|
||||
@ -240,86 +257,65 @@ export class AppViewManager {
|
||||
var viewContainer = parentView.viewContainers[boundElementIndex];
|
||||
var view = viewContainer.views[atIndex];
|
||||
this._utils.detachViewInContainer(parentView, boundElementIndex, atIndex);
|
||||
this._renderer.detachViewInContainer(viewContainerLocation, atIndex, view.render);
|
||||
this._renderer.detachFragment(view.renderFragment);
|
||||
return view.ref;
|
||||
}
|
||||
|
||||
_createMainView(protoView: viewModule.AppProtoView,
|
||||
renderViewWithFragments: RenderViewWithFragments): viewModule.AppView {
|
||||
var mergedParentView =
|
||||
this._utils.createView(protoView, renderViewWithFragments, this, this._renderer);
|
||||
this._renderer.setEventDispatcher(mergedParentView.render, mergedParentView);
|
||||
this._viewListener.viewCreated(mergedParentView);
|
||||
return mergedParentView;
|
||||
}
|
||||
|
||||
_createPooledView(protoView: viewModule.AppProtoView): viewModule.AppView {
|
||||
var view = this._viewPool.getView(protoView);
|
||||
if (isBlank(view)) {
|
||||
view = this._utils.createView(protoView, this._renderer.createView(protoView.render), this,
|
||||
this._renderer);
|
||||
this._renderer.setEventDispatcher(view.render, view);
|
||||
this._createViewRecurse(view);
|
||||
this._viewListener.viewCreated(view);
|
||||
view = this._createMainView(
|
||||
protoView, this._renderer.createView(protoView.mergeMapping.renderProtoViewRef,
|
||||
protoView.mergeMapping.renderFragmentCount));
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
_createViewRecurse(view: viewModule.AppView) {
|
||||
var binders = view.proto.elementBinders;
|
||||
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
|
||||
var binder = binders[binderIdx];
|
||||
if (binder.hasStaticComponent()) {
|
||||
var childView = this._createPooledView(binder.nestedProtoView);
|
||||
this._renderer.attachComponentView(view.elementRefs[binderIdx], childView.render);
|
||||
this._utils.attachComponentView(view, binderIdx, childView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_destroyPooledView(view: viewModule.AppView) {
|
||||
var wasReturned = this._viewPool.returnView(view);
|
||||
if (!wasReturned) {
|
||||
this._renderer.destroyView(view.render);
|
||||
this._viewListener.viewDestroyed(view);
|
||||
this._renderer.destroyView(view.render);
|
||||
}
|
||||
}
|
||||
|
||||
_destroyViewInContainer(parentView, boundElementIndex, atIndex: number) {
|
||||
_destroyViewInContainer(parentView: viewModule.AppView, boundElementIndex: number,
|
||||
atIndex: number) {
|
||||
var viewContainer = parentView.viewContainers[boundElementIndex];
|
||||
var view = viewContainer.views[atIndex];
|
||||
this._viewDehydrateRecurse(view, false);
|
||||
|
||||
this._viewDehydrateRecurse(view);
|
||||
this._utils.detachViewInContainer(parentView, boundElementIndex, atIndex);
|
||||
this._renderer.detachViewInContainer(parentView.elementRefs[boundElementIndex], atIndex,
|
||||
view.render);
|
||||
this._destroyPooledView(view);
|
||||
}
|
||||
|
||||
_destroyComponentView(hostView, boundElementIndex, componentView) {
|
||||
this._viewDehydrateRecurse(componentView, false);
|
||||
this._renderer.detachComponentView(hostView.elementRefs[boundElementIndex],
|
||||
componentView.render);
|
||||
this._utils.detachComponentView(hostView, boundElementIndex);
|
||||
this._destroyPooledView(componentView);
|
||||
}
|
||||
|
||||
_viewHydrateRecurse(view: viewModule.AppView) {
|
||||
this._renderer.hydrateView(view.render);
|
||||
|
||||
var binders = view.proto.elementBinders;
|
||||
for (var i = 0; i < binders.length; ++i) {
|
||||
if (binders[i].hasStaticComponent()) {
|
||||
this._utils.hydrateComponentView(view, i);
|
||||
this._viewHydrateRecurse(view.componentChildViews[i]);
|
||||
}
|
||||
if (view.viewOffset > 0) {
|
||||
// Case 1: a view that is part of another view.
|
||||
// Just detach the fragment
|
||||
this._renderer.detachFragment(view.renderFragment);
|
||||
} else {
|
||||
// Case 2: a view that is not part of another view.
|
||||
// dehydrate and destroy it.
|
||||
this._renderer.dehydrateView(view.render);
|
||||
this._renderer.detachFragment(view.renderFragment);
|
||||
this._destroyPooledView(view);
|
||||
}
|
||||
}
|
||||
|
||||
_viewDehydrateRecurse(view: viewModule.AppView, forceDestroyComponents) {
|
||||
this._utils.dehydrateView(view);
|
||||
this._renderer.dehydrateView(view.render);
|
||||
var binders = view.proto.elementBinders;
|
||||
for (var i = 0; i < binders.length; i++) {
|
||||
var componentView = view.componentChildViews[i];
|
||||
if (isPresent(componentView)) {
|
||||
if (forceDestroyComponents) {
|
||||
this._destroyComponentView(view, i, componentView);
|
||||
} else {
|
||||
this._viewDehydrateRecurse(componentView, false);
|
||||
}
|
||||
}
|
||||
var vc = view.viewContainers[i];
|
||||
_viewDehydrateRecurse(view: viewModule.AppView) {
|
||||
if (view.hydrated()) {
|
||||
this._utils.dehydrateView(view);
|
||||
}
|
||||
var viewContainers = view.viewContainers;
|
||||
for (var i = view.elementOffset, ii = view.elementOffset + view.proto.mergeMapping.elementCount;
|
||||
i < ii; i++) {
|
||||
var vc = viewContainers[i];
|
||||
if (isPresent(vc)) {
|
||||
for (var j = vc.views.length - 1; j >= 0; j--) {
|
||||
this._destroyViewInContainer(view, i, j);
|
||||
|
@ -3,10 +3,12 @@ import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src
|
||||
import * as eli from './element_injector';
|
||||
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
|
||||
import * as viewModule from './view';
|
||||
import {internalView} from './view_ref';
|
||||
import * as avmModule from './view_manager';
|
||||
import {Renderer} from 'angular2/src/render/api';
|
||||
import {ElementRef} from './element_ref';
|
||||
import {Renderer, RenderViewWithFragments} from 'angular2/src/render/api';
|
||||
import {Locals} from 'angular2/change_detection';
|
||||
import {RenderViewRef} from 'angular2/src/render/api';
|
||||
import {RenderViewRef, RenderFragmentRef, ViewType} from 'angular2/src/render/api';
|
||||
|
||||
@Injectable()
|
||||
export class AppViewManagerUtils {
|
||||
@ -17,68 +19,84 @@ export class AppViewManagerUtils {
|
||||
return eli.getComponent();
|
||||
}
|
||||
|
||||
createView(protoView: viewModule.AppProtoView, renderView: RenderViewRef,
|
||||
createView(mergedParentViewProto: viewModule.AppProtoView,
|
||||
renderViewWithFragments: RenderViewWithFragments,
|
||||
viewManager: avmModule.AppViewManager, renderer: Renderer): viewModule.AppView {
|
||||
var view = new viewModule.AppView(renderer, protoView, protoView.protoLocals);
|
||||
// TODO(tbosch): pass RenderViewRef as argument to AppView!
|
||||
view.render = renderView;
|
||||
var renderFragments = renderViewWithFragments.fragmentRefs;
|
||||
var renderView = renderViewWithFragments.viewRef;
|
||||
|
||||
var changeDetector = protoView.protoChangeDetector.instantiate(view);
|
||||
var elementCount = mergedParentViewProto.mergeMapping.elementCount;
|
||||
var viewCount = mergedParentViewProto.mergeMapping.viewCount;
|
||||
var elementRefs: ElementRef[] = ListWrapper.createFixedSize(elementCount);
|
||||
var viewContainers = ListWrapper.createFixedSize(elementCount);
|
||||
var preBuiltObjects: eli.PreBuiltObjects[] = ListWrapper.createFixedSize(elementCount);
|
||||
var elementInjectors = ListWrapper.createFixedSize(elementCount);
|
||||
var views = ListWrapper.createFixedSize(viewCount);
|
||||
|
||||
var binders = protoView.elementBinders;
|
||||
var elementInjectors = ListWrapper.createFixedSize(binders.length);
|
||||
var rootElementInjectors = [];
|
||||
var preBuiltObjects = ListWrapper.createFixedSize(binders.length);
|
||||
var componentChildViews = ListWrapper.createFixedSize(binders.length);
|
||||
var elementOffset = 0;
|
||||
var textOffset = 0;
|
||||
var fragmentIdx = 0;
|
||||
for (var viewOffset = 0; viewOffset < viewCount; viewOffset++) {
|
||||
var hostElementIndex =
|
||||
mergedParentViewProto.mergeMapping.hostElementIndicesByViewIndex[viewOffset];
|
||||
var parentView = isPresent(hostElementIndex) ?
|
||||
internalView(elementRefs[hostElementIndex].parentView) :
|
||||
null;
|
||||
var protoView =
|
||||
isPresent(hostElementIndex) ?
|
||||
parentView.proto.elementBinders[hostElementIndex - parentView.elementOffset]
|
||||
.nestedProtoView :
|
||||
mergedParentViewProto;
|
||||
var renderFragment = null;
|
||||
if (viewOffset === 0 || protoView.type === ViewType.EMBEDDED) {
|
||||
renderFragment = renderFragments[fragmentIdx++];
|
||||
}
|
||||
var currentView = new viewModule.AppView(
|
||||
renderer, protoView, mergedParentViewProto.mergeMapping, viewOffset, elementOffset,
|
||||
textOffset, protoView.protoLocals, renderView, renderFragment);
|
||||
views[viewOffset] = currentView;
|
||||
var rootElementInjectors = [];
|
||||
for (var binderIdx = 0; binderIdx < protoView.elementBinders.length; binderIdx++) {
|
||||
var binder = protoView.elementBinders[binderIdx];
|
||||
var boundElementIndex = elementOffset + binderIdx;
|
||||
var elementInjector = null;
|
||||
|
||||
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
|
||||
var binder = binders[binderIdx];
|
||||
var elementInjector = null;
|
||||
// elementInjectors and rootElementInjectors
|
||||
var protoElementInjector = binder.protoElementInjector;
|
||||
if (isPresent(protoElementInjector)) {
|
||||
if (isPresent(protoElementInjector.parent)) {
|
||||
var parentElementInjector =
|
||||
elementInjectors[elementOffset + protoElementInjector.parent.index];
|
||||
elementInjector = protoElementInjector.instantiate(parentElementInjector);
|
||||
} else {
|
||||
elementInjector = protoElementInjector.instantiate(null);
|
||||
rootElementInjectors.push(elementInjector);
|
||||
}
|
||||
}
|
||||
elementInjectors[boundElementIndex] = elementInjector;
|
||||
|
||||
// elementInjectors and rootElementInjectors
|
||||
var protoElementInjector = binder.protoElementInjector;
|
||||
if (isPresent(protoElementInjector)) {
|
||||
if (isPresent(protoElementInjector.parent)) {
|
||||
var parentElementInjector = elementInjectors[protoElementInjector.parent.index];
|
||||
elementInjector = protoElementInjector.instantiate(parentElementInjector);
|
||||
} else {
|
||||
elementInjector = protoElementInjector.instantiate(null);
|
||||
rootElementInjectors.push(elementInjector);
|
||||
// elementRefs
|
||||
var el = new ElementRef(
|
||||
currentView.ref, boundElementIndex,
|
||||
mergedParentViewProto.mergeMapping.renderElementIndices[boundElementIndex], renderer);
|
||||
elementRefs[el.boundElementIndex] = el;
|
||||
|
||||
// preBuiltObjects
|
||||
if (isPresent(elementInjector)) {
|
||||
var embeddedProtoView = binder.hasEmbeddedProtoView() ? binder.nestedProtoView : null;
|
||||
preBuiltObjects[boundElementIndex] =
|
||||
new eli.PreBuiltObjects(viewManager, currentView, el, embeddedProtoView);
|
||||
}
|
||||
}
|
||||
elementInjectors[binderIdx] = elementInjector;
|
||||
|
||||
// preBuiltObjects
|
||||
if (isPresent(elementInjector)) {
|
||||
var embeddedProtoView = binder.hasEmbeddedProtoView() ? binder.nestedProtoView : null;
|
||||
preBuiltObjects[binderIdx] = new eli.PreBuiltObjects(viewManager, view, embeddedProtoView);
|
||||
currentView.init(protoView.protoChangeDetector.instantiate(currentView), elementInjectors,
|
||||
rootElementInjectors, preBuiltObjects, views, elementRefs, viewContainers);
|
||||
if (isPresent(parentView) && protoView.type === ViewType.COMPONENT) {
|
||||
parentView.changeDetector.addShadowDomChild(currentView.changeDetector);
|
||||
}
|
||||
elementOffset += protoView.elementBinders.length;
|
||||
textOffset += protoView.textBindingCount;
|
||||
}
|
||||
|
||||
view.init(changeDetector, elementInjectors, rootElementInjectors, preBuiltObjects,
|
||||
componentChildViews);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
attachComponentView(hostView: viewModule.AppView, boundElementIndex: number,
|
||||
componentView: viewModule.AppView) {
|
||||
var childChangeDetector = componentView.changeDetector;
|
||||
hostView.changeDetector.addShadowDomChild(childChangeDetector);
|
||||
hostView.componentChildViews[boundElementIndex] = componentView;
|
||||
}
|
||||
|
||||
detachComponentView(hostView: viewModule.AppView, boundElementIndex: number) {
|
||||
var componentView = hostView.componentChildViews[boundElementIndex];
|
||||
hostView.changeDetector.removeShadowDomChild(componentView.changeDetector);
|
||||
hostView.componentChildViews[boundElementIndex] = null;
|
||||
}
|
||||
|
||||
hydrateComponentView(hostView: viewModule.AppView, boundElementIndex: number) {
|
||||
var elementInjector = hostView.elementInjectors[boundElementIndex];
|
||||
var componentView = hostView.componentChildViews[boundElementIndex];
|
||||
var component = this.getComponentInstance(hostView, boundElementIndex);
|
||||
this._hydrateView(componentView, null, elementInjector, component, null);
|
||||
return views[0];
|
||||
}
|
||||
|
||||
hydrateRootHostView(hostView: viewModule.AppView, injector: Injector) {
|
||||
@ -94,7 +112,11 @@ export class AppViewManagerUtils {
|
||||
contextBoundElementIndex = boundElementIndex;
|
||||
}
|
||||
parentView.changeDetector.addChild(view.changeDetector);
|
||||
var viewContainer = this._getOrCreateViewContainer(parentView, boundElementIndex);
|
||||
var viewContainer = parentView.viewContainers[boundElementIndex];
|
||||
if (isBlank(viewContainer)) {
|
||||
viewContainer = new viewModule.AppViewContainer();
|
||||
parentView.viewContainers[boundElementIndex] = viewContainer;
|
||||
}
|
||||
ListWrapper.insert(viewContainer.views, atIndex, view);
|
||||
var sibling;
|
||||
if (atIndex == 0) {
|
||||
@ -124,7 +146,9 @@ export class AppViewManagerUtils {
|
||||
inj.unlink();
|
||||
} else {
|
||||
var removeIdx = ListWrapper.indexOf(parentView.rootElementInjectors, inj);
|
||||
ListWrapper.removeAt(parentView.rootElementInjectors, removeIdx);
|
||||
if (removeIdx >= 0) {
|
||||
ListWrapper.removeAt(parentView.rootElementInjectors, removeIdx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -145,25 +169,45 @@ export class AppViewManagerUtils {
|
||||
contextView.locals);
|
||||
}
|
||||
|
||||
_hydrateView(view: viewModule.AppView, imperativelyCreatedInjector: Injector,
|
||||
_hydrateView(initView: viewModule.AppView, imperativelyCreatedInjector: Injector,
|
||||
hostElementInjector: eli.ElementInjector, context: Object, parentLocals: Locals) {
|
||||
view.context = context;
|
||||
view.locals.parent = parentLocals;
|
||||
var viewIdx = initView.viewOffset;
|
||||
var endViewOffset = viewIdx + initView.proto.mergeMapping.viewCount;
|
||||
while (viewIdx < endViewOffset) {
|
||||
var currView = initView.views[viewIdx];
|
||||
var currProtoView = currView.proto;
|
||||
if (currView !== initView && currView.proto.type === ViewType.EMBEDDED) {
|
||||
// Don't hydrate components of embedded fragment views.
|
||||
viewIdx += currProtoView.mergeMapping.viewCount;
|
||||
} else {
|
||||
if (currView !== initView) {
|
||||
// hydrate a nested component view
|
||||
imperativelyCreatedInjector = null;
|
||||
parentLocals = null;
|
||||
var hostElementIndex = initView.mainMergeMapping.hostElementIndicesByViewIndex[viewIdx];
|
||||
hostElementInjector = initView.elementInjectors[hostElementIndex];
|
||||
context = hostElementInjector.getComponent();
|
||||
}
|
||||
currView.context = context;
|
||||
currView.locals.parent = parentLocals;
|
||||
var binders = currProtoView.elementBinders;
|
||||
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
|
||||
var boundElementIndex = binderIdx + currView.elementOffset;
|
||||
var elementInjector = initView.elementInjectors[boundElementIndex];
|
||||
|
||||
var binders = view.proto.elementBinders;
|
||||
for (var i = 0; i < binders.length; ++i) {
|
||||
var elementInjector = view.elementInjectors[i];
|
||||
|
||||
if (isPresent(elementInjector)) {
|
||||
elementInjector.hydrate(imperativelyCreatedInjector, hostElementInjector,
|
||||
view.preBuiltObjects[i]);
|
||||
this._populateViewLocals(view, elementInjector);
|
||||
this._setUpEventEmitters(view, elementInjector, i);
|
||||
this._setUpHostActions(view, elementInjector, i);
|
||||
if (isPresent(elementInjector)) {
|
||||
elementInjector.hydrate(imperativelyCreatedInjector, hostElementInjector,
|
||||
currView.preBuiltObjects[boundElementIndex]);
|
||||
this._populateViewLocals(currView, elementInjector, boundElementIndex);
|
||||
this._setUpEventEmitters(currView, elementInjector, boundElementIndex);
|
||||
this._setUpHostActions(currView, elementInjector, boundElementIndex);
|
||||
}
|
||||
}
|
||||
var pipes = this._getPipes(imperativelyCreatedInjector, hostElementInjector);
|
||||
currView.changeDetector.hydrate(currView.context, currView.locals, currView, pipes);
|
||||
viewIdx++;
|
||||
}
|
||||
}
|
||||
var pipes = this._getPipes(imperativelyCreatedInjector, hostElementInjector);
|
||||
view.changeDetector.hydrate(view.context, view.locals, view, pipes);
|
||||
}
|
||||
|
||||
_getPipes(imperativelyCreatedInjector: Injector, hostElementInjector: eli.ElementInjector) {
|
||||
@ -174,11 +218,12 @@ export class AppViewManagerUtils {
|
||||
return null;
|
||||
}
|
||||
|
||||
_populateViewLocals(view: viewModule.AppView, elementInjector: eli.ElementInjector): void {
|
||||
_populateViewLocals(view: viewModule.AppView, elementInjector: eli.ElementInjector,
|
||||
boundElementIdx: number): void {
|
||||
if (isPresent(elementInjector.getDirectiveVariableBindings())) {
|
||||
MapWrapper.forEach(elementInjector.getDirectiveVariableBindings(), (directiveIndex, name) => {
|
||||
if (isBlank(directiveIndex)) {
|
||||
view.locals.set(name, elementInjector.getElementRef().nativeElement);
|
||||
view.locals.set(name, view.elementRefs[boundElementIdx].nativeElement);
|
||||
} else {
|
||||
view.locals.set(name, elementInjector.getDirectiveAtIndex(directiveIndex));
|
||||
}
|
||||
@ -186,15 +231,6 @@ export class AppViewManagerUtils {
|
||||
}
|
||||
}
|
||||
|
||||
_getOrCreateViewContainer(parentView: viewModule.AppView, boundElementIndex: number) {
|
||||
var viewContainer = parentView.viewContainers[boundElementIndex];
|
||||
if (isBlank(viewContainer)) {
|
||||
viewContainer = new viewModule.AppViewContainer();
|
||||
parentView.viewContainers[boundElementIndex] = viewContainer;
|
||||
}
|
||||
return viewContainer;
|
||||
}
|
||||
|
||||
_setUpEventEmitters(view: viewModule.AppView, elementInjector: eli.ElementInjector,
|
||||
boundElementIndex: number) {
|
||||
var emitters = elementInjector.getEventEmitterAccessors();
|
||||
@ -223,18 +259,25 @@ export class AppViewManagerUtils {
|
||||
}
|
||||
}
|
||||
|
||||
dehydrateView(view: viewModule.AppView) {
|
||||
var binders = view.proto.elementBinders;
|
||||
for (var i = 0; i < binders.length; ++i) {
|
||||
var elementInjector = view.elementInjectors[i];
|
||||
if (isPresent(elementInjector)) {
|
||||
elementInjector.dehydrate();
|
||||
dehydrateView(initView: viewModule.AppView) {
|
||||
for (var viewIdx = initView.viewOffset,
|
||||
endViewOffset = viewIdx + initView.proto.mergeMapping.viewCount;
|
||||
viewIdx < endViewOffset; viewIdx++) {
|
||||
var currView = initView.views[viewIdx];
|
||||
if (currView.hydrated()) {
|
||||
if (isPresent(currView.locals)) {
|
||||
currView.locals.clearValues();
|
||||
}
|
||||
currView.context = null;
|
||||
currView.changeDetector.dehydrate();
|
||||
var binders = currView.proto.elementBinders;
|
||||
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
|
||||
var eli = initView.elementInjectors[currView.elementOffset + binderIdx];
|
||||
if (isPresent(eli)) {
|
||||
eli.dehydrate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isPresent(view.locals)) {
|
||||
view.locals.clearValues();
|
||||
}
|
||||
view.context = null;
|
||||
view.changeDetector.dehydrate();
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {isPresent} from 'angular2/src/facade/lang';
|
||||
import * as viewModule from './view';
|
||||
import {RenderViewRef} from 'angular2/src/render/api';
|
||||
import {RenderViewRef, RenderFragmentRef} from 'angular2/src/render/api';
|
||||
|
||||
// This is a workaround for privacy in Dart as we don't have library parts
|
||||
export function internalView(viewRef: ViewRef): viewModule.AppView {
|
||||
@ -71,6 +71,11 @@ export class ViewRef {
|
||||
*/
|
||||
get render(): RenderViewRef { return this._view.render; }
|
||||
|
||||
/**
|
||||
* Return {@link RenderFragmentRef}
|
||||
*/
|
||||
get renderFragment(): RenderFragmentRef { return this._view.renderFragment; }
|
||||
|
||||
/**
|
||||
* Set local variable for a view.
|
||||
*
|
||||
|
Reference in New Issue
Block a user