Refactor EventHandlerInfosForElement storage to avoid ad-hoc assumptions about storage formats
This commit is contained in:
parent
e6f183e39e
commit
3249dce498
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue