npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@mangoweb/delegated-events

v2.0.0

Published

A small, fast delegated event library.

Downloads

96

Readme

Delegated event listeners

A small, fast delegated event library for JavaScript.

Usage

import {on, off, fire} from 'delegated-events';

// Listen for browser-generated events.
on(document, 'click', '.js-button', function(event) {
  console.log('clicked', this);
});

// Listen for custom events triggered by your app.
on(document, 'robot:singularity', '.js-robot-image', function(event) {
  console.log('robot', event.detail.name, this.src);
});

// Dispatch a custom event on an element.
var image = document.querySelector('.js-robot-image');
fire(image, 'robot:singularity', {name: 'Hubot'});

// Stop listening for event type, selector and handler.
off(document, 'click', '.js-button', clickHandler);

// Stop listening for event type and selector.
off(document, 'robot:singularity', '.js-robot-image');

// Stop listening for event type for all selectors.
off(document, 'robot:singularity');

Directly-bound events

The standard method of registering event handler functions is to directly bind the listener to the element.

// Find an element and bind a function directly to it.
var button = document.querySelector('.js-button');
button.addEventListener('click', function(event) {
  console.log('clicked', event.target);
});

If we have several clickable elements, listeners can be directly registered on them in a loop.

// Find all the buttons and attach a function to each of them.
var buttons = document.querySelectorAll('.js-button');
buttons.forEach(function(button) {
  button.addEventListener('click', function(event) {
    console.log('clicked', event.target);
  });
});

Directly binding event listeners to elements works great if the page doesn't change after it's initially loaded. However, if we dynamically add another button to the document, it won't receive a click event.

// No click handler is registered on the new element.
var button = document.createElement('button');
button.textContent = 'Push';

var list = document.querySelector('.js-button-list');
list.appendChild(button);

Delegated events

A solution to this is to delegate the event handler up the tree to the parent element that contains all of the button children. When a button is clicked, the event bubbles up the tree until it reaches the parent, at which point the handler is invoked.

// Event handling delegated to a parent element.
var list = document.querySelector('.js-button-list');
list.addEventListener('click', function(event) {
  console.log('clicked', event.target);
});

However, now anything clicked inside the list will trigger this event handler, not just clicks on buttons. So we add a selector check to determine if a button generated the click event, rather than a span element, text, etc.

// Filter events by matching the element with a selector.
var list = document.querySelector('.js-button-list');
list.addEventListener('click', function(event) {
  if (event.target.matches('.js-button')) {
    console.log('clicked', event.target);
  }
});

Now we have something that works for any button element inside the list whether it was included in the initial HTML page or added dynamically to the document sometime later.

But what if the list element is added to the page dynamically?

If we delegate most events up to the global document, we no longer worry about when elements are appended to the page—they will receive event listeners automatically.

// Delegated click handling up to global document.
document.addEventListener('click', function(event) {
  if (event.target.matches('.js-button')) {
    console.log('clicked', event.target);
  }
});

Globally delegated events

Now that we've covered how browsers handle directly-bound and delegated events natively, let's look at what this library actually does.

The goals of this library are to:

  1. Provide shortcuts that make this common delegation pattern easy to use in web applications with hundreds of event listeners.
  2. Use the browser's native event system.
  3. Speed :racehorse:

Shortcuts

Delegated event handling shortcuts (on, off, fire) are provided so event handlers aren't required to test for matching elements themselves. jQuery has great documentation on event delegation with selectors too.

Here's the same globally delegated handler as above but using on.

// Easy :)
on(document, 'click', '.js-button', function(event) {
  console.log('clicked', event.target);
});

Native events

To provide compatibility with older browsers, jQuery uses "synthetic" events. jQuery listens for the native browser event, wraps it inside a new event object, and proxies all function calls, with modifications, through to the native object.

All browsers now share a standard event system, so we can remove the extra layer of event handling to recover performance.

Performance

The delegated event system is written in vanilla JavaScript, so it won't significantly increase download times (minified + gzip = 640 bytes). It relies on a small SelectorSet data structure to optimize selector matching against the delegated events.

A micro-benchmark to compare relative event handling performance is included and can be run with npm run bench.

Triggering custom events

A fire shortcut function is provided to trigger custom events with attached data objects.

on(document, 'validation:success', '.js-comment-input', function(event) {
  console.log('succeeded for', event.detail.login);
});

var input = document.querySelector('.js-comment-input');
fire(input, 'validation:success', {login: 'hubot'});

The standard way of doing this works well but is more verbose.

document.addEventListener('validation:success', function(event) {
  if (event.target.matches('.js-comment-input')) {
    console.log('succeeded for', event.detail.login);
  }
});

var input = document.querySelector('.js-comment-input');
input.dispatchEvent(
  new CustomEvent('validation:success', {
    bubbles: true,
    cancelable: true,
    detail: {login: 'hubot'}
  })
);

Browser support

  • Chrome
  • Firefox
  • Safari 6+
  • Internet Explorer 9+
  • Microsoft Edge

Internet Explorer requires polyfills for CustomEvent and WeakMap.

Development

npm run bootstrap
npm test
npm run bench
npm run browser

License

Distributed under the MIT license. See LICENSE for details.