Guide to creating JSDoc for a TouchEvent handler

Looking to improve my shorter-js codebase with JSDoc for TypeScript definitions, but hitting a roadblock.

I've implemented the on() function using Element.addEventListener, working well so far. However, when passing a TouchEvent as a parameter for an event handler, TypeScript throws a 4-line error as shown below:

/**
 * @param {HTMLElement | Element | Document} element event.target
 * @param {string} eventName event.type
 * @param {EventListener} handler callback
 * @param {EventListenerOptions | boolean | undefined} options other event options
 */
function on(element, eventName, handler, options) {
  const ops = options || false;
  element.addEventListener(eventName, handler, ops);
}

/**
 * test handler
 * @type {EventListener}
 * @param {TouchEvent} e
 */
function touchdownHandler(e){
  console.log(e.touches)
}

// test invocation
on(document.body, 'touchdown', touchdownHandler);
body {height: 100%}

The error is triggered by the

on(document.body, 'touchdown', touchdownHandler)
call and reads as follows:

Argument of type '(e: TouchEvent) => void' is not assignable to parameter of type 'EventListener'.
  Type '(e: TouchEvent) => void' is not assignable to type 'EventListener'.
    Types of parameters 'e' and 'evt' are incompatible.
      Type 'Event' is missing the following properties from type 'TouchEvent': altKey, changedTouches, ctrlKey, metaKey, and 7 more.

Even using

document.body.addEventListener(...)
leads to the same error. I have attempted different definitions in my index.d.ts file without success.

It seems like I need to define something in my index.d.ts file and then reference it in the JSDoc for the touchdownHandler. Any ideas?

Answer №1

The issue here is that when using addEventListener, you must provide the actual type of event in order to map the handler to accept that specific kind of event. The declaration file dom.d.ts from the DOM Library contains event maps for this purpose. Your objective should be to ensure that the eventName is mapped to the event type of the handler.

In TypeScript, we can utilize param types to achieve this without generics. However, with JSDoc, we need to introduce a template variable for the Event Type:

/**
 * @template {keyof HTMLElementEventMap} T
 * @param {HTMLElement} element
 * @param {T} eventName
*/

Although there are other event maps available, in most cases, this approach will suffice. If more specificity is required, refer to the source code of the dom.d.ts.

Next, it's essential to define the type of the handler:

/**
 * @template {keyof HTMLElementEventMap} T
 * @param {HTMLElement} element
 * @param {T} eventName
 * @param {(event: HTMLElementEventMap[T]) => void} handler
 */

For instance, if T is set to 'click', the handler will receive the event as HTMLElementEventMap['click'], which is equivalent to MouseEvent.

Lastly, let's discuss the options. In JSDoc, you can indicate a parameter as optional instead of explicitly mentioning undefined:

/**
 * @template {keyof HTMLElementEventMap} T
 * @param {HTMLElement} element
 * @param {T} eventName
 * @param {(event: HTMLElementEventMap[T]) => void} handler
 * @param {EventListenerOptions | boolean} [options]
 */

This method aligns with your expectations.

Full Code:

/**
 * @template {keyof HTMLElementEventMap} T
 * @param {HTMLElement} element
 * @param {T} eventName
 * @param {(event: HTMLElementEventMap[T]) => void} handler
 * @param {EventListenerOptions | boolean} [options]
 */
function on(element, eventName,  handler, options) {
  const ops = options || false;
  element.addEventListener(eventName, handler, ops);
}

/**
 * test handler
 * @param {TouchEvent} e
 */
function touchdownHandler(e) {
  console.log(e.touches)
}

// test invocation
on(document.body, 'touchend', touchdownHandler);

Sandbox

To declare this using Generics, include the following code snippet:

declare function on<T extends keyof HTMLElementEventMap>(element: HTMLElement, eventName: T, handler: (event: HTMLElementEventMap[T]) => void, options?: EventListenerOptions | boolean): void;

Answer №2

After already marking an answer, I have decided to share my own solution, which turned out to be simpler than I initially thought. This involves leveraging the EventListenerObject and its handleEvent method:

/**
 * Add eventListener to an `Element` | `HTMLElement` | `Document` | `Window` target.
 *
 * @param {HTMLElement | Element | Document | Window} element event.target
 * @param {string} eventName event.type
 * @param {EventListenerObject['handleEvent']} handler callback
 * @param {(EventListenerOptions | boolean)=} options other event options
 */
function on(element, eventName, handler, options) {
  const ops = options || false;
  element.addEventListener(eventName, handler, ops);
}

Now everything is functioning as expected.

on(document, 'load', (e) => console.log(e), false)

on(window, 'resize', (e) => console.log(e.type), false)

/**
 * test handler
 * @type {(event: Event) => void}
 * @param {TouchEvent} e
 */
 function touchdownHandler(e) {
  console.log(e.touches)
}

const div = document.createElement('div')
on(div, 'touchstart', touchdownHandler, false)

const main = document.createElement('main')
on(main, 'touchstart', touchdownHandler, false)

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Leveraging AngularJS and ng-map to incorporate interactive dynamic heatmap displays

