Alex Rickabaugh 60727c4d2b revert(format): Revert "chore(format): update to latest formatter"
This reverts commit 03627aa84d90f7f1d8d62f160997b783fdf9eaa4.
2016-04-12 09:41:01 -07:00

373 lines
13 KiB
TypeScript

import {
ListWrapper,
MapWrapper,
Map,
StringMapWrapper,
} from 'angular2/src/facade/collection';
import {
ChangeDetector,
ChangeDispatcher,
DirectiveIndex,
BindingTarget,
Locals,
ProtoChangeDetector,
ChangeDetectorRef
} from 'angular2/src/core/change_detection/change_detection';
import {ResolvedProvider, Injectable, Injector} from 'angular2/src/core/di';
import {DebugContext} from 'angular2/src/core/change_detection/interfaces';
import {AppProtoElement, AppElement, DirectiveProvider} from './element';
import {
isPresent,
isBlank,
Type,
isArray,
isNumber,
CONST,
CONST_EXPR
} from 'angular2/src/facade/lang';
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
import {Renderer, RootRenderer, RenderDebugInfo} from 'angular2/src/core/render/api';
import {ViewRef_, HostViewFactoryRef} from './view_ref';
import {ProtoPipes} from 'angular2/src/core/pipes/pipes';
import {camelCaseToDashCase} from 'angular2/src/core/render/util';
export {DebugContext} from 'angular2/src/core/change_detection/interfaces';
import {Pipes} from 'angular2/src/core/pipes/pipes';
import {AppViewManager_, AppViewManager} from './view_manager';
import {ResolvedMetadataCache} from './resolved_metadata_cache';
import {ViewType} from './view_type';
const REFLECT_PREFIX: string = 'ng-reflect-';
const EMPTY_CONTEXT = CONST_EXPR(new Object());
/**
* Cost of making objects: http://jsperf.com/instantiate-size-of-object
*
*/
export class AppView implements ChangeDispatcher {
ref: ViewRef_;
rootNodesOrAppElements: any[];
allNodes: any[];
disposables: Function[];
appElements: AppElement[];
/**
* The context against which data-binding expressions in this view are evaluated against.
* This is always a component instance.
*/
context: any = null;
/**
* Variables, local to this view, that can be used in binding expressions (in addition to the
* context). This is used for thing like `<video #player>` or
* `<li template="for #item of items">`, where "player" and "item" are locals, respectively.
*/
locals: Locals;
pipes: Pipes;
parentInjector: Injector;
/**
* Whether root injectors of this view
* have a hostBoundary.
*/
hostInjectorBoundary: boolean;
destroyed: boolean = false;
constructor(public proto: AppProtoView, public renderer: Renderer,
public viewManager: AppViewManager_, public projectableNodes: Array<any | any[]>,
public containerAppElement: AppElement,
imperativelyCreatedProviders: ResolvedProvider[], rootInjector: Injector,
public changeDetector: ChangeDetector) {
this.ref = new ViewRef_(this);
var injectorWithHostBoundary = AppElement.getViewParentInjector(
this.proto.type, containerAppElement, imperativelyCreatedProviders, rootInjector);
this.parentInjector = injectorWithHostBoundary.injector;
this.hostInjectorBoundary = injectorWithHostBoundary.hostInjectorBoundary;
var pipes;
var context;
switch (proto.type) {
case ViewType.COMPONENT:
pipes = new Pipes(proto.protoPipes, containerAppElement.getInjector());
context = containerAppElement.getComponent();
break;
case ViewType.EMBEDDED:
pipes = containerAppElement.parentView.pipes;
context = containerAppElement.parentView.context;
break;
case ViewType.HOST:
pipes = null;
context = EMPTY_CONTEXT;
break;
}
this.pipes = pipes;
this.context = context;
}
init(rootNodesOrAppElements: any[], allNodes: any[], disposables: Function[],
appElements: AppElement[]) {
this.rootNodesOrAppElements = rootNodesOrAppElements;
this.allNodes = allNodes;
this.disposables = disposables;
this.appElements = appElements;
var localsMap = new Map<string, any>();
StringMapWrapper.forEach(
this.proto.templateVariableBindings,
(templateName: string, _: string) => { localsMap.set(templateName, null); });
for (var i = 0; i < appElements.length; i++) {
var appEl = appElements[i];
var providerTokens = [];
if (isPresent(appEl.proto.protoInjector)) {
for (var j = 0; j < appEl.proto.protoInjector.numberOfProviders; j++) {
providerTokens.push(appEl.proto.protoInjector.getProviderAtIndex(j).key.token);
}
}
StringMapWrapper.forEach(appEl.proto.directiveVariableBindings,
(directiveIndex: number, name: string) => {
if (isBlank(directiveIndex)) {
localsMap.set(name, appEl.nativeElement);
} else {
localsMap.set(name, appEl.getDirectiveAtIndex(directiveIndex));
}
});
this.renderer.setElementDebugInfo(
appEl.nativeElement, new RenderDebugInfo(appEl.getInjector(), appEl.getComponent(),
providerTokens, localsMap));
}
var parentLocals = null;
if (this.proto.type !== ViewType.COMPONENT) {
parentLocals =
isPresent(this.containerAppElement) ? this.containerAppElement.parentView.locals : null;
}
if (this.proto.type === ViewType.COMPONENT) {
// Note: the render nodes have been attached to their host element
// in the ViewFactory already.
this.containerAppElement.attachComponentView(this);
this.containerAppElement.parentView.changeDetector.addViewChild(this.changeDetector);
}
this.locals = new Locals(parentLocals, localsMap);
this.changeDetector.hydrate(this.context, this.locals, this, this.pipes);
this.viewManager.onViewCreated(this);
}
destroy() {
if (this.destroyed) {
throw new BaseException('This view has already been destroyed!');
}
this.changeDetector.destroyRecursive();
}
notifyOnDestroy() {
this.destroyed = true;
var hostElement =
this.proto.type === ViewType.COMPONENT ? this.containerAppElement.nativeElement : null;
this.renderer.destroyView(hostElement, this.allNodes);
for (var i = 0; i < this.disposables.length; i++) {
this.disposables[i]();
}
this.viewManager.onViewDestroyed(this);
}
get changeDetectorRef(): ChangeDetectorRef { return this.changeDetector.ref; }
get flatRootNodes(): any[] { return flattenNestedViewRenderNodes(this.rootNodesOrAppElements); }
hasLocal(contextName: string): boolean {
return StringMapWrapper.contains(this.proto.templateVariableBindings, contextName);
}
setLocal(contextName: string, value: any): void {
if (!this.hasLocal(contextName)) {
return;
}
var templateName = this.proto.templateVariableBindings[contextName];
this.locals.set(templateName, value);
}
// dispatch to element injector or text nodes based on context
notifyOnBinding(b: BindingTarget, currentValue: any): void {
if (b.isTextNode()) {
this.renderer.setText(this.allNodes[b.elementIndex], currentValue);
} else {
var nativeElement = this.appElements[b.elementIndex].nativeElement;
if (b.isElementProperty()) {
this.renderer.setElementProperty(nativeElement, b.name, currentValue);
} else if (b.isElementAttribute()) {
this.renderer.setElementAttribute(nativeElement, b.name,
isPresent(currentValue) ? `${currentValue}` : null);
} else if (b.isElementClass()) {
this.renderer.setElementClass(nativeElement, b.name, currentValue);
} else if (b.isElementStyle()) {
var unit = isPresent(b.unit) ? b.unit : '';
this.renderer.setElementStyle(nativeElement, b.name,
isPresent(currentValue) ? `${currentValue}${unit}` : null);
} else {
throw new BaseException('Unsupported directive record');
}
}
}
logBindingUpdate(b: BindingTarget, value: any): void {
if (b.isDirective() || b.isElementProperty()) {
var nativeElement = this.appElements[b.elementIndex].nativeElement;
this.renderer.setBindingDebugInfo(
nativeElement, `${REFLECT_PREFIX}${camelCaseToDashCase(b.name)}`, `${value}`);
}
}
notifyAfterContentChecked(): void {
var count = this.appElements.length;
for (var i = count - 1; i >= 0; i--) {
this.appElements[i].ngAfterContentChecked();
}
}
notifyAfterViewChecked(): void {
var count = this.appElements.length;
for (var i = count - 1; i >= 0; i--) {
this.appElements[i].ngAfterViewChecked();
}
}
getDebugContext(appElement: AppElement, elementIndex: number,
directiveIndex: number): DebugContext {
try {
if (isBlank(appElement) && elementIndex < this.appElements.length) {
appElement = this.appElements[elementIndex];
}
var container = this.containerAppElement;
var element = isPresent(appElement) ? appElement.nativeElement : null;
var componentElement = isPresent(container) ? container.nativeElement : null;
var directive =
isPresent(directiveIndex) ? appElement.getDirectiveAtIndex(directiveIndex) : null;
var injector = isPresent(appElement) ? appElement.getInjector() : null;
return new DebugContext(element, componentElement, directive, this.context,
_localsToStringMap(this.locals), injector);
} catch (e) {
// TODO: vsavkin log the exception once we have a good way to log errors and warnings
// if an error happens during getting the debug context, we return null.
return null;
}
}
getDirectiveFor(directive: DirectiveIndex): any {
return this.appElements[directive.elementIndex].getDirectiveAtIndex(directive.directiveIndex);
}
getDetectorFor(directive: DirectiveIndex): ChangeDetector {
var componentView = this.appElements[directive.elementIndex].componentView;
return isPresent(componentView) ? componentView.changeDetector : null;
}
/**
* Triggers the event handlers for the element and the directives.
*
* This method is intended to be called from directive EventEmitters.
*
* @param {string} eventName
* @param {*} eventObj
* @param {number} boundElementIndex
* @return false if preventDefault must be applied to the DOM event
*/
triggerEventHandlers(eventName: string, eventObj: Event, boundElementIndex: number): boolean {
return this.changeDetector.handleEvent(eventName, boundElementIndex, eventObj);
}
}
function _localsToStringMap(locals: Locals): {[key: string]: any} {
var res = {};
var c = locals;
while (isPresent(c)) {
res = StringMapWrapper.merge(res, MapWrapper.toStringMap(c.current));
c = c.parent;
}
return res;
}
/**
*
*/
export class AppProtoView {
static create(metadataCache: ResolvedMetadataCache, type: ViewType, pipes: Type[],
templateVariableBindings: {[key: string]: string}): AppProtoView {
var protoPipes = null;
if (isPresent(pipes) && pipes.length > 0) {
var boundPipes = ListWrapper.createFixedSize(pipes.length);
for (var i = 0; i < pipes.length; i++) {
boundPipes[i] = metadataCache.getResolvedPipeMetadata(pipes[i]);
}
protoPipes = ProtoPipes.fromProviders(boundPipes);
}
return new AppProtoView(type, protoPipes, templateVariableBindings);
}
constructor(public type: ViewType, public protoPipes: ProtoPipes,
public templateVariableBindings: {[key: string]: string}) {}
}
@CONST()
export class HostViewFactory {
constructor(public selector: string, public viewFactory: Function) {}
}
export function flattenNestedViewRenderNodes(nodes: any[]): any[] {
return _flattenNestedViewRenderNodes(nodes, []);
}
function _flattenNestedViewRenderNodes(nodes: any[], renderNodes: any[]): any[] {
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
if (node instanceof AppElement) {
var appEl = <AppElement>node;
renderNodes.push(appEl.nativeElement);
if (isPresent(appEl.nestedViews)) {
for (var k = 0; k < appEl.nestedViews.length; k++) {
_flattenNestedViewRenderNodes(appEl.nestedViews[k].rootNodesOrAppElements, renderNodes);
}
}
} else {
renderNodes.push(node);
}
}
return renderNodes;
}
export 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;
}
export function checkSlotCount(componentName: string, expectedSlotCount: number,
projectableNodes: any[][]): void {
var givenSlotCount = isPresent(projectableNodes) ? projectableNodes.length : 0;
if (givenSlotCount < expectedSlotCount) {
throw new BaseException(
`The component ${componentName} has ${expectedSlotCount} <ng-content> elements,` +
` but only ${givenSlotCount} slots were provided.`);
}
}