2015-03-06 15:44:59 +01:00
|
|
|
import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection';
|
2015-05-20 09:48:15 -07:00
|
|
|
import {
|
|
|
|
AST,
|
|
|
|
Locals,
|
|
|
|
ChangeDispatcher,
|
|
|
|
ProtoChangeDetector,
|
|
|
|
ChangeDetector,
|
|
|
|
BindingRecord,
|
|
|
|
DirectiveRecord,
|
|
|
|
DirectiveIndex,
|
|
|
|
ChangeDetectorRef
|
|
|
|
} from 'angular2/change_detection';
|
|
|
|
|
|
|
|
import {
|
|
|
|
ProtoElementInjector,
|
|
|
|
ElementInjector,
|
|
|
|
PreBuiltObjects,
|
|
|
|
DirectiveBinding
|
|
|
|
} from './element_injector';
|
2014-11-11 17:33:47 -08:00
|
|
|
import {ElementBinder} from './element_binder';
|
2015-05-20 17:19:46 -07:00
|
|
|
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
|
2015-04-07 17:24:09 -07:00
|
|
|
import * as renderApi from 'angular2/src/render/api';
|
2015-05-18 11:57:20 -07:00
|
|
|
import {EventDispatcher} from 'angular2/src/render/api';
|
2015-06-23 11:21:56 -07:00
|
|
|
import {ViewRef} from './view_ref';
|
|
|
|
import {ElementRef} from './element_ref';
|
2015-04-24 17:53:06 -07:00
|
|
|
|
2015-04-27 09:26:55 -07:00
|
|
|
export class AppViewContainer {
|
2015-06-03 11:02:51 -07:00
|
|
|
// The order in this list matches the DOM order.
|
|
|
|
views: List<AppView> = [];
|
2015-04-24 17:53:06 -07:00
|
|
|
}
|
2014-09-28 16:29:11 -07:00
|
|
|
|
2014-11-21 15:13:01 -08:00
|
|
|
/**
|
2014-10-10 20:44:55 -07:00
|
|
|
* Const 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
|
|
|
*/
|
2015-05-20 09:48:15 -07:00
|
|
|
export class AppView implements ChangeDispatcher, EventDispatcher {
|
2015-06-12 22:38:44 +02:00
|
|
|
render: renderApi.RenderViewRef = null;
|
2014-09-28 16:29:11 -07:00
|
|
|
/// This list matches the _nodes list. It is sparse, since only Elements have ElementInjector
|
2015-05-20 09:48:15 -07:00
|
|
|
rootElementInjectors: List<ElementInjector>;
|
2015-06-12 22:38:44 +02:00
|
|
|
elementInjectors: List<ElementInjector> = null;
|
|
|
|
changeDetector: ChangeDetector = null;
|
|
|
|
componentChildViews: List<AppView> = null;
|
2015-04-27 09:26:55 -07:00
|
|
|
viewContainers: List<AppViewContainer>;
|
2015-06-12 22:38:44 +02:00
|
|
|
preBuiltObjects: List<PreBuiltObjects> = null;
|
2015-06-23 11:21:56 -07:00
|
|
|
elementRefs: List<ElementRef>;
|
|
|
|
ref: ViewRef;
|
2015-04-06 13:19:30 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The context against which data-binding expressions in this view are evaluated against.
|
|
|
|
* This is always a component instance.
|
|
|
|
*/
|
2015-04-09 21:20:11 +02:00
|
|
|
|
2015-06-12 22:38:44 +02:00
|
|
|
context: any = null;
|
2015-04-06 13:19:30 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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-01-02 14:23:59 -08:00
|
|
|
|
2015-05-20 09:48:15 -07:00
|
|
|
constructor(public renderer: renderApi.Renderer, public proto: AppProtoView,
|
|
|
|
protoLocals: Map<string, any>) {
|
2015-04-16 15:38:28 -07:00
|
|
|
this.viewContainers = ListWrapper.createFixedSize(this.proto.elementBinders.length);
|
2015-06-23 11:21:56 -07:00
|
|
|
this.elementRefs = ListWrapper.createFixedSize(this.proto.elementBinders.length);
|
|
|
|
this.ref = new ViewRef(this);
|
|
|
|
for (var i = 0; i < this.elementRefs.length; i++) {
|
2015-06-23 14:26:02 -07:00
|
|
|
this.elementRefs[i] = new ElementRef(this.ref, i, renderer);
|
2015-06-23 11:21:56 -07:00
|
|
|
}
|
2015-05-20 09:48:15 -07:00
|
|
|
this.locals = new Locals(null, MapWrapper.clone(protoLocals)); // TODO optimize this
|
2014-12-01 18:41:55 -08:00
|
|
|
}
|
|
|
|
|
2015-05-20 09:48:15 -07:00
|
|
|
init(changeDetector: ChangeDetector, elementInjectors: List<ElementInjector>,
|
|
|
|
rootElementInjectors: List<ElementInjector>, preBuiltObjects: List<PreBuiltObjects>,
|
|
|
|
componentChildViews: List<AppView>) {
|
2015-03-11 21:43:22 -07:00
|
|
|
this.changeDetector = changeDetector;
|
2014-12-09 10:31:19 -08:00
|
|
|
this.elementInjectors = elementInjectors;
|
|
|
|
this.rootElementInjectors = rootElementInjectors;
|
|
|
|
this.preBuiltObjects = preBuiltObjects;
|
|
|
|
this.componentChildViews = componentChildViews;
|
2014-12-01 18:41:55 -08:00
|
|
|
}
|
|
|
|
|
2015-05-20 09:48:15 -07:00
|
|
|
setLocal(contextName: string, value): void {
|
2014-12-01 18:41:55 -08:00
|
|
|
if (!this.hydrated()) throw new BaseException('Cannot set locals on dehydrated view.');
|
2015-06-17 21:42:56 -07:00
|
|
|
if (!this.proto.variableBindings.has(contextName)) {
|
2015-01-28 00:42:08 +01:00
|
|
|
return;
|
2014-12-01 18:41:55 -08:00
|
|
|
}
|
2015-06-17 16:21:40 -07:00
|
|
|
var templateName = this.proto.variableBindings.get(contextName);
|
2015-03-11 21:11:39 -07:00
|
|
|
this.locals.set(templateName, value);
|
2014-12-01 18:41:55 -08:00
|
|
|
}
|
|
|
|
|
2015-05-20 09:48:15 -07:00
|
|
|
hydrated(): boolean { return isPresent(this.context); }
|
2014-12-01 18:41:55 -08:00
|
|
|
|
2015-03-10 10:03:26 +01:00
|
|
|
/**
|
|
|
|
* 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 {int} binderIndex
|
|
|
|
*/
|
2015-04-19 14:47:02 -07:00
|
|
|
triggerEventHandlers(eventName: string, eventObj, binderIndex: int): void {
|
2015-06-17 16:21:40 -07:00
|
|
|
var locals = new Map();
|
|
|
|
locals.set('$event', eventObj);
|
2015-04-07 20:54:20 -07:00
|
|
|
this.dispatchEvent(binderIndex, eventName, locals);
|
2015-03-10 10:03:26 +01:00
|
|
|
}
|
|
|
|
|
2015-03-31 09:07:01 -07:00
|
|
|
// dispatch to element injector or text nodes based on context
|
2015-05-20 09:48:15 -07:00
|
|
|
notifyOnBinding(b: BindingRecord, currentValue: any): void {
|
2015-06-18 15:44:44 -07:00
|
|
|
if (b.isElementProperty()) {
|
2015-06-23 11:21:56 -07:00
|
|
|
this.renderer.setElementProperty(this.elementRefs[b.elementIndex], b.propertyName,
|
|
|
|
currentValue);
|
2015-06-18 15:44:44 -07:00
|
|
|
} else if (b.isElementAttribute()) {
|
2015-06-23 11:21:56 -07:00
|
|
|
this.renderer.setElementAttribute(this.elementRefs[b.elementIndex], b.propertyName,
|
|
|
|
currentValue);
|
2015-06-18 15:44:44 -07:00
|
|
|
} else if (b.isElementClass()) {
|
2015-06-23 11:21:56 -07:00
|
|
|
this.renderer.setElementClass(this.elementRefs[b.elementIndex], b.propertyName, currentValue);
|
2015-06-18 15:44:44 -07:00
|
|
|
} else if (b.isElementStyle()) {
|
|
|
|
var unit = isPresent(b.propertyUnit) ? b.propertyUnit : '';
|
2015-06-23 11:21:56 -07:00
|
|
|
this.renderer.setElementStyle(this.elementRefs[b.elementIndex], b.propertyName,
|
2015-06-18 15:44:44 -07:00
|
|
|
`${currentValue}${unit}`);
|
|
|
|
} else if (b.isTextNode()) {
|
2015-04-15 21:51:30 -07:00
|
|
|
this.renderer.setText(this.render, b.elementIndex, currentValue);
|
2015-06-18 15:44:44 -07:00
|
|
|
} else {
|
|
|
|
throw new BaseException('Unsupported directive record');
|
2015-04-07 20:54:20 -07:00
|
|
|
}
|
|
|
|
}
|
2015-04-09 21:20:11 +02:00
|
|
|
|
2015-06-12 09:45:31 -07:00
|
|
|
notifyOnAllChangesDone(): void {
|
|
|
|
var ei = this.elementInjectors;
|
|
|
|
for (var i = ei.length - 1; i >= 0; i--) {
|
|
|
|
if (isPresent(ei[i])) ei[i].onAllChangesDone();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-26 11:10:52 -07:00
|
|
|
getDirectiveFor(directive: DirectiveIndex): any {
|
2015-04-14 08:54:09 -07:00
|
|
|
var elementInjector = this.elementInjectors[directive.elementIndex];
|
2015-04-09 07:57:33 -07:00
|
|
|
return elementInjector.getDirectiveAtIndex(directive.directiveIndex);
|
|
|
|
}
|
2015-04-07 20:54:20 -07:00
|
|
|
|
2015-06-26 11:10:52 -07:00
|
|
|
getDetectorFor(directive: DirectiveIndex): any {
|
2015-04-24 17:53:06 -07:00
|
|
|
var childView = this.componentChildViews[directive.elementIndex];
|
|
|
|
return isPresent(childView) ? childView.changeDetector : null;
|
2015-04-14 08:54:09 -07:00
|
|
|
}
|
|
|
|
|
2015-06-18 15:44:44 -07:00
|
|
|
invokeElementMethod(elementIndex: number, methodName: string, args: List<any>) {
|
2015-06-23 11:21:56 -07:00
|
|
|
this.renderer.invokeElementMethod(this.elementRefs[elementIndex], methodName, args);
|
2015-05-11 12:31:16 -07:00
|
|
|
}
|
|
|
|
|
2015-04-07 20:54:20 -07:00
|
|
|
// implementation of EventDispatcher#dispatchEvent
|
2015-04-16 18:03:15 +02:00
|
|
|
// returns false if preventDefault must be applied to the DOM event
|
2015-05-20 09:48:15 -07:00
|
|
|
dispatchEvent(elementIndex: number, eventName: string, locals: Map<string, any>): boolean {
|
2015-04-07 20:54:20 -07:00
|
|
|
// 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.
|
2015-04-16 18:03:15 +02:00
|
|
|
var allowDefaultBehavior = true;
|
2015-04-07 20:54:20 -07:00
|
|
|
if (this.hydrated()) {
|
|
|
|
var elBinder = this.proto.elementBinders[elementIndex];
|
2015-04-16 18:03:15 +02:00
|
|
|
if (isBlank(elBinder.hostListeners)) return allowDefaultBehavior;
|
2015-04-09 21:20:11 +02:00
|
|
|
var eventMap = elBinder.hostListeners[eventName];
|
2015-04-16 18:03:15 +02:00
|
|
|
if (isBlank(eventMap)) return allowDefaultBehavior;
|
2015-04-07 20:54:20 -07:00
|
|
|
MapWrapper.forEach(eventMap, (expr, directiveIndex) => {
|
|
|
|
var context;
|
|
|
|
if (directiveIndex === -1) {
|
|
|
|
context = this.context;
|
|
|
|
} else {
|
|
|
|
context = this.elementInjectors[elementIndex].getDirectiveAtIndex(directiveIndex);
|
|
|
|
}
|
2015-04-16 18:03:15 +02:00
|
|
|
var result = expr.eval(context, new Locals(this.locals, locals));
|
|
|
|
if (isPresent(result)) {
|
2015-05-13 15:28:03 -07:00
|
|
|
allowDefaultBehavior = allowDefaultBehavior && result == true;
|
2015-04-16 18:03:15 +02:00
|
|
|
}
|
2015-04-07 20:54:20 -07:00
|
|
|
});
|
2014-12-02 17:09:46 -08:00
|
|
|
}
|
2015-04-16 18:03:15 +02:00
|
|
|
return allowDefaultBehavior;
|
2014-12-02 17:09:46 -08:00
|
|
|
}
|
2014-09-28 16:29:11 -07:00
|
|
|
}
|
|
|
|
|
2015-03-17 19:22:13 +00:00
|
|
|
/**
|
2015-03-31 22:47:11 +00:00
|
|
|
*
|
2015-03-17 19:22:13 +00:00
|
|
|
*/
|
2015-04-09 21:20:11 +02:00
|
|
|
export class AppProtoView {
|
2015-06-12 22:38:44 +02:00
|
|
|
elementBinders: List<ElementBinder> = [];
|
2015-06-17 16:21:40 -07:00
|
|
|
protoLocals: Map<string, any> = new Map();
|
2015-05-20 09:48:15 -07:00
|
|
|
|
|
|
|
constructor(public render: renderApi.RenderProtoViewRef,
|
|
|
|
public protoChangeDetector: ProtoChangeDetector,
|
2015-06-16 09:45:03 -07:00
|
|
|
public variableBindings: Map<string, string>,
|
|
|
|
public variableLocations: Map<string, number>) {
|
2015-05-11 17:59:39 -07:00
|
|
|
if (isPresent(variableBindings)) {
|
2015-06-17 16:21:40 -07:00
|
|
|
MapWrapper.forEach(variableBindings,
|
|
|
|
(templateName, _) => { this.protoLocals.set(templateName, null); });
|
2015-05-11 17:59:39 -07:00
|
|
|
}
|
2014-11-18 16:38:36 -08:00
|
|
|
}
|
|
|
|
|
2015-05-20 09:48:15 -07:00
|
|
|
bindElement(parent: ElementBinder, distanceToParent: int,
|
|
|
|
protoElementInjector: ProtoElementInjector,
|
|
|
|
componentDirective: DirectiveBinding = null): ElementBinder {
|
2015-06-15 15:18:11 -07:00
|
|
|
var elBinder = new ElementBinder(this.elementBinders.length, parent, distanceToParent,
|
|
|
|
protoElementInjector, componentDirective);
|
2015-06-04 13:45:08 -07:00
|
|
|
|
2015-06-17 11:17:21 -07:00
|
|
|
this.elementBinders.push(elBinder);
|
2014-11-11 17:33:47 -08:00
|
|
|
return elBinder;
|
|
|
|
}
|
|
|
|
|
2014-11-19 14:54:07 -08:00
|
|
|
/**
|
2015-03-06 15:44:59 +01:00
|
|
|
* Adds an event binding for the last created ElementBinder via bindElement.
|
|
|
|
*
|
|
|
|
* If the directive index is a positive integer, the event is evaluated in the context of
|
|
|
|
* the given directive.
|
|
|
|
*
|
|
|
|
* If the directive index is -1, the event is evaluated in the context of the enclosing view.
|
|
|
|
*
|
|
|
|
* @param {string} eventName
|
|
|
|
* @param {AST} expression
|
|
|
|
* @param {int} directiveIndex The directive index in the binder or -1 when the event is not bound
|
|
|
|
* to a directive
|
2014-11-19 14:54:07 -08:00
|
|
|
*/
|
2015-05-20 09:48:15 -07:00
|
|
|
bindEvent(eventBindings: List<renderApi.EventBinding>, boundElementIndex: number,
|
|
|
|
directiveIndex: int = -1): void {
|
2015-04-30 14:45:42 -07:00
|
|
|
var elBinder = this.elementBinders[boundElementIndex];
|
2015-04-09 21:20:11 +02:00
|
|
|
var events = elBinder.hostListeners;
|
2015-03-06 15:44:59 +01:00
|
|
|
if (isBlank(events)) {
|
|
|
|
events = StringMapWrapper.create();
|
2015-04-09 21:20:11 +02:00
|
|
|
elBinder.hostListeners = events;
|
2015-03-06 15:44:59 +01:00
|
|
|
}
|
2015-04-02 15:56:58 +02:00
|
|
|
for (var i = 0; i < eventBindings.length; i++) {
|
|
|
|
var eventBinding = eventBindings[i];
|
|
|
|
var eventName = eventBinding.fullName;
|
|
|
|
var event = StringMapWrapper.get(events, eventName);
|
|
|
|
if (isBlank(event)) {
|
2015-06-17 16:21:40 -07:00
|
|
|
event = new Map();
|
2015-04-02 15:56:58 +02:00
|
|
|
StringMapWrapper.set(events, eventName, event);
|
|
|
|
}
|
2015-06-17 16:21:40 -07:00
|
|
|
event.set(directiveIndex, eventBinding.source);
|
2014-11-19 14:54:07 -08:00
|
|
|
}
|
|
|
|
}
|
2015-04-09 21:20:11 +02:00
|
|
|
}
|