Skip to content

Addons Events API #7946

@limzykenneth

Description

@limzykenneth

Increasing access

This feature enables addon libraries to define their own events and custom functions in the global scope (in global mode) to handle events. It opens up a new category of functionalities for addons in a semantic way.

Most appropriate sub-area of p5.js?

  • Accessibility
  • Color
  • Core/Environment/Rendering
  • Data
  • DOM
  • Events
  • Image
  • IO
  • Math
  • Typography
  • Utilities
  • WebGL
  • Build process
  • Unit testing
  • Internationalization
  • Friendly errors
  • Other (specify if possible)

Feature enhancement details

This only applies to 2.x.
As briefly alluded to in #7937, this is a proposal (with accompanying implementation) of an Events API for addons.

The problem

Currently there is no documented or easy way for an addon to add a new event listener function like the mouseClicked() function in a semantic way. Addons that aims to do that have gone with a couple different approach for example p5.joystick uses an object oriented approach more similar to the p5.Element's API instead; or an addon may implement access to global user defined function by accessing window directly, making the addon available in global mode only.

As such this proposal provides a way that fulfills the following requirements:

  1. Platform agnostic event handling that addons has control over (ie. no expectation that window.addEventlistener exist)
  2. Easy method of removing attached event listeners on sketch removal with minimal code in the addon
  3. Easy, semantic, and consistent access to user defined global functions such as mouseClicked() with single interface that works whether the sketch is in global mode or instance mode

Proposed solution

It is easier to think of the proposal here as two separate features that are related but can be used independently.

Events API

Per the first requirement above, this means that p5 will not provide any custom event handling API directly. One eventual goal of p5.js 2.0 is to make p5.js as platform agnostic as possible and to put it simply, enable p5 core to be imported and run in a non-browser environment, like Node.js for example. Because different JS runtime has slight difference in event handling (Node.js has no DOM to listen to events for example), p5 won't be able to provide an events API. It is better for the addon which has knowledge of its supported platform (browser or otherwise) to implement the supported events API directly.

This means for example the pointer events p5 is using to listen to mouse and touch events will have the following:

window.addEventListener('click', clickEventHandler, {
  passive: false
});

and the same that was in core will be refactored out.

For the second requirement, usually when a sketch is removed, all associated event listeners will also be detached to avoid potential memory leak or unintended behavior. As a consequence of not handling events directly in p5 core as per above, addons will need to remove event handlers themselves in the remove lifecycle hook. While one of the challenge is removeEventListener() of Web API expects the same reference to the handler function to be able to remove it, which can make the implementation a bit clunky because the addon author will now need to keep a reference to all event handler function as they are attached.

To make the process more streamline, the addEventListener() Web API has an option property of signal which expects an AbortController signal that when received, automatically clean up the event listener. p5 will implement a signal that will be triggered on sketch removal for this. This means that an addon author can just do the following:

window.addEventListener('click', clickEventHandler, {
  passive: false,
  signal: this._removeSignal
});

and no longer need to worry about cleaning up event listeners in the remove hook.

PS. The abort controller signal can be used as an event target to listen to the remove event if needed, although this is likely a bit more complicated than just using the remove hook so that is recommended instead.

User defined functions (Name WIP)

While the above provides the flexibility for addons to define their events listeners in whichever environment that made sense, it still needs access to user defined functions such as mouseClicked() is accessed by the click event listener in the pointer events implementation.

To provide uniform and semantic access to these regardless of global or instance mode sketch (per requirement 3 above), a new interface this.userDefinedFunctions is provided, an object whose keys can be used to access the functions, eg. this.userDefinedFunctions.mouseClicked will return the user defined function mouseClicked(){} in global mode or p.mouseClicked = function(){} in instance mode. The name userDefinedFunctions is a placeholder, I think this name is descriptive and accurate in what it does but potentially too verbose, any suggestion on better name for this welcomed.

This interface is implemented as a Proxy with return value memoized so each request to the same key will return the same function object. If a function with request name don't exist in the global object (in global mode) or the instance object (in instance mode), undefined will be returned.

Putting it all together, we have the following implementation for events:

window.addEventListener('click', this.userDefinedFunctions.mouseClicked.bind(this), {
  passive: false,
  signal: this._removeSignal
});

Do note that back earlier in this proposal, it was mentioned that this user defined function feature is best thought of as a separate thing to the Events API itself. This is because these functions don't have to be event handlers, they are just functions the user has written and your addon may choose to execute it in a different manner.


I have completed a proof of concept implementation of these ideas in pointer.js and will file a PR that aims to transition all core event listeners to use this API, also as a way to work out any potential missed problem.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    Completed

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions