angular2
Version:
Angular 2 - a web framework for modern web apps
303 lines (302 loc) • 13.8 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
import { ListWrapper, MapWrapper, Map, StringMapWrapper } from 'angular2/src/facade/collection';
import { Locals } from 'angular2/src/core/change_detection/change_detection';
import { DebugContext } from 'angular2/src/core/change_detection/interfaces';
import { AppElement } from './element';
import { isPresent, isBlank, CONST, CONST_EXPR } from 'angular2/src/facade/lang';
import { BaseException } from 'angular2/src/facade/exceptions';
import { RenderDebugInfo } from 'angular2/src/core/render/api';
import { ViewRef_ } 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 { ViewType } from './view_type';
const REFLECT_PREFIX = 'ng-reflect-';
const EMPTY_CONTEXT = CONST_EXPR(new Object());
/**
* Cost of making objects: http://jsperf.com/instantiate-size-of-object
*
*/
export class AppView {
constructor(proto, renderer, viewManager, projectableNodes, containerAppElement, imperativelyCreatedProviders, rootInjector, changeDetector) {
this.proto = proto;
this.renderer = renderer;
this.viewManager = viewManager;
this.projectableNodes = projectableNodes;
this.containerAppElement = containerAppElement;
this.changeDetector = changeDetector;
/**
* The context against which data-binding expressions in this view are evaluated against.
* This is always a component instance.
*/
this.context = null;
this.destroyed = false;
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, allNodes, disposables, appElements) {
this.rootNodesOrAppElements = rootNodesOrAppElements;
this.allNodes = allNodes;
this.disposables = disposables;
this.appElements = appElements;
var localsMap = new Map();
StringMapWrapper.forEach(this.proto.templateVariableBindings, (templateName, _) => { 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, name) => {
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() { return this.changeDetector.ref; }
get flatRootNodes() { return flattenNestedViewRenderNodes(this.rootNodesOrAppElements); }
hasLocal(contextName) {
return StringMapWrapper.contains(this.proto.templateVariableBindings, contextName);
}
setLocal(contextName, value) {
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, currentValue) {
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, value) {
if (b.isDirective() || b.isElementProperty()) {
var nativeElement = this.appElements[b.elementIndex].nativeElement;
this.renderer.setBindingDebugInfo(nativeElement, `${REFLECT_PREFIX}${camelCaseToDashCase(b.name)}`, `${value}`);
}
}
notifyAfterContentChecked() {
var count = this.appElements.length;
for (var i = count - 1; i >= 0; i--) {
this.appElements[i].ngAfterContentChecked();
}
}
notifyAfterViewChecked() {
var count = this.appElements.length;
for (var i = count - 1; i >= 0; i--) {
this.appElements[i].ngAfterViewChecked();
}
}
getDebugContext(appElement, elementIndex, directiveIndex) {
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) {
return this.appElements[directive.elementIndex].getDirectiveAtIndex(directive.directiveIndex);
}
getDetectorFor(directive) {
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, eventObj, boundElementIndex) {
return this.changeDetector.handleEvent(eventName, boundElementIndex, eventObj);
}
}
function _localsToStringMap(locals) {
var res = {};
var c = locals;
while (isPresent(c)) {
res = StringMapWrapper.merge(res, MapWrapper.toStringMap(c.current));
c = c.parent;
}
return res;
}
/**
*
*/
export class AppProtoView {
constructor(type, protoPipes, templateVariableBindings) {
this.type = type;
this.protoPipes = protoPipes;
this.templateVariableBindings = templateVariableBindings;
}
static create(metadataCache, type, pipes, templateVariableBindings) {
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);
}
}
export let HostViewFactory = class {
constructor(selector, viewFactory) {
this.selector = selector;
this.viewFactory = viewFactory;
}
};
HostViewFactory = __decorate([
CONST(),
__metadata('design:paramtypes', [String, Function])
], HostViewFactory);
export function flattenNestedViewRenderNodes(nodes) {
return _flattenNestedViewRenderNodes(nodes, []);
}
function _flattenNestedViewRenderNodes(nodes, renderNodes) {
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
if (node instanceof AppElement) {
var appEl = 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) {
var lastNode;
if (node instanceof AppElement) {
var appEl = 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, expectedSlotCount, projectableNodes) {
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.`);
}
}