Greetings everyone! I am completely new to frontend development and have embarked on my first journey with AngularJS. I must admit, it's quite challenging and I'm still trying to wrap my head around how it all works. Currently, I'm working o ...

Are there any possible resolutions to the issues plaguing Next.Js?

After switching from React to Next.JS, I've encountered some challenges that I'm hoping to find solutions for: In contrast to React, Next.JS doesn't allow me to change a specific part of a webpage and maintain a static element like a navb ...

When integrating the React custom hook setValue into another component, it appears to be returning an undefined

I have created a custom useLocalStorage hook. When I directly use it in my component and try to update the value, I encounter an error stating that setValue is not a function and is actually undefined. Here's the code snippet: // Link to the original ...

What is the best way to switch an element's attributes using only JavaScript?

As I crafted this inquiry, I noticed that most of the analogous questions were along the lines of this one (where the user seeks to toggle an element's class using pure JS) or that one (where the user wants to toggle other attributes with jQuery). My ...

Using Google App Script for Slides to center-align text within a text box

Currently, I am tackling a project that demands the center alignment of text within a textbox on a google slide using a google app script. The primary objective is to fetch text from a google worksheet and an image from google drive. Subsequently, combine ...

Encountering an "Undefined index" error when attempting to send a file and path using FormData through

I have a question about my code. I am trying to send a file and a path to the server. The path needs to be constructed using these variables so that I can use it to output the file later on. var FD = new FormData(); var MyString = "uploads/docs/KEP" + m ...

Why am I not receiving any results from the communication between JavaScript and PHP using an HTTP GET request?

Currently, I have a small JavaScript program running within an HTML5 canvas and included a HTTP GET request function in my JavaScript code. The function itself is functioning properly as I tested it with multiple examples from the internet, all of which wo ...

Javascript/Webpack/React: encountering issues with refs in a particular library

I've encountered a peculiar issue that I've narrowed down to the simplest possible scenario. To provide concrete evidence, I have put together a reproducible repository which you can access here: https://github.com/bmeg/webpack-react-test Here&a ...

Using jQuery's $.ajax() function to make an asynchronous request, and then passing the

I'm currently utilizing the jQuery $.ajax() function within a parent function that passes values into the ajax call. I am looking to have a custom callback function that can access the data parameter returned from the success function of the ajax call ...

Avoiding the use of destructuring for undefined values in JavaScript can be achieved by implementing

Upon receiving the response registryReportSettings from the server: this.getRegistrySettings(registry.Id).subscribe((registryReportSettings: { extended: ReportPropertiesRequest }) => { const { objectProperties, reportProperties, textProperties } = reg ...

Activate Keyboard and Background in the Bootstrap Modal

I have set up my modal to disable the escape key and backdrop by default. $(modal).modal({ backdrop: "static", keyboard: false }); However, at a later time, I want to enable them again. $(modal).modal({ backdrop: true, keyboard: true }); The is ...

What is the memory allocation for null values in arrays by node.js?

Continuing the discussion from this thread: Do lots of null values in an array pose any harm? I experimented with node.js by doing this: arr=[] arr[1000]=1 arr[1000000000]=2 arr.sort() However, I encountered the following error: FATAL ERROR: JS Alloca ...

Exploring the power of Blowfish encryption with Java and JavaScript

I am facing an issue where I need to encrypt data using Blowfish on a java-based server and send it to a client. Despite successfully encrypting the data, I am unable to decrypt it on the client side. Below is my Java code snippet: byte[] kd = key.getByt ...

Every time a row is selected, React and material-ui cause all TableRows to be re-rendered anew

Recently, I came across a code snippet that looks like this: <Table selectable onRowSelection={this.onRecordSelected} bodyStyle={tableBodyStyle}> <TableBody deselectOnClickaway={false} showRowHover displayRowCheckbox={false}> ...

Angular is reporting that the check-in component is nonexistent

I encountered an error in my Angular 8 application while working on a component. The error seems to be related to nested components within the main component. It appears that if the component is empty, the error will be shown, but if it's not null, th ...

A conditional type used with an array to return either an Error object or a generic type when the array is destructured

Within my Typescript project, I've implemented a Result type to be returned from functions, containing either an error or some data. This can take the form of [Error, null], or [null, Data]. Here's an example: type Result<Data> = [ Error | ...

The height of the browser action will not return to its original state

I'm currently working on an extension that provides responses based on user text input. However, I'm running into an issue where the height of the browser action won't reset properly. I've tried various methods to fix this problem, in ...

Enhancing your Selenium test case strategies for better performance

I've created a test case that compares two arrays, removing matching elements and throwing an exception for non-matching ones. Although it's functional, the test is quite long and messy. Can anyone suggest ways to optimize or improve it? System ...

How can Swipe support help you slide a menu back in?

For implementing swipe support on my landing page carousel, I included jquery.mobile.custom.min.js. However, I am facing a challenge with adding swipe support to "close" the menu. Essentially, swiping left should have the same effect as clicking the butto ...

What is the best way to retrieve the value of a JavaScript variable and display it within an HTML label tag

In my JavaScript code, I'm attempting to retrieve location coordinates using the onchange event and then display those coordinates in a label. I've tried alerting the coordinates successfully, but I'm struggling to update the label using doc ...