CustomEvents in JavaScript

Taking notes on CustomEvents in JavaScript. Creating a list of CustomEvents that I use in my own design system so that I can more easily keep track of them

Date Created:
Last Edited:

References


Custom Events


The CustomEvent interface represents events initialized by an application for any purpose.

CustomEvent.detail - the read-only property of the CustomEvent interface returns any data when initializing an event.

CustomEvent.bubbles - set to true if you want the event to bubble up through the DOM tree, else set to false if you don't want the event to bubble.

Events created and dispatched by the client-side JavaScript code, rather than by the browser, are often called synthetic events.

Events can be created with the new Event() and new CustomEvent() constructors, and they can be dispatched with HTMLElement.dispatchEvent(Event|CustomEvent).

Compatibility

Why Use Custom Events


  1. If you are creating an npm package and you want to notify users of this package that some event that they might want to listen to has occurred or you want to send some manipulated / created data back to the user.
    • The user can't access variables in another module unless those variables are exported or passed to the user through custom events.
    • Example: I use the @pquina/pintura package for image editing, and I listen for the process, loaderror, and processerror Custom Events that are dispatched on the editor so that that I can handle them appropriately.
    • I use many HTMX events as well.
  1. Similar to why you might want to dispatch Custom Events in a npm package, you might want to dispatch events when you use lazy loading of JavaScript code.
    1. It is sometimes easier to access data across modularized code through events rather than by importing the module and accessing the data through a getter-like function.

Adding Custom Events in Typescript


Probably the best way to add Custom Events in TypeScript would be to follow the answer to this stack overflow question and modify the addEventListener / dispatchEvent definitions in the global namespace as seen in the Typescript documentation. Below, you can see an example of modifying the global namespace to add the custom events "customnumberevent" and "anothercustomevent", which have a detail property (see above) of type number and CustomParams.

interface CustomEventMap {
"customnumberevent": CustomEvent<number>;
"anothercustomevent": CustomEvent<CustomParams>;
}
declare global {
interface Document { //adds definition to Document, but you can do the same with HTMLElement
addEventListener<K extends keyof CustomEventMap>(type: K,
listener: (this: Document, ev: CustomEventMap[K]) => void): void;
dispatchEvent<K extends keyof CustomEventMap>(ev: CustomEventMap[K]): void;
}
}
export { }; //keep that for TS compiler.

While the above may be the best way to implement Custom Events in TypeScript, I use the below method because it is easier and since I have a weird way of transpiling TypeScript - enough so that modifying global type definition wouldn't help much.

type RandomType = CustomEvent<{ a: "Hello World" }>
function handleA(a: Event) {
  const event = a as RandomType;
  const detail = event.detail.a;
}
document.addEventListener('CHANGE',handleA);

Custom Events in My Design System


  • NEW_CONTENT_LOADED
type NEW_CONTENT_LOADED_Event = CustomEvent<{ el: HTMLElement|undefined }>
    • The NEW_CONTENT_LOADED event should be dispatched whenever new content is loaded in the DOM. We check all of the newly loaded elements to see if we should add listeners to them or register Lexical or Code editors.
    • If the el attribute in detail is missing, then we use document.querySelector to search for elements to add listeners to, else we use el.querySelector to search for elements to add listeners to.
  • tabChange
type tabChange_Event = CustomEvent<{ from: number, to: number: wrapper: HTMLDivElement }>
    • Listen for tab changes. I created this event originally so that we can use one google maps instance, but load different geographies based on whether or not the tab has changed.
  • new_element_added
type VALID_CURRENT_MODES = "circle" | "ellipse" | "line" | "path" | "polygon" | "polyline" | "text" | "manipulate" | "rectangle"
type new_element_added_Event = CustomEvent<{ type: VALID_CURRENT_NODES, shape: PIXI.Graphics|PIXI.Text }>
    • This event is current used in the create svg implementation to notify other elements that an element has been added to the <canvas>.
  • lexical_state_registered
type lexical_state_registered_Event = CustomEvent<undefined>
    • Event is dispatched on the form that wraps around the lexical editor to notify the form element that a new lexical state has been registered.
    • This is used so that the form can keep track of the state of the lexical editors in the form. It is currently used so that we can warn users when they are about to navigate away from a page with unsaved changes.
  • media-upload-complete
