Refactor EventHandlerInfosForElement storage to avoid ad-hoc assumptions about storage formats

This commit is contained in:
Steve Sanderson 2019-09-26 13:09:04 +01:00 committed by Artak
parent e6f183e39e
commit 3249dce498
1 changed files with 50 additions and 22 deletions

View File

@ -31,7 +31,7 @@ export interface OnEventCallback {
export class EventDelegator { export class EventDelegator {
private static nextEventDelegatorId = 0; private static nextEventDelegatorId = 0;
private eventsCollectionKey: string; private readonly eventsCollectionKey: string;
private eventInfoStore: EventInfoStore; private eventInfoStore: EventInfoStore;
@ -42,21 +42,18 @@ export class EventDelegator {
} }
public setListener(element: Element, eventName: string, eventHandlerId: number, renderingComponentId: number) { public setListener(element: Element, eventName: string, eventHandlerId: number, renderingComponentId: number) {
// Ensure we have a place to store event info for this element const infoForElement = this.getEventHandlerInfosForElement(element, true)!;
let infoForElement: EventHandlerInfosForElement = element[this.eventsCollectionKey]; const existingHandler = infoForElement.getHandler(eventName);
if (!infoForElement) {
infoForElement = element[this.eventsCollectionKey] = {};
}
if (infoForElement.hasOwnProperty(eventName)) { if (existingHandler) {
// We can cheaply update the info on the existing object and don't need any other housekeeping // We can cheaply update the info on the existing object and don't need any other housekeeping
const oldInfo = infoForElement[eventName]; // Note that this also takes care of updating the eventHandlerId on the existing handler object
this.eventInfoStore.update(oldInfo.eventHandlerId, eventHandlerId); this.eventInfoStore.update(existingHandler.eventHandlerId, eventHandlerId);
} else { } else {
// Go through the whole flow which might involve registering a new global handler // Go through the whole flow which might involve registering a new global handler
const newInfo = { element, eventName, eventHandlerId, renderingComponentId }; const newInfo = { element, eventName, eventHandlerId, renderingComponentId };
this.eventInfoStore.add(newInfo); this.eventInfoStore.add(newInfo);
infoForElement[eventName] = newInfo; infoForElement.setHandler(eventName, newInfo);
} }
} }
@ -69,12 +66,10 @@ export class EventDelegator {
// Looks like this event handler wasn't already disposed // Looks like this event handler wasn't already disposed
// Remove the associated data from the DOM element // Remove the associated data from the DOM element
const element = info.element; const element = info.element;
if (element.hasOwnProperty(this.eventsCollectionKey)) { const elementEventInfos = this.getEventHandlerInfosForElement(element, false);
const elementEventInfos: EventHandlerInfosForElement = element[this.eventsCollectionKey]; if (elementEventInfos) {
delete elementEventInfos[info.eventName]; elementEventInfos.removeHandler(info.eventName);
if (Object.getOwnPropertyNames(elementEventInfos).length === 0) { this.cleanEventHandlerInfosForElement(element);
delete element[this.eventsCollectionKey];
}
} }
} }
} }
@ -89,15 +84,15 @@ export class EventDelegator {
let eventArgs: EventForDotNet<UIEventArgs> | null = null; // Populate lazily let eventArgs: EventForDotNet<UIEventArgs> | null = null; // Populate lazily
const eventIsNonBubbling = nonBubblingEvents.hasOwnProperty(evt.type); const eventIsNonBubbling = nonBubblingEvents.hasOwnProperty(evt.type);
while (candidateElement) { while (candidateElement) {
if (candidateElement.hasOwnProperty(this.eventsCollectionKey)) { const handlerInfos = this.getEventHandlerInfosForElement(candidateElement, false);
const handlerInfos: EventHandlerInfosForElement = candidateElement[this.eventsCollectionKey]; if (handlerInfos) {
if (handlerInfos.hasOwnProperty(evt.type)) { const handlerInfo = handlerInfos.getHandler(evt.type);
if (handlerInfo) {
// We are going to raise an event for this element, so prepare info needed by the .NET code // We are going to raise an event for this element, so prepare info needed by the .NET code
if (!eventArgs) { if (!eventArgs) {
eventArgs = EventForDotNet.fromDOMEvent(evt); eventArgs = EventForDotNet.fromDOMEvent(evt);
} }
const handlerInfo = handlerInfos[evt.type];
const eventFieldInfo = EventFieldInfo.fromEvent(handlerInfo.renderingComponentId, evt); const eventFieldInfo = EventFieldInfo.fromEvent(handlerInfo.renderingComponentId, evt);
this.onEvent(evt, handlerInfo.eventHandlerId, eventArgs, eventFieldInfo); this.onEvent(evt, handlerInfo.eventHandlerId, eventArgs, eventFieldInfo);
} }
@ -106,6 +101,23 @@ export class EventDelegator {
candidateElement = eventIsNonBubbling ? null : candidateElement.parentElement; candidateElement = eventIsNonBubbling ? null : candidateElement.parentElement;
} }
} }
private getEventHandlerInfosForElement(element: Element, createIfNeeded: boolean): EventHandlerInfosForElement | null {
if (element.hasOwnProperty(this.eventsCollectionKey)) {
return element[this.eventsCollectionKey];
} else if (createIfNeeded) {
return (element[this.eventsCollectionKey] = new EventHandlerInfosForElement());
} else {
return null;
}
}
private cleanEventHandlerInfosForElement(element: Element) {
const existingValue = this.getEventHandlerInfosForElement(element, false);
if (existingValue && existingValue.isEmpty()) {
delete element[this.eventsCollectionKey];
}
}
} }
// Responsible for adding and removing the global listener when the number of listeners // Responsible for adding and removing the global listener when the number of listeners
@ -168,14 +180,30 @@ class EventInfoStore {
} }
} }
interface EventHandlerInfosForElement { class EventHandlerInfosForElement {
// Although we *could* track multiple event handlers per (element, eventName) pair // Although we *could* track multiple event handlers per (element, eventName) pair
// (since they have distinct eventHandlerId values), there's no point doing so because // (since they have distinct eventHandlerId values), there's no point doing so because
// our programming model is that you declare event handlers as attributes. An element // our programming model is that you declare event handlers as attributes. An element
// can only have one attribute with a given name, hence only one event handler with // can only have one attribute with a given name, hence only one event handler with
// that name at any one time. // that name at any one time.
// So to keep things simple, only track one EventHandlerInfo per (element, eventName) // So to keep things simple, only track one EventHandlerInfo per (element, eventName)
[eventName: string]: EventHandlerInfo; private handlers: { [eventName: string]: EventHandlerInfo } = {};
public getHandler(eventName: string): EventHandlerInfo | null {
return this.handlers.hasOwnProperty(eventName) ? this.handlers[eventName] : null;
}
public setHandler(eventName: string, handler: EventHandlerInfo) {
this.handlers[eventName] = handler;
}
public removeHandler(eventName: string) {
delete this.handlers[eventName];
}
public isEmpty(): boolean {
return Object.getOwnPropertyNames(this.handlers).length === 0;
}
} }
interface EventHandlerInfo { interface EventHandlerInfo {