refactor(change_detector): made change detection responsible for processing events
Closes #3666
This commit is contained in:
@ -1,15 +1,11 @@
|
||||
import {AST} from 'angular2/src/change_detection/change_detection';
|
||||
import {isBlank, isPresent, BaseException} from 'angular2/src/facade/lang';
|
||||
import * as eiModule from './element_injector';
|
||||
import {DirectiveBinding} from './element_injector';
|
||||
import {List, StringMap} from 'angular2/src/facade/collection';
|
||||
import * as viewModule from './view';
|
||||
|
||||
export class ElementBinder {
|
||||
// updated later, so we are able to resolve cycles
|
||||
nestedProtoView: viewModule.AppProtoView = null;
|
||||
// updated later when events are bound
|
||||
hostListeners: StringMap<string, Map<number, AST>> = null;
|
||||
|
||||
constructor(public index: int, public parent: ElementBinder, public distanceToParent: int,
|
||||
public protoElementInjector: eiModule.ProtoElementInjector,
|
||||
|
@ -23,12 +23,50 @@ import {AppProtoView} from './view';
|
||||
import {ElementBinder} from './element_binder';
|
||||
import {ProtoElementInjector, DirectiveBinding} from './element_injector';
|
||||
|
||||
class BindingRecordsCreator {
|
||||
export class BindingRecordsCreator {
|
||||
_directiveRecordsMap: Map<number, DirectiveRecord> = new Map();
|
||||
|
||||
getBindingRecords(textBindings: List<ASTWithSource>,
|
||||
elementBinders: List<renderApi.ElementBinder>,
|
||||
allDirectiveMetadatas: List<renderApi.DirectiveMetadata>): List<BindingRecord> {
|
||||
getEventBindingRecords(elementBinders: List<renderApi.ElementBinder>,
|
||||
allDirectiveMetadatas: renderApi.DirectiveMetadata[]): BindingRecord[] {
|
||||
var res = [];
|
||||
for (var boundElementIndex = 0; boundElementIndex < elementBinders.length;
|
||||
boundElementIndex++) {
|
||||
var renderElementBinder = elementBinders[boundElementIndex];
|
||||
|
||||
this._createTemplateEventRecords(res, renderElementBinder, boundElementIndex);
|
||||
this._createHostEventRecords(res, renderElementBinder, allDirectiveMetadatas,
|
||||
boundElementIndex);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private _createTemplateEventRecords(res: BindingRecord[],
|
||||
renderElementBinder: renderApi.ElementBinder,
|
||||
boundElementIndex: number): void {
|
||||
renderElementBinder.eventBindings.forEach(eb => {
|
||||
res.push(BindingRecord.createForEvent(eb.source, eb.fullName, boundElementIndex));
|
||||
});
|
||||
}
|
||||
|
||||
private _createHostEventRecords(res: BindingRecord[],
|
||||
renderElementBinder: renderApi.ElementBinder,
|
||||
allDirectiveMetadatas: renderApi.DirectiveMetadata[],
|
||||
boundElementIndex: number): void {
|
||||
for (var i = 0; i < renderElementBinder.directives.length; ++i) {
|
||||
var dir = renderElementBinder.directives[i];
|
||||
var directiveMetadata = allDirectiveMetadatas[dir.directiveIndex];
|
||||
var dirRecord = this._getDirectiveRecord(boundElementIndex, i, directiveMetadata);
|
||||
dir.eventBindings.forEach(heb => {
|
||||
res.push(
|
||||
BindingRecord.createForHostEvent(heb.source, heb.fullName, dirRecord.directiveIndex));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getPropertyBindingRecords(textBindings: List<ASTWithSource>,
|
||||
elementBinders: List<renderApi.ElementBinder>,
|
||||
allDirectiveMetadatas:
|
||||
List<renderApi.DirectiveMetadata>): List<BindingRecord> {
|
||||
var bindings = [];
|
||||
|
||||
this._createTextNodeRecords(bindings, textBindings);
|
||||
@ -232,8 +270,10 @@ function _getChangeDetectorDefinitions(
|
||||
return ListWrapper.map(nestedPvsWithIndex, (pvWithIndex) => {
|
||||
var elementBinders = pvWithIndex.renderProtoView.elementBinders;
|
||||
var bindingRecordsCreator = new BindingRecordsCreator();
|
||||
var bindingRecords = bindingRecordsCreator.getBindingRecords(
|
||||
var propBindingRecords = bindingRecordsCreator.getPropertyBindingRecords(
|
||||
pvWithIndex.renderProtoView.textBindings, elementBinders, allRenderDirectiveMetadata);
|
||||
var eventBindingRecords =
|
||||
bindingRecordsCreator.getEventBindingRecords(elementBinders, allRenderDirectiveMetadata);
|
||||
var directiveRecords =
|
||||
bindingRecordsCreator.getDirectiveRecords(elementBinders, allRenderDirectiveMetadata);
|
||||
var strategyName = DEFAULT;
|
||||
@ -248,8 +288,8 @@ function _getChangeDetectorDefinitions(
|
||||
}
|
||||
var id = `${hostComponentMetadata.id}_${typeString}_${pvWithIndex.index}`;
|
||||
var variableNames = nestedPvVariableNames[pvWithIndex.index];
|
||||
return new ChangeDetectorDefinition(id, strategyName, variableNames, bindingRecords,
|
||||
directiveRecords, assertionsEnabled());
|
||||
return new ChangeDetectorDefinition(id, strategyName, variableNames, propBindingRecords,
|
||||
eventBindingRecords, directiveRecords, assertionsEnabled());
|
||||
});
|
||||
}
|
||||
|
||||
@ -266,8 +306,6 @@ function _createAppProtoView(
|
||||
protoChangeDetector, variableBindings, createVariableLocations(elementBinders),
|
||||
renderProtoView.textBindings.length, protoPipes);
|
||||
_createElementBinders(protoView, elementBinders, allDirectives);
|
||||
_bindDirectiveEvents(protoView, elementBinders);
|
||||
|
||||
return protoView;
|
||||
}
|
||||
|
||||
@ -393,8 +431,6 @@ function _createElementBinder(protoView: AppProtoView, boundElementIndex, render
|
||||
}
|
||||
var elBinder = protoView.bindElement(parent, renderElementBinder.distanceToParent,
|
||||
protoElementInjector, componentDirectiveBinding);
|
||||
protoView.bindEvent(renderElementBinder.eventBindings, boundElementIndex, -1);
|
||||
// variables
|
||||
// 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
|
||||
@ -450,19 +486,6 @@ function _directiveExportAs(directive): string {
|
||||
}
|
||||
}
|
||||
|
||||
function _bindDirectiveEvents(protoView, elementBinders: List<renderApi.ElementBinder>) {
|
||||
for (var boundElementIndex = 0; boundElementIndex < elementBinders.length; ++boundElementIndex) {
|
||||
var dirs = elementBinders[boundElementIndex].directives;
|
||||
for (var i = 0; i < dirs.length; i++) {
|
||||
var directiveBinder = dirs[i];
|
||||
|
||||
// directive events
|
||||
protoView.bindEvent(directiveBinder.eventBindings, boundElementIndex, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class RenderProtoViewWithIndex {
|
||||
constructor(public renderProtoView: renderApi.ProtoViewDto, public index: number,
|
||||
public parentIndex: number, public boundElementIndex: number) {}
|
||||
|
@ -262,29 +262,12 @@ export class AppView implements ChangeDispatcher, RenderEventDispatcher {
|
||||
// returns false if preventDefault must be applied to the DOM event
|
||||
dispatchEvent(boundElementIndex: number, eventName: string, locals: Map<string, any>): boolean {
|
||||
try {
|
||||
// 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.
|
||||
var allowDefaultBehavior = true;
|
||||
if (this.hydrated()) {
|
||||
var elBinder = this.proto.elementBinders[boundElementIndex - this.elementOffset];
|
||||
if (isBlank(elBinder.hostListeners)) return allowDefaultBehavior;
|
||||
var eventMap = elBinder.hostListeners[eventName];
|
||||
if (isBlank(eventMap)) return allowDefaultBehavior;
|
||||
MapWrapper.forEach(eventMap, (expr, directiveIndex) => {
|
||||
var context;
|
||||
if (directiveIndex === -1) {
|
||||
context = this.context;
|
||||
} else {
|
||||
context = this.elementInjectors[boundElementIndex].getDirectiveAtIndex(directiveIndex);
|
||||
}
|
||||
var result = expr.eval(context, new Locals(this.locals, locals));
|
||||
if (isPresent(result)) {
|
||||
allowDefaultBehavior = allowDefaultBehavior && result == true;
|
||||
}
|
||||
});
|
||||
return !this.changeDetector.handleEvent(eventName, boundElementIndex - this.elementOffset,
|
||||
new Locals(this.locals, locals));
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return allowDefaultBehavior;
|
||||
} catch (e) {
|
||||
var c = this.getDebugContext(boundElementIndex - this.elementOffset, null);
|
||||
var context = isPresent(c) ? new _Context(c.element, c.componentElement, c.context, c.locals,
|
||||
@ -354,37 +337,4 @@ export class AppProtoView {
|
||||
this.elementBinders.push(elBinder);
|
||||
return elBinder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
bindEvent(eventBindings: List<renderApi.EventBinding>, boundElementIndex: number,
|
||||
directiveIndex: int = -1): void {
|
||||
var elBinder = this.elementBinders[boundElementIndex];
|
||||
var events = elBinder.hostListeners;
|
||||
if (isBlank(events)) {
|
||||
events = StringMapWrapper.create();
|
||||
elBinder.hostListeners = events;
|
||||
}
|
||||
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)) {
|
||||
event = new Map();
|
||||
StringMapWrapper.set(events, eventName, event);
|
||||
}
|
||||
event.set(directiveIndex, eventBinding.source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user