Files
angular-docs-cn/modules/angular2/src/core/linker/view.ts
T

352 lines
13 KiB
TypeScript
Raw Normal View History

import {
ListWrapper,
MapWrapper,
Map,
StringMapWrapper,
2015-11-06 17:34:07 -08:00
} from 'angular2/src/facade/collection';
2015-05-20 09:48:15 -07:00
import {
ChangeDetector,
ChangeDispatcher,
2015-05-20 09:48:15 -07:00
DirectiveIndex,
BindingTarget,
Locals,
ProtoChangeDetector
2015-08-20 14:28:25 -07:00
} from 'angular2/src/core/change_detection/change_detection';
import {DebugContext} from 'angular2/src/core/change_detection/interfaces';
2015-05-20 09:48:15 -07:00
import {
ProtoElementInjector,
ElementInjector,
PreBuiltObjects,
2015-10-10 22:11:13 -07:00
DirectiveProvider
2015-05-20 09:48:15 -07:00
} from './element_injector';
import {ElementBinder} from './element_binder';
2015-11-06 17:34:07 -08:00
import {isPresent} from 'angular2/src/facade/lang';
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
2015-08-20 14:28:25 -07:00
import * as renderApi from 'angular2/src/core/render/api';
import {RenderEventDispatcher} from 'angular2/src/core/render/api';
import {ViewRef, ProtoViewRef, internalView} from './view_ref';
import {ElementRef} from './element_ref';
import {ProtoPipes} from 'angular2/src/core/pipes/pipes';
2015-11-17 15:24:36 -08:00
import {camelCaseToDashCase} from 'angular2/src/core/render/util';
import {TemplateCmd} from './template_commands';
import {ViewRef_, ProtoViewRef_} from "./view_ref";
2015-08-20 14:28:25 -07:00
export {DebugContext} from 'angular2/src/core/change_detection/interfaces';
const REFLECT_PREFIX: string = 'ng-reflect-';
export enum ViewType {
// A view that contains the host element with bound component directive.
// Contains a COMPONENT view
HOST,
// The view of the component
// Can contain 0 to n EMBEDDED views
COMPONENT,
// A view that is embedded into another View via a <template> element
// inside of a COMPONENT view
EMBEDDED
}
export class AppViewContainer {
2015-06-03 11:02:51 -07:00
// The order in this list matches the DOM order.
2015-08-28 11:29:19 -07:00
views: AppView[] = [];
}
2014-09-28 16:29:11 -07:00
/**
* Cost of making objects: http://jsperf.com/instantiate-size-of-object
2015-03-31 22:47:11 +00:00
*
2014-10-10 20:44:55 -07:00
*/
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.
2015-08-28 11:29:19 -07:00
views: AppView[] = null;
// root elementInjectors of this AppView
// This list is local to this AppView and not shared with other Views.
2015-08-28 11:29:19 -07:00
rootElementInjectors: 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.
2015-08-28 11:29:19 -07:00
elementInjectors: ElementInjector[] = null;
// 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.
2015-08-28 11:29:19 -07:00
viewContainers: 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.
2015-08-28 11:29:19 -07:00
preBuiltObjects: 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.
2015-08-28 11:29:19 -07:00
elementRefs: ElementRef[];
ref: ViewRef;
changeDetector: ChangeDetector = null;
/**
* The context against which data-binding expressions in this view are evaluated against.
* This is always a component instance.
*/
2015-06-12 22:38:44 +02:00
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.
*/
2015-05-20 09:48:15 -07:00
locals: Locals;
2015-05-20 09:48:15 -07:00
constructor(public renderer: renderApi.Renderer, public proto: AppProtoView,
public viewOffset: number, public elementOffset: number, public textOffset: number,
protoLocals: Map<string, any>, public render: renderApi.RenderViewRef,
public renderFragment: renderApi.RenderFragmentRef,
public containerElementInjector: ElementInjector) {
this.ref = new ViewRef_(this);
2015-05-20 09:48:15 -07:00
this.locals = new Locals(null, MapWrapper.clone(protoLocals)); // TODO optimize this
}
2015-08-28 11:29:19 -07:00
init(changeDetector: ChangeDetector, elementInjectors: ElementInjector[],
rootElementInjectors: ElementInjector[], preBuiltObjects: PreBuiltObjects[],
views: AppView[], elementRefs: ElementRef[], viewContainers: AppViewContainer[]) {
this.changeDetector = changeDetector;
this.elementInjectors = elementInjectors;
this.rootElementInjectors = rootElementInjectors;
this.preBuiltObjects = preBuiltObjects;
this.views = views;
this.elementRefs = elementRefs;
this.viewContainers = viewContainers;
}
2015-07-07 20:03:00 -07:00
setLocal(contextName: string, value: any): void {
if (!this.hydrated()) throw new BaseException('Cannot set locals on dehydrated view.');
if (!this.proto.templateVariableBindings.has(contextName)) {
2015-01-28 00:42:08 +01:00
return;
}
var templateName = this.proto.templateVariableBindings.get(contextName);
2015-03-11 21:11:39 -07:00
this.locals.set(templateName, value);
}
2015-05-20 09:48:15 -07:00
hydrated(): boolean { return isPresent(this.context); }
/**
* 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
2015-08-20 16:25:34 -07:00
* @param {number} boundElementIndex
*/
2015-08-20 16:25:34 -07:00
triggerEventHandlers(eventName: string, eventObj: Event, boundElementIndex: number): void {
2015-09-29 11:11:06 -07:00
var locals = new Map<string, any>();
locals.set('$event', eventObj);
this.dispatchEvent(boundElementIndex, eventName, locals);
}
// dispatch to element injector or text nodes based on context
notifyOnBinding(b: BindingTarget, currentValue: any): void {
if (b.isTextNode()) {
this.renderer.setText(this.render, b.elementIndex + this.textOffset, currentValue);
} else {
var elementRef = this.elementRefs[this.elementOffset + b.elementIndex];
if (b.isElementProperty()) {
this.renderer.setElementProperty(elementRef, b.name, currentValue);
} else if (b.isElementAttribute()) {
this.renderer.setElementAttribute(elementRef, b.name,
isPresent(currentValue) ? `${currentValue}` : null);
} else if (b.isElementClass()) {
this.renderer.setElementClass(elementRef, b.name, currentValue);
} else if (b.isElementStyle()) {
var unit = isPresent(b.unit) ? b.unit : '';
this.renderer.setElementStyle(elementRef, b.name,
isPresent(currentValue) ? `${currentValue}${unit}` : null);
} else {
throw new BaseException('Unsupported directive record');
}
2015-04-07 20:54:20 -07:00
}
}
logBindingUpdate(b: BindingTarget, value: any): void {
if (b.isDirective() || b.isElementProperty()) {
var elementRef = this.elementRefs[this.elementOffset + b.elementIndex];
this.renderer.setBindingDebugInfo(
elementRef, `${REFLECT_PREFIX}${camelCaseToDashCase(b.name)}`, `${value}`);
}
}
notifyAfterContentChecked(): void {
var eiCount = this.proto.elementBinders.length;
2015-06-12 09:45:31 -07:00
var ei = this.elementInjectors;
for (var i = eiCount - 1; i >= 0; i--) {
if (isPresent(ei[i + this.elementOffset])) ei[i + this.elementOffset].ngAfterContentChecked();
2015-06-12 09:45:31 -07:00
}
}
notifyAfterViewChecked(): void {
var eiCount = this.proto.elementBinders.length;
var ei = this.elementInjectors;
for (var i = eiCount - 1; i >= 0; i--) {
if (isPresent(ei[i + this.elementOffset])) ei[i + this.elementOffset].ngAfterViewChecked();
}
}
getDirectiveFor(directive: DirectiveIndex): any {
var elementInjector = this.elementInjectors[this.elementOffset + directive.elementIndex];
return elementInjector.getDirectiveAtIndex(directive.directiveIndex);
}
2015-04-07 20:54:20 -07:00
getNestedView(boundElementIndex: number): AppView {
var eli = this.elementInjectors[boundElementIndex];
return isPresent(eli) ? eli.getNestedView() : null;
}
getContainerElement(): ElementRef {
return isPresent(this.containerElementInjector) ?
this.containerElementInjector.getElementRef() :
null;
}
getDebugContext(elementIndex: number, directiveIndex: DirectiveIndex): DebugContext {
try {
var offsettedIndex = this.elementOffset + elementIndex;
var hasRefForIndex = offsettedIndex < this.elementRefs.length;
var elementRef = hasRefForIndex ? this.elementRefs[this.elementOffset + elementIndex] : null;
var container = this.getContainerElement();
var ei = hasRefForIndex ? this.elementInjectors[this.elementOffset + elementIndex] : null;
var element = isPresent(elementRef) ? elementRef.nativeElement : null;
var componentElement = isPresent(container) ? container.nativeElement : null;
var directive = isPresent(directiveIndex) ? this.getDirectiveFor(directiveIndex) : null;
var injector = isPresent(ei) ? ei.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;
}
}
getDetectorFor(directive: DirectiveIndex): any {
var childView = this.getNestedView(this.elementOffset + directive.elementIndex);
return isPresent(childView) ? childView.changeDetector : null;
}
2015-08-28 11:29:19 -07:00
invokeElementMethod(elementIndex: number, methodName: string, args: any[]) {
this.renderer.invokeElementMethod(this.elementRefs[elementIndex], methodName, args);
}
// implementation of RenderEventDispatcher#dispatchRenderEvent
dispatchRenderEvent(boundElementIndex: number, eventName: string,
locals: Map<string, any>): boolean {
var elementRef = this.elementRefs[boundElementIndex];
var view = internalView(elementRef.parentView);
return view.dispatchEvent(elementRef.boundElementIndex, eventName, locals);
}
2015-04-16 18:03:15 +02:00
// returns false if preventDefault must be applied to the DOM event
dispatchEvent(boundElementIndex: number, eventName: string, locals: Map<string, any>): boolean {
try {
if (this.hydrated()) {
return !this.changeDetector.handleEvent(eventName, boundElementIndex - this.elementOffset,
new Locals(this.locals, locals));
} else {
return true;
}
} catch (e) {
var c = this.getDebugContext(boundElementIndex - this.elementOffset, null);
var context = isPresent(c) ? new _Context(c.element, c.componentElement, c.context, c.locals,
c.injector) :
null;
throw new EventEvaluationError(eventName, e, e.stack, context);
}
}
get ownBindersCount(): number { return this.proto.elementBinders.length; }
2014-09-28 16:29:11 -07:00
}
2015-10-02 16:47:54 -07:00
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;
}
/**
* Error context included when an event handler throws an exception.
*/
class _Context {
constructor(public element: any, public componentElement: any, public context: any,
public locals: any, public injector: any) {}
}
/**
* Wraps an exception thrown by an event handler.
*/
class EventEvaluationError extends WrappedException {
constructor(eventName: string, originalException: any, originalStack: any, context: any) {
super(`Error during evaluation of "${eventName}"`, originalException, originalStack, context);
}
}
export class AppProtoViewMergeInfo {
constructor(public embeddedViewCount: number, public elementCount: number,
public viewCount: number) {}
}
2015-03-17 19:22:13 +00:00
/**
2015-03-31 22:47:11 +00:00
*
2015-03-17 19:22:13 +00:00
*/
export class AppProtoView {
ref: ProtoViewRef;
protoLocals: Map<string, any>;
elementBinders: ElementBinder[] = null;
mergeInfo: AppProtoViewMergeInfo = null;
variableLocations: Map<string, number> = null;
textBindingCount = null;
render: renderApi.RenderProtoViewRef = null;
2015-05-20 09:48:15 -07:00
2015-11-02 08:39:14 -08:00
constructor(public templateId: string, public templateCmds: TemplateCmd[], public type: ViewType,
public isMergable: boolean, public changeDetectorFactory: Function,
public templateVariableBindings: Map<string, string>, public pipes: ProtoPipes) {
this.ref = new ProtoViewRef_(this);
}
init(render: renderApi.RenderProtoViewRef, elementBinders: ElementBinder[],
textBindingCount: number, mergeInfo: AppProtoViewMergeInfo,
variableLocations: Map<string, number>) {
this.render = render;
this.elementBinders = elementBinders;
this.textBindingCount = textBindingCount;
this.mergeInfo = mergeInfo;
this.variableLocations = variableLocations;
this.protoLocals = new Map<string, any>();
if (isPresent(this.templateVariableBindings)) {
this.templateVariableBindings.forEach(
(templateName, _) => { this.protoLocals.set(templateName, null); });
}
if (isPresent(variableLocations)) {
// The view's locals needs to have a full set of variable names at construction time
// in order to prevent new variables from being set later in the lifecycle. Since we don't
// want
// to actually create variable bindings for the $implicit bindings, add to the
// protoLocals manually.
variableLocations.forEach((_, templateName) => { this.protoLocals.set(templateName, null); });
}
}
isInitialized(): boolean { return isPresent(this.elementBinders); }
2015-10-02 16:47:54 -07:00
}