import { EventForDotNet , UIEventArgs } from './EventForDotNet' ; import { EventFieldInfo } from './EventFieldInfo' ; const nonBubblingEvents = toLookup ( [ 'abort' , 'blur' , 'change' , 'error' , 'focus' , 'load' , 'loadend' , 'loadstart' , 'mouseenter' , 'mouseleave' , 'progress' , 'reset' , 'scroll' , 'submit' , 'unload' , 'DOMNodeInsertedIntoDocument' , 'DOMNodeRemovedFromDocument' , ] ) ; export interface OnEventCallback { ( event , eventHandlerId , eventArgs , eventFieldInfo ) : void ; } export class EventDelegator { private static nextEventDelegatorId = 0 ; private eventsCollectionKey : string ; private eventInfoStore : EventInfoStore ; constructor ( private onEvent ) { const eventDelegatorId = ++ EventDelegator . nextEventDelegatorId ; this . eventsCollectionKey = `_blazorEvents_${ eventDelegatorId }` ; this . eventInfoStore = new EventInfoStore ( this . onGlobalEvent . bind ( this ) ) ; } public setListener ( element , eventName , eventHandlerId , renderingComponentId ) { let infoForElement = element [ this . eventsCollectionKey ] ; if ( ! infoForElement ) { infoForElement = element [ this . eventsCollectionKey ] = { } ; } if ( infoForElement . hasOwnProperty ( eventName ) ) { const oldInfo = infoForElement [ eventName ] ; this . eventInfoStore . update ( oldInfo . eventHandlerId , eventHandlerId ) ; } else { const newInfo = { element , eventName , eventHandlerId , renderingComponentId } ; this . eventInfoStore . add ( newInfo ) ; infoForElement [ eventName ] = newInfo ; } } public removeListener ( eventHandlerId ) { const info = this . eventInfoStore . remove ( eventHandlerId ) ; if ( info ) { const element = info . element ; if ( element . hasOwnProperty ( this . eventsCollectionKey ) ) { const elementEventInfos = element [ this . eventsCollectionKey ] ; delete elementEventInfos [ info . eventName ] ; if ( Object . getOwnPropertyNames ( elementEventInfos ) . length === 0 ) { delete element [ this . eventsCollectionKey ] ; } } } } private onGlobalEvent ( evt ) { if ( ! ( evt . target instanceof Element ) ) { return ; } let candidateElement = evt . target as Element | null ; let eventArgs = null ; const eventIsNonBubbling = nonBubblingEvents . hasOwnProperty ( evt . type ) ; while ( candidateElement ) { if ( candidateElement . hasOwnProperty ( this . eventsCollectionKey ) ) { const handlerInfos = candidateElement [ this . eventsCollectionKey ] ; if ( handlerInfos . hasOwnProperty ( evt . type ) ) { if ( ! eventArgs ) { eventArgs = EventForDotNet . fromDOMEvent ( evt ) ; } const handlerInfo = handlerInfos [ evt . type ] ; const eventFieldInfo = EventFieldInfo . fromEvent ( handlerInfo . renderingComponentId , evt ) ; this . onEvent ( evt , handlerInfo . eventHandlerId , eventArgs , eventFieldInfo ) ; } } candidateElement = eventIsNonBubbling ? null : candidateElement . parentElement ; } } } class EventInfoStore { private infosByEventHandlerId : { [ eventHandlerId ] : EventHandlerInfo ; } = { } ; private countByEventName : { [ eventName ] : number ; } = { } ; constructor ( private globalListener ) { } public add ( info ) { if ( this . infosByEventHandlerId [ info . eventHandlerId ] ) { throw new Error ( `Event ${ info . eventHandlerId } is already tracked` ) ; } this . infosByEventHandlerId [ info . eventHandlerId ] = info ; const eventName = info . eventName ; if ( this . countByEventName . hasOwnProperty ( eventName ) ) { this . countByEventName [ eventName ] ++ ; } else { this . countByEventName [ eventName ] = 1 ; const useCapture = nonBubblingEvents . hasOwnProperty ( eventName ) ; document . addEventListener ( eventName , this . globalListener , useCapture ) ; } } public update ( oldEventHandlerId , newEventHandlerId ) { if ( this . infosByEventHandlerId . hasOwnProperty ( newEventHandlerId ) ) { throw new Error ( `Event ${ newEventHandlerId } is already tracked` ) ; } const info = this . infosByEventHandlerId [ oldEventHandlerId ] ; delete this . infosByEventHandlerId [ oldEventHandlerId ] ; info . eventHandlerId = newEventHandlerId ; this . infosByEventHandlerId [ newEventHandlerId ] = info ; } public remove ( eventHandlerId ) { const info = this . infosByEventHandlerId [ eventHandlerId ] ; if ( info ) { delete this . infosByEventHandlerId [ eventHandlerId ] ; const eventName = info . eventName ; if ( -- this . countByEventName [ eventName ] === 0 ) { delete this . countByEventName [ eventName ] ; document . removeEventListener ( eventName , this . globalListener ) ; } } return info ; } } interface EventHandlerInfosForElement { [ eventName ] : EventHandlerInfo ; } interface EventHandlerInfo { element : Element ; eventName : string ; eventHandlerId : number ; renderingComponentId : number ; } function toLookup ( items ) { const result = { } ; items . forEach ( value => { result [ value ] = true ; } ) ; return result ; }