type objs_type = { url: string, height?:number, width?: number}[];
type media_upload_complete_Event = CustomEvent<{ upload: objs_type }>
    • This event is dispatched on the input element that accepts image, audio, or video files.
    • The reason this event is dispatched / needs to be dispatched is because the default event that a change in the <input> element triggers is prevented with e.preventDefault() and also because we typically don't care about the object that was uploaded by the user, but we do care about the object that is stored in the S3 database.
      • In other words, we care about the media object after it has been uploaded to the database.
    • The url attribute is equal to the URL of the object stored in S3 (or its CloudFront equivalent) and the width/height attributes are equal to the width / height of the image / video that was uploaded (or they are undefined in the case of audio files).
    • This event is triggered when images, audio files, or video files are uploaded to the application.
  • nav-media
type nav_media_Event = CustomEvent<{ detail: "next"|"back" }>
    • For multiple image inputs, we only want to use one pintura image editor to edit each of the images, but we want to be able to switch between images to edit. We need to use this nav-media event to listen for when users want to navigate between images to edit, so that we can load the new image appropriately into the image editor.
  • gif-upoad
// urls is a string with multiple urls conatentated by joining with | as the delimeter - might want to change that and use a string array
type gif_upload_Event = CustomEvent<{ urls: string }>
    • GIFs can't be edited using the pintura image editor, so we need our own custom upload process for them. As a result, we need the gif-upload event, which is like the media-upload-complete event, but for gifs.
  • delete-image
type delete_image_Event = CustomEvent<{ inputName: string }>
    • Images are kept track of using a client side object that holds references to the pintura image editor for that image input. When you delete an image input, you also want to delete the reference to the input on the client side object. This is where the delete-image event comes into play. The delete-image event deletes from the client side object references to the image input with the name inputName.
    • All image inputs and the client side object that keeps track of images are deleted on page change.
  • HIDE_OPEN_POPOVER
type HIDE_OPEN_POPOVER_event = CustomEvent<{ str: string }>
    • I use floating-ui for (custom) tooltips, menus, floating action buttons, and select components. The library is great for these elements - elements that need to be positioned absolutely but need to appear in the same place relative to another element.
    • The custom popovers are dispatched on a click or mouseover event, and the element they show (the popover) is identified by the data-pelem attribute on the element that dispatched the popover. The data-pelem attribute should look like a CSS query selector.
    • Open popovers and their update functions (functions that run to make sure the elements are positioned correctly after a scroll event or some manipulation of the screen) are stored in a client side object.
    • Custom popovers are normally closed ad removed from the client side object on a click event outside of the element or a blur event, but sometimes you need to force the element to close - which is why the HIDE_OPEN_POPOVER custom event was created. This event allows you to force the open popover to be closed and removed from the client side object.
    • The str attribute should be a string that is equal to the data-pelem attribute of the element that dispatched the popover.
  • delete-lexical-instance
type delete_lexical_instance_Event = CustomEvent<undefined>
    • The delete-lexical-instance event should be dispatched on an element that is a wrapper for lexical instance - <div data-rich-text-editor, and when this event is dispatched on a rich text editor wrapper, the reference to that Lexical Editor in the client side object is deleted.
  • OPEN_DIALOG
type OPEN_DIALOG_Event = CustomEvent<{ dialog: HTMLDialogElement }>
    • The OPEN_DIALOG event should be dispatched when you want to open a dialog properly and you don't have access to the openDialog() function in the main script. This should be utilized in lazy-loaded scripts.
  • CLOSE_DIALOG
type CLOSE_DIALOG_Event = CustomEvent<{ dialog: HTMLDialogElement }>
    • The CLOSE_DIALOG event should be dispatched when you want to close a dialog properly and you don't have access to the closeDialog() function in the main script. This should be utilized in lazy-loaded scripts.
  • REGISTER_LEXICAL_CODE_EDITOR
type REGISTER_LEXICAL_CODE_EDITOR_Event = CustomEvent<{ detail: HTMLElement  }>
    • This event is registered on the document.
    • This CustomEvent is used for the HtmlNode in the Lexical implementation, and it probably should not be used elsewhere.
  • GET_CODEMIRROR_TEXT
type GET_CODEMIRROR_TEXT_Event = CustomEvent<{ id: string }>
    • This event is registered on the document.
    • The id provided in this event, which should be the id of the div[data-editor-wrapper], is used to get the input to the code mirror editor that is wrapped with the wrapper.
    • This event then dispatches the GO_CODEMIRROR_TEXT event on that div[data-editor-wrapper].
  • GET_LEXICAL_EDITORS_CODE
type GET_LEXICAL_EDITORS_CODE_Event = CustomEvent<>
    • This event is registered on the form#custom-html-form and on any element that was provided in the detail of the REGISTER_LEXICAL_CODE_EDITOR custom event. It was created so that the user can edit custom HTML inside a lexical instance inside a HtmlNode.
    • The event looks for a div[id^="html_wrapper"] inside or on the element that it was dispatched. If this element exists, then we get the code inside that editor, and dispatch a GOT_LEXICAL_EDITORS_CODE event on that editor.
  • INSERT_LEXICAL_CODE_EDITORS
type INSERT_LEXICAL_CODE_EDITORS_Event = CustomEvent<{ detail: HTMLElement }>
    • This event looks for all div[data-codemirror] elements inside the element provided in the detail, and it registers those editors.
  • REMOVE_LEXICAL_CODE_EDITORS
type REMOVE_LEXICAL_CODE_EDITORS_Event = CustomEvent<{  }>
    • Unregisters the editor with id^="html_wrapper_" that is inside of element that dispatched the event.
    • The event should be dispatched on an element with a code mirror editor inside of it.
  • GOT_LEXICAL_EDITORS_CODE
type GOT_LEXICAL_EDITORS_CODE_Event = CustomEvent<{ html: string  }>
    • This event is dispatch on any element that listens for the GET_LEXICAL_EDITORS_CODE event.
    • It returns the html code inside the code mirror editor that has a wrapper with an id ^="html_wrapper".
  • GOT_CODEMIRROR_TEXT
type GOT_CODEMIRROR_TEXT_Event = CustomEvent<{ code: string }>
    • This event is dispatched on and should be listened to on the div[data-editor-wrapper] element. The event was created so that you can get the text of a code mirror editor without having to dynamically load the library.
    • It returns the code inside of the element with the id that was provided in the GET_CODEMIRROR_TEXT event.
    • It is called after the GET_CODEMIRROR_TEXT event.
  • OPEN_SNACKBAR
type GOT_CODEMIRROR_TEXT_Event = CustomEvent<{ selem: string }>
    • This event is dispatched on the document
    • You should pass in the snackbar selector string to the detail of the event.
    • The purpose of this custom event is to open a snackbar that matches the selector string.

Comments

You must be logged in to post a comment!

Insert Math Markup

ESC
About Inserting Math Content
Display Style:

Embed News Content

ESC
About Embedding News Content

Embed Youtube Video

ESC
Embedding Youtube Videos

Embed TikTok Video

ESC
Embedding TikTok Videos

Embed X Post

ESC
Embedding X Posts

Embed Instagram Post

ESC
Embedding Instagram Posts

Insert Details Element

ESC

Example Output:

Summary Title
You will be able to insert content here after confirming the title of the <details> element.

Insert Table

ESC
Customization
Align:
Preview:

Insert Horizontal Rule

#000000

Preview:


Insert Chart

ESC

View Content At Different Sizes

ESC

Edit Style of Block Nodes

ESC

Edit the background color, default text color, margin, padding, and border of block nodes. Editable block nodes include paragraphs, headers, and lists.

#ffffff
#000000

Edit Selected Cells

Change the background color, vertical align, and borders of the cells in the current selection.

#ffffff
Vertical Align:
Border
#000000
Border Style:

Edit Table

ESC
Customization:
Align:

Upload Lexical State

ESC

Upload a .lexical file. If the file type matches the type of the current editor, then a preview will be shown below the file input.

Upload 3D Object

ESC

Upload Jupyter Notebook

ESC

Upload a Jupyter notebook and embed the resulting HTML in the text editor.

Insert Custom HTML

ESC

Edit Image Background Color

ESC
#ffffff

Insert Columns Layout

ESC
Column Type:

Select Code Language

ESC
Select Coding Language