Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow for filtering events in "once" handlers #990

Closed
Jamesernator opened this issue Jun 12, 2021 · 3 comments
Closed

Allow for filtering events in "once" handlers #990

Jamesernator opened this issue Jun 12, 2021 · 3 comments
Labels
addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest topic: events

Comments

@Jamesernator
Copy link

Currently when listening for events one may wish to filter events for certain criterion.

This is doable today by :

someElement.addEventListener("keypress", (event) => {
    if (event.key !== "Escape") { // Only escape key presses
        return;
    }
    // Process the event
});

However the problem with explicit checks is it doesn't compose whatsoever with the once option that can be passed to .addEventListener.

e.g. Consider the following example with AbortController:

const abortController = new AbortController();
const { signal: abortSignal } = abortController;

abortSignal.addEventListener("abort", (event) => {
    if (!event.isTrusted) { 
        // Not a real abort, we can't trust this to update our internal state or whatever
        return;
    }
    console.log(".abort() was called"); // This never happens
}, { once: true });

abortSignal.dispatchEvent(new Event("abort"));

abortController.abort(); // Nothing happens

Now it's possible to work around this by essentially reimplementing "once", but this seems rather redundant when the "once" option already exists.

As such I'd like to suggest that a way to filter events be added, such that failure in the filter does not prevent a "once" handler from running.

As some examples of where this would be useful:

  • Ignoring fake events (i.e. ones without .isTrusted) for robustness in library code where users may be using synthetic events for triggering their own listeners
  • Capturing an event the first time something happens, e.g. the first time the cursor is some distance from some initial point
  • Ignoring events of certain kinds when multiple different kinds come under the same event type, e.g. capturing the next time the escape key is pressed (rather than any other key)

I'm not sure exactly what the API shape would be, but I imagine passing a filter function in the options bag:

someElement.addEventListener("keypress", (event) => {
    hideTheModal();
}, {
    once: true,
    eventFilter: (event) => event.key === "Escape",
});

It's kinda redundant if you're not using once: true (as you can just do if (!condition) return), in principle it could just replace the once e.g. once: (event) => event.key === "Escape", but it also seems kinda weird to overload the option (although also maybe not).

@annevk annevk added addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest labels Jun 14, 2021
@annevk
Copy link
Member

annevk commented Jun 14, 2021

Is this common functionality in libraries or frequently requested on Stack Overflow (or equivalent)?

It's also somewhat easy to implement using abort signals, albeit not as elegant. Perhaps the various shorthands proposed for abort signals could help with this. cc @jakearchibald

@jakearchibald
Copy link
Collaborator

Example using abort controllers:

const controller = new AbortController();
const { signal } = controller;

someElement.addEventListener('keypress', (event) => {
  if (event.key !== 'Escape') return;
  controller.abort();
  // Process the event
}, { signal });

I don't think any of the proposed shorthands help here.

I agree that once isn't particularly useful. eventFilter is interesting and makes it a bit more useful, but it doesn't help the case where you're waiting for one of multiple events:

const controller = new AbortController();
const { signal } = controller;

thing.addEventListener('load', (event) => {
  controller.abort();
  // …
}, { signal });

thing.addEventListener('error', (event) => {
  controller.abort();
  // …
}, { signal });

@Jamesernator
Copy link
Author

This might be solvable better with compositional tools for events such as this proposal as yeah it doesn't really extend to multiple events well, but compositional tools (e.g. Observable) extend to many patterns rather than trying to work around these event issues on a case-by-case basis.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest topic: events
Development

No branches or pull requests

3 participants