459 lines
14 KiB
TypeScript
459 lines
14 KiB
TypeScript
import {
|
|
ListWrapper,
|
|
StringMapWrapper,
|
|
Map,
|
|
MapWrapper
|
|
} from '../facade/collection';
|
|
|
|
import {AppElement} from './element';
|
|
import {
|
|
assertionsEnabled,
|
|
isPresent,
|
|
isBlank,
|
|
Type,
|
|
isArray,
|
|
isNumber,
|
|
stringify,
|
|
isPrimitive,
|
|
isString
|
|
} from '../facade/lang';
|
|
|
|
import {ObservableWrapper} from '../facade/async';
|
|
import {Renderer, RootRenderer, RenderComponentType, RenderDebugInfo} from '../render/api';
|
|
import {ViewRef_} from './view_ref';
|
|
|
|
import {ViewType} from './view_type';
|
|
import {
|
|
ViewUtils,
|
|
flattenNestedViewRenderNodes,
|
|
ensureSlotCount,
|
|
arrayLooseIdentical,
|
|
mapLooseIdentical
|
|
} from './view_utils';
|
|
import {
|
|
ChangeDetectorRef,
|
|
ChangeDetectionStrategy,
|
|
ChangeDetectorState,
|
|
} from '../change_detection/change_detection';
|
|
import {wtfCreateScope, wtfLeave, WtfScopeFn} from '../profile/profile';
|
|
import {
|
|
ExpressionChangedAfterItHasBeenCheckedException,
|
|
ViewDestroyedException,
|
|
ViewWrappedException
|
|
} from './exceptions';
|
|
import {StaticNodeDebugInfo, DebugContext} from './debug_context';
|
|
import {ElementInjector} from './element_injector';
|
|
import {Injector} from '../di/injector';
|
|
|
|
import {AUTO_STYLE} from '../animation/metadata';
|
|
import {AnimationPlayer} from '../animation/animation_player';
|
|
import {AnimationGroupPlayer} from '../animation/animation_group_player';
|
|
import {AnimationKeyframe} from '../animation/animation_keyframe';
|
|
import {AnimationStyles} from '../animation/animation_styles';
|
|
import {AnimationDriver} from '../animation/animation_driver';
|
|
import {ActiveAnimationPlayersMap} from '../animation/active_animation_players_map';
|
|
|
|
var _scope_check: WtfScopeFn = wtfCreateScope(`AppView#check(ascii id)`);
|
|
|
|
/**
|
|
* Cost of making objects: http://jsperf.com/instantiate-size-of-object
|
|
*
|
|
*/
|
|
export abstract class AppView<T> {
|
|
ref: ViewRef_<T>;
|
|
rootNodesOrAppElements: any[];
|
|
allNodes: any[];
|
|
disposables: Function[];
|
|
subscriptions: any[];
|
|
contentChildren: AppView<any>[] = [];
|
|
viewChildren: AppView<any>[] = [];
|
|
viewContainerElement: AppElement = null;
|
|
|
|
// The names of the below fields must be kept in sync with codegen_name_util.ts or
|
|
// change detection will fail.
|
|
cdState: ChangeDetectorState = ChangeDetectorState.NeverChecked;
|
|
|
|
projectableNodes: Array<any | any[]>;
|
|
|
|
destroyed: boolean = false;
|
|
|
|
renderer: Renderer;
|
|
|
|
private _hasExternalHostElement: boolean;
|
|
|
|
public activeAnimationPlayers = new ActiveAnimationPlayersMap();
|
|
|
|
public context: T;
|
|
|
|
constructor(public clazz: any, public componentType: RenderComponentType, public type: ViewType,
|
|
public viewUtils: ViewUtils, public parentInjector: Injector,
|
|
public declarationAppElement: AppElement, public cdMode: ChangeDetectionStrategy) {
|
|
this.ref = new ViewRef_(this);
|
|
if (type === ViewType.COMPONENT || type === ViewType.HOST) {
|
|
this.renderer = viewUtils.renderComponent(componentType);
|
|
} else {
|
|
this.renderer = declarationAppElement.parentView.renderer;
|
|
}
|
|
}
|
|
|
|
cancelActiveAnimation(element: any, animationName: string, removeAllAnimations: boolean = false) {
|
|
if (removeAllAnimations) {
|
|
this.activeAnimationPlayers.findAllPlayersByElement(element).forEach(player => player.destroy());
|
|
} else {
|
|
var player = this.activeAnimationPlayers.find(element, animationName);
|
|
if (isPresent(player)) {
|
|
player.destroy();
|
|
}
|
|
}
|
|
}
|
|
|
|
registerAndStartAnimation(element: any, animationName: string, player: AnimationPlayer): void {
|
|
this.activeAnimationPlayers.set(element, animationName, player);
|
|
player.onDone(() => {
|
|
this.activeAnimationPlayers.remove(element, animationName);
|
|
});
|
|
player.play();
|
|
}
|
|
|
|
create(context: T, givenProjectableNodes: Array<any | any[]>,
|
|
rootSelectorOrNode: string | any): AppElement {
|
|
this.context = context;
|
|
var projectableNodes;
|
|
switch (this.type) {
|
|
case ViewType.COMPONENT:
|
|
projectableNodes = ensureSlotCount(givenProjectableNodes, this.componentType.slotCount);
|
|
break;
|
|
case ViewType.EMBEDDED:
|
|
projectableNodes = this.declarationAppElement.parentView.projectableNodes;
|
|
break;
|
|
case ViewType.HOST:
|
|
// Note: Don't ensure the slot count for the projectableNodes as we store
|
|
// them only for the contained component view (which will later check the slot count...)
|
|
projectableNodes = givenProjectableNodes;
|
|
break;
|
|
}
|
|
this._hasExternalHostElement = isPresent(rootSelectorOrNode);
|
|
this.projectableNodes = projectableNodes;
|
|
return this.createInternal(rootSelectorOrNode);
|
|
}
|
|
|
|
/**
|
|
* Overwritten by implementations.
|
|
* Returns the AppElement for the host element for ViewType.HOST.
|
|
*/
|
|
createInternal(rootSelectorOrNode: string | any): AppElement { return null; }
|
|
|
|
init(rootNodesOrAppElements: any[], allNodes: any[], disposables: Function[],
|
|
subscriptions: any[]) {
|
|
this.rootNodesOrAppElements = rootNodesOrAppElements;
|
|
this.allNodes = allNodes;
|
|
this.disposables = disposables;
|
|
this.subscriptions = subscriptions;
|
|
if (this.type === ViewType.COMPONENT) {
|
|
// Note: the render nodes have been attached to their host element
|
|
// in the ViewFactory already.
|
|
this.declarationAppElement.parentView.viewChildren.push(this);
|
|
this.dirtyParentQueriesInternal();
|
|
}
|
|
}
|
|
|
|
selectOrCreateHostElement(elementName: string, rootSelectorOrNode: string | any,
|
|
debugInfo: RenderDebugInfo): any {
|
|
var hostElement;
|
|
if (isPresent(rootSelectorOrNode)) {
|
|
hostElement = this.renderer.selectRootElement(rootSelectorOrNode, debugInfo);
|
|
} else {
|
|
hostElement = this.renderer.createElement(null, elementName, debugInfo);
|
|
}
|
|
return hostElement;
|
|
}
|
|
|
|
injectorGet(token: any, nodeIndex: number, notFoundResult: any): any {
|
|
return this.injectorGetInternal(token, nodeIndex, notFoundResult);
|
|
}
|
|
|
|
/**
|
|
* Overwritten by implementations
|
|
*/
|
|
injectorGetInternal(token: any, nodeIndex: number, notFoundResult: any): any {
|
|
return notFoundResult;
|
|
}
|
|
|
|
injector(nodeIndex: number): Injector {
|
|
if (isPresent(nodeIndex)) {
|
|
return new ElementInjector(this, nodeIndex);
|
|
} else {
|
|
return this.parentInjector;
|
|
}
|
|
}
|
|
|
|
destroy() {
|
|
if (this._hasExternalHostElement) {
|
|
this.renderer.detachView(this.flatRootNodes);
|
|
} else if (isPresent(this.viewContainerElement)) {
|
|
this.viewContainerElement.detachView(this.viewContainerElement.nestedViews.indexOf(this));
|
|
}
|
|
this._destroyRecurse();
|
|
}
|
|
|
|
private _destroyRecurse() {
|
|
if (this.destroyed) {
|
|
return;
|
|
}
|
|
var children = this.contentChildren;
|
|
for (var i = 0; i < children.length; i++) {
|
|
children[i]._destroyRecurse();
|
|
}
|
|
children = this.viewChildren;
|
|
for (var i = 0; i < children.length; i++) {
|
|
children[i]._destroyRecurse();
|
|
}
|
|
this.destroyLocal();
|
|
|
|
this.destroyed = true;
|
|
}
|
|
|
|
destroyLocal() {
|
|
var hostElement =
|
|
this.type === ViewType.COMPONENT ? this.declarationAppElement.nativeElement : null;
|
|
for (var i = 0; i < this.disposables.length; i++) {
|
|
this.disposables[i]();
|
|
}
|
|
for (var i = 0; i < this.subscriptions.length; i++) {
|
|
ObservableWrapper.dispose(this.subscriptions[i]);
|
|
}
|
|
this.destroyInternal();
|
|
this.dirtyParentQueriesInternal();
|
|
|
|
if (this.activeAnimationPlayers.length == 0) {
|
|
this.renderer.destroyView(hostElement, this.allNodes);
|
|
} else {
|
|
var player = new AnimationGroupPlayer(this.activeAnimationPlayers.getAllPlayers());
|
|
player.onDone(() => {
|
|
this.renderer.destroyView(hostElement, this.allNodes);
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Overwritten by implementations
|
|
*/
|
|
destroyInternal(): void {}
|
|
|
|
/**
|
|
* Overwritten by implementations
|
|
*/
|
|
detachInternal(): void {}
|
|
|
|
detach(): void {
|
|
this.detachInternal();
|
|
if (this.activeAnimationPlayers.length == 0) {
|
|
this.renderer.detachView(this.flatRootNodes);
|
|
} else {
|
|
var player = new AnimationGroupPlayer(this.activeAnimationPlayers.getAllPlayers());
|
|
player.onDone(() => {
|
|
this.renderer.detachView(this.flatRootNodes);
|
|
});
|
|
}
|
|
}
|
|
|
|
get changeDetectorRef(): ChangeDetectorRef { return this.ref; }
|
|
|
|
get parent(): AppView<any> {
|
|
return isPresent(this.declarationAppElement) ? this.declarationAppElement.parentView : null;
|
|
}
|
|
|
|
get flatRootNodes(): any[] { return flattenNestedViewRenderNodes(this.rootNodesOrAppElements); }
|
|
|
|
get lastRootNode(): any {
|
|
var lastNode = this.rootNodesOrAppElements.length > 0 ?
|
|
this.rootNodesOrAppElements[this.rootNodesOrAppElements.length - 1] :
|
|
null;
|
|
return _findLastRenderNode(lastNode);
|
|
}
|
|
|
|
/**
|
|
* Overwritten by implementations
|
|
*/
|
|
dirtyParentQueriesInternal(): void {}
|
|
|
|
detectChanges(throwOnChange: boolean): void {
|
|
var s = _scope_check(this.clazz);
|
|
if (this.cdMode === ChangeDetectionStrategy.Detached ||
|
|
this.cdMode === ChangeDetectionStrategy.Checked ||
|
|
this.cdState === ChangeDetectorState.Errored)
|
|
return;
|
|
if (this.destroyed) {
|
|
this.throwDestroyedError('detectChanges');
|
|
}
|
|
this.detectChangesInternal(throwOnChange);
|
|
if (this.cdMode === ChangeDetectionStrategy.CheckOnce)
|
|
this.cdMode = ChangeDetectionStrategy.Checked;
|
|
|
|
this.cdState = ChangeDetectorState.CheckedBefore;
|
|
wtfLeave(s);
|
|
}
|
|
|
|
/**
|
|
* Overwritten by implementations
|
|
*/
|
|
detectChangesInternal(throwOnChange: boolean): void {
|
|
this.detectContentChildrenChanges(throwOnChange);
|
|
this.detectViewChildrenChanges(throwOnChange);
|
|
}
|
|
|
|
detectContentChildrenChanges(throwOnChange: boolean) {
|
|
for (var i = 0; i < this.contentChildren.length; ++i) {
|
|
this.contentChildren[i].detectChanges(throwOnChange);
|
|
}
|
|
}
|
|
|
|
detectViewChildrenChanges(throwOnChange: boolean) {
|
|
for (var i = 0; i < this.viewChildren.length; ++i) {
|
|
this.viewChildren[i].detectChanges(throwOnChange);
|
|
}
|
|
}
|
|
|
|
addToContentChildren(renderAppElement: AppElement): void {
|
|
renderAppElement.parentView.contentChildren.push(this);
|
|
this.viewContainerElement = renderAppElement;
|
|
this.dirtyParentQueriesInternal();
|
|
}
|
|
|
|
removeFromContentChildren(renderAppElement: AppElement): void {
|
|
ListWrapper.remove(renderAppElement.parentView.contentChildren, this);
|
|
this.dirtyParentQueriesInternal();
|
|
this.viewContainerElement = null;
|
|
}
|
|
|
|
markAsCheckOnce(): void { this.cdMode = ChangeDetectionStrategy.CheckOnce; }
|
|
|
|
markPathToRootAsCheckOnce(): void {
|
|
let c: AppView<any> = this;
|
|
while (isPresent(c) && c.cdMode !== ChangeDetectionStrategy.Detached) {
|
|
if (c.cdMode === ChangeDetectionStrategy.Checked) {
|
|
c.cdMode = ChangeDetectionStrategy.CheckOnce;
|
|
}
|
|
let parentEl =
|
|
c.type === ViewType.COMPONENT ? c.declarationAppElement : c.viewContainerElement;
|
|
c = isPresent(parentEl) ? parentEl.parentView : null;
|
|
}
|
|
}
|
|
|
|
eventHandler(cb: Function): Function { return cb; }
|
|
|
|
throwDestroyedError(details: string): void { throw new ViewDestroyedException(details); }
|
|
}
|
|
|
|
export class DebugAppView<T> extends AppView<T> {
|
|
private _currentDebugContext: DebugContext = null;
|
|
|
|
constructor(clazz: any, componentType: RenderComponentType, type: ViewType, viewUtils: ViewUtils,
|
|
parentInjector: Injector, declarationAppElement: AppElement,
|
|
cdMode: ChangeDetectionStrategy, public staticNodeDebugInfos: StaticNodeDebugInfo[]) {
|
|
super(clazz, componentType, type, viewUtils, parentInjector, declarationAppElement, cdMode);
|
|
}
|
|
|
|
create(context: T, givenProjectableNodes: Array<any | any[]>,
|
|
rootSelectorOrNode: string | any): AppElement {
|
|
this._resetDebug();
|
|
try {
|
|
return super.create(context, givenProjectableNodes, rootSelectorOrNode);
|
|
} catch (e) {
|
|
this._rethrowWithContext(e, e.stack);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
injectorGet(token: any, nodeIndex: number, notFoundResult: any): any {
|
|
this._resetDebug();
|
|
try {
|
|
return super.injectorGet(token, nodeIndex, notFoundResult);
|
|
} catch (e) {
|
|
this._rethrowWithContext(e, e.stack);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
detach(): void {
|
|
this._resetDebug();
|
|
try {
|
|
super.detach();
|
|
} catch (e) {
|
|
this._rethrowWithContext(e, e.stack);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
destroyLocal() {
|
|
this._resetDebug();
|
|
try {
|
|
super.destroyLocal();
|
|
} catch (e) {
|
|
this._rethrowWithContext(e, e.stack);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
detectChanges(throwOnChange: boolean): void {
|
|
this._resetDebug();
|
|
try {
|
|
super.detectChanges(throwOnChange);
|
|
} catch (e) {
|
|
this._rethrowWithContext(e, e.stack);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
private _resetDebug() { this._currentDebugContext = null; }
|
|
|
|
debug(nodeIndex: number, rowNum: number, colNum: number): DebugContext {
|
|
return this._currentDebugContext = new DebugContext(this, nodeIndex, rowNum, colNum);
|
|
}
|
|
|
|
private _rethrowWithContext(e: any, stack: any) {
|
|
if (!(e instanceof ViewWrappedException)) {
|
|
if (!(e instanceof ExpressionChangedAfterItHasBeenCheckedException)) {
|
|
this.cdState = ChangeDetectorState.Errored;
|
|
}
|
|
if (isPresent(this._currentDebugContext)) {
|
|
throw new ViewWrappedException(e, stack, this._currentDebugContext);
|
|
}
|
|
}
|
|
}
|
|
|
|
eventHandler(cb: Function): Function {
|
|
var superHandler = super.eventHandler(cb);
|
|
return (event) => {
|
|
this._resetDebug();
|
|
try {
|
|
return superHandler(event);
|
|
} catch (e) {
|
|
this._rethrowWithContext(e, e.stack);
|
|
throw e;
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
function _findLastRenderNode(node: any): any {
|
|
var lastNode;
|
|
if (node instanceof AppElement) {
|
|
var appEl = <AppElement>node;
|
|
lastNode = appEl.nativeElement;
|
|
if (isPresent(appEl.nestedViews)) {
|
|
// Note: Views might have no root nodes at all!
|
|
for (var i = appEl.nestedViews.length - 1; i >= 0; i--) {
|
|
var nestedView = appEl.nestedViews[i];
|
|
if (nestedView.rootNodesOrAppElements.length > 0) {
|
|
lastNode = _findLastRenderNode(
|
|
nestedView.rootNodesOrAppElements[nestedView.rootNodesOrAppElements.length - 1]);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
lastNode = node;
|
|
}
|
|
return lastNode;
|
|
}
|