Skip to content

Instantly share code, notes, and snippets.

@KonnorRogers
Last active September 3, 2023 16:47
Show Gist options
  • Save KonnorRogers/bf2b5e0356578263ebabf8799064f242 to your computer and use it in GitHub Desktop.
Save KonnorRogers/bf2b5e0356578263ebabf8799064f242 to your computer and use it in GitHub Desktop.
Automatically assign the proper "this" when using addEventListener
class BaseElement extends HTMLElement {
constructor () {
super()
/**
* @type {Map<(this: HTMLElement, evt: HTMLElementEventMap[keyof HTMLElementEventMap]) => any, {handleEvent: HTMLElementEventMap[keyof HTMLElementEventMap]}> | null}
*/
this.__eventMap__ = null
}
/**
* @template K
* @param {K extends keyof HTMLElementEventMap ? keyof HTMLElementEventMap : never} type
* @param {K extends keyof HTMLElementEventMap ? ((this: HTMLElement, evt: HTMLElementEventMap[K]) => any) : never } callback
* @param {boolean | AddEventListenerOptions} [options]
* @returns {void}
*/
addEventListener (type, callback, options) {
if (typeof callback !== "function") {
super.addEventListener(type, callback, options)
return
}
if (this.__eventMap__ == null) this.__eventMap__ = new Map()
/** @type {K extends keyof HTMLElementEventMap ? (this: HTMLElement, ev: HTMLElementEventMap[K]) => any : never} */
// @ts-expect-error
let event = this.__eventMap__.get(callback)
if (event == null) {
const self = this
const toEvent = {
/**
* @param {HTMLElementEventMap[type]} evt
*/
handleEvent (evt) {
/** @type {K extends keyof HTMLElementEventMap ? (this: HTMLElement, ev: HTMLElementEventMap[K]) => any : never} */
// @ts-expect-error
callback.call(self, evt)
}
}
/** @type {K extends keyof HTMLElementEventMap ? (this: HTMLElement, ev: HTMLElementEventMap[K]) => any : never} */
// @ts-expect-error
this.__eventMap__.set(callback, toEvent)
/** @type {K extends keyof HTMLElementEventMap ? (this: HTMLElement, ev: HTMLElementEventMap[K]) => any : never} */
// @ts-expect-error
event = toEvent
}
// @ts-expect-error
super.addEventListener(type, event, options)
}
/**
* @param {Parameters<HTMLElement["removeEventListener"]>} args
* @returns {ReturnType<HTMLElement["removeEventListener"]>}
*/
removeEventListener (...args) {
const [type, callback, options] = args
if (typeof callback !== "function" || this.__eventMap__ == null) {
super.removeEventListener(type, callback, options)
return
}
let event = this.__eventMap__.get(callback)
if (event == null) {
super.removeEventListener(type, callback, options)
return
}
// @ts-expect-error
super.removeEventListener(type, event, options)
}
}
// Usage
class MyEl extends BaseElement {
constructor () {
super()
this.addEventListener("click", this.handleClick)
}
handleClick (evt) {
// Should have proper "this"!
console.log({ this: this, evt })
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment