when()
The when() helper simplifies the use of event listeners both inside and outside Yozo components.
Syntax
when(...targets).does(eventType);
when(...targets).does(eventType, options);
when(...targets)[readableType]();
when(...targets)[readableType](options);
Parameters
- ...targets
- One or more EventTarget objects to attach a listener to.
- eventType
- A string representing the event type to listen to. Equivalent to the first argument to .addEventListener().
- options optional
- An options object, which is passed as third argument to .addEventListener(). The options once and signal are not recommended, as more appropriate alternatives are available (specifically,
flow.once()
and flow.until()
or flow.stop()
respectively).
- readableType
- Similar to eventType, but with an extra rule for readability; a single trailing "s" will be stripped off from readableType to find the event type. This allows for much more English-sounding expressions such as when(button).clicks() or when(image).loads().
Return value
A Flow
object that triggers whenever the event triggers on one of the targets. The dispatched event object is passed to the callbacks in the flow. To attach a simple listener like with .addEventListener(), use the flow.then()
method.
Examples
Basic usage
Attaching simple listeners with when() is similar in shape to .addEventListener(), just a tad more ergonomic and readable:
when(input).inputs().then(event => {
console.log(event.key);
});
// which is (almost) identical to...
input.addEventListener('input', event => {
console.log(event.key);
});
There are a variety of advantages to using when() over .addEventListener(), some of which more prominant than others. The main advantage is that the calls (since they create a Flow object) are monitored, which means the listeners are automatically taken down in certain contexts. For example, when writing a listener inside of the connected()
callback of a component, the hook is monitoring the listeners created inside it and takes them down when the component disconnects. Without when(), we'd need to manually take down the event listeners when the component disconnects. Another example of the monitoring is inside an effect() (which re-runs when any of its live dependencies change):
effect(() => {
const button = $.activeButton;
when(button).clicks().then(() => {
// do the thing…
});
});
In short, it means we can stop worrying about excess event listeners that we forgot to take down, and we can focus on describing what our code does in a simple, concise manner.
It flows
As described above, in monitored contexts, we generally don't need to worry about taking down event listeners ourselves. However, that's not the only case where when() provides an ergonomic developer experience; since it returns a flow, we get all the handy-dandy methods that come with flows. For example:
// debouncing events
when(input).inputs().debounce(300).then(() => {
// do heavy operation…
});
// combining event listeners
when(button).mousedowns().or(
when(button).touchstarts()).then(() => {
// button active…
});
// stopping one event based on another
when(box).pointerdowns().then(() => {
when(document).pointermoves().then(() => {
// render dragging…
}).until(when(document).pointerups());
});
// awaiting events
await when(img).loads().once()
.after(() => img.src = '/cat.jpg');
For more information about flows and what you can do with them, see flows.
The "s" thing
When using when()'s shorthand, a single "s" is stripped off the event name provided if there is one. To help understanding, here are some examples:
- when(window).scrolls() listens for the 'scroll' event;
- when(audio).pauses() listens for the 'pause' event;
- when(...inputs).change() listens for the 'change' event;
- when(window).appinstalled() listens for the 'appinstalled' event;
- when(document).DOMContentLoaded() listens for the 'DOMContentLoaded' event;
- when(fileReader).focus() listens for the 'focu' event (probably unintentional!)
- when(media).progress() listens for the 'progres' event (also likely unintentional!)
As demonstrated above, in some cases, an extra "s" makes for a very English-sounding expression. In other cases, the event name without the "s" looks a bit better. For almost all native events, this is merely a choice to make. For a handful of events (i.e. the ones that already end in an "s"), the shorthand does not work well; most notably focus, keypress, progress and success (and some variations such as vrdisplayfocus). For these events, use the longhand like when(…).does('focus').
If the shorthand feels weird, keep in mind that .does() is always an option; it provides a more familiar string-based interface while retaining all the benefits that come with when().
Usage notes
To avoid monitoring, use monitor.ignore()
either around the complete when(…).does(…) (or the shorthand equivalent) and optionally also around the .then() or other methods. In particular, then when() function itself returns a "magic" object (i.e. Proxy) that allows for the shorthand, and so does not by itself create the flow object, which is the part being monitored.
Also, note that the combination of the shorthand and the existance of .does() means that it is not possible to listen to the 'doe' event using the shorthand. Similarly, it is not possible to listen to the observe event using the shorthand, as when().observes()
is reserved for observers. Luckily, these are not native events, so it is rather rare that they need to be listened to. If needed, use .does('doe') or .does('observe') respectively.
See also