{"id":13447122,"url":"https://github.com/dgraham/delegated-events","last_synced_at":"2025-05-15T10:00:26.434Z","repository":{"id":35525778,"uuid":"39796502","full_name":"dgraham/delegated-events","owner":"dgraham","description":"A small, fast delegated event library for JavaScript.","archived":false,"fork":false,"pushed_at":"2022-05-16T13:46:11.000Z","size":752,"stargazers_count":1010,"open_issues_count":2,"forks_count":52,"subscribers_count":22,"default_branch":"master","last_synced_at":"2025-04-14T16:53:40.333Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dgraham.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-07-27T20:33:33.000Z","updated_at":"2025-04-01T03:57:22.000Z","dependencies_parsed_at":"2022-08-30T15:31:43.967Z","dependency_job_id":null,"html_url":"https://github.com/dgraham/delegated-events","commit_stats":null,"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dgraham%2Fdelegated-events","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dgraham%2Fdelegated-events/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dgraham%2Fdelegated-events/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dgraham%2Fdelegated-events/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dgraham","download_url":"https://codeload.github.com/dgraham/delegated-events/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254319715,"owners_count":22051072,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T05:01:08.812Z","updated_at":"2025-05-15T10:00:26.090Z","avatar_url":"https://github.com/dgraham.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# Delegated event listeners\n\nA small, fast delegated event library for JavaScript.\n\n## Usage\n\n```js\nimport {on, off, fire} from 'delegated-events';\n\n// Listen for browser-generated events.\non('click', '.js-button', function(event) {\n  console.log('clicked', this);\n});\n\n// Listen for custom events triggered by your app.\non('robot:singularity', '.js-robot-image', function(event) {\n  console.log('robot', event.detail.name, this.src);\n});\n\n// Dispatch a custom event on an element.\nvar image = document.querySelector('.js-robot-image');\nfire(image, 'robot:singularity', {name: 'Hubot'});\n```\n\n## Directly-bound events\n\nThe standard method of registering event handler functions is to directly bind\nthe listener to the element.\n\n```js\n// Find an element and bind a function directly to it.\nvar button = document.querySelector('.js-button');\nbutton.addEventListener('click', function(event) {\n  console.log('clicked', event.target);\n});\n```\n\nIf we have several clickable elements, listeners can be directly registered\non them in a loop.\n\n```js\n// Find all the buttons and attach a function to each of them.\nvar buttons = document.querySelectorAll('.js-button');\nbuttons.forEach(function(button) {\n  button.addEventListener('click', function(event) {\n    console.log('clicked', event.target);\n  });\n});\n```\n\nDirectly binding event listeners to elements works great if the page doesn't\nchange after it's initially loaded. However, if we dynamically add another\nbutton to the document, it won't receive a click event.\n\n```js\n// No click handler is registered on the new element.\nvar button = document.createElement('button');\nbutton.textContent = 'Push';\n\nvar list = document.querySelector('.js-button-list');\nlist.appendChild(button);\n```\n\n## Delegated events\n\nA solution to this is to *delegate* the event handler up the tree to the parent\nelement that contains all of the button children. When a button is clicked, the\nevent bubbles up the tree until it reaches the parent, at which point\nthe handler is invoked.\n\n```js\n// Event handling delegated to a parent element.\nvar list = document.querySelector('.js-button-list');\nlist.addEventListener('click', function(event) {\n  console.log('clicked', event.target);\n});\n```\n\nHowever, now *anything* clicked inside the list will trigger this event\nhandler, not just clicks on buttons. So we add a selector check to determine\nif a `button` generated the click event, rather than a `span` element, text, etc.\n\n```js\n// Filter events by matching the element with a selector.\nvar list = document.querySelector('.js-button-list');\nlist.addEventListener('click', function(event) {\n  if (event.target.matches('.js-button')) {\n    console.log('clicked', event.target);\n  }\n});\n```\n\nNow we have something that works for any button element inside the list\nwhether it was included in the initial HTML page or added dynamically to the\ndocument sometime later.\n\nBut what if the list element is added to the page dynamically?\n\nIf we delegate *most events* up to the global `document`, we no longer worry\nabout when elements are appended to the page—they will receive event listeners\nautomatically.\n\n```js\n// Delegated click handling up to global document.\ndocument.addEventListener('click', function(event) {\n  if (event.target.matches('.js-button')) {\n    console.log('clicked', event.target);\n  }\n});\n```\n\n## Globally delegated events\n\nNow that we've covered how browsers handle directly-bound and delegated events\nnatively, let's look at what this library actually does.\n\nThe goals of this library are to:\n\n1. Provide shortcuts that make this common delegation pattern easy to use\n   in web applications with hundreds of event listeners.\n2. Use the browser's native event system.\n3. Speed :racehorse:\n\n### Shortcuts\n\nDelegated event handling shortcuts (`on`, `off`, `fire`) are provided\nso event handlers aren't required to test for matching elements\nthemselves. jQuery has great documentation on [event delegation with selectors][jq] too.\n\n[jq]: http://api.jquery.com/on/\n\nHere's the same globally delegated handler as above but using `on`.\n\n```js\n// Easy :)\non('click', '.js-button', function(event) {\n  console.log('clicked', event.target);\n});\n```\n\n### Native events\n\nTo provide compatibility with older browsers, jQuery uses \"synthetic\" events.\njQuery listens for the native browser event, wraps it inside a new event\nobject, and proxies all function calls, with modifications, through to the\nnative object.\n\nAll browsers now share a standard event system, so we can remove the extra\nlayer of event handling to recover performance.\n\n### Performance\n\nThe delegated event system is written in [vanilla JavaScript](delegated-events.js),\nso it won't significantly increase download times (minified + gzip = 640 bytes).\nIt relies on a small [`SelectorSet`](https://github.com/josh/selector-set)\ndata structure to optimize selector matching against the delegated events.\n\nA micro-benchmark to compare relative event handling performance is included\nand can be run with `npm run bench`.\n\n## Triggering custom events\n\nA `fire` shortcut function is provided to trigger custom events with\nattached data objects.\n\n```js\non('validation:success', '.js-comment-input', function(event) {\n  console.log('succeeded for', event.detail.login);\n});\n\nvar input = document.querySelector('.js-comment-input');\nfire(input, 'validation:success', {login: 'hubot'});\n```\n\nThe standard way of doing this works well but is more verbose.\n\n```js\ndocument.addEventListener('validation:success', function(event) {\n  if (event.target.matches('.js-comment-input')) {\n    console.log('succeeded for', event.detail.login);\n  }\n});\n\nvar input = document.querySelector('.js-comment-input');\ninput.dispatchEvent(\n  new CustomEvent('validation:success', {\n    bubbles: true,\n    cancelable: true,\n    detail: {login: 'hubot'}\n  })\n);\n```\n\n## Adding TypeScript typed events\n\nIf you're using TypeScript, you can maintain a list of custom event names that map to their specific types, making it easier to write type-safe code while using delegated events. Add the following snippet to a `.d.ts` file in your local project and alter the contents of `CustomEventMap` to list all the well-known events in your project:\n\n```typescript\n// events.d.ts\ninterface CustomEventMap {\n  'my-event:foo': {\n    something: boolean\n  }\n  // When adding a new custom event to your code, add the event.name + event.detail type to this map!\n}\n\n// Do not change code below this line!\ntype CustomDelegatedEventListener\u003cT\u003e = (this: Element, ev: CustomEvent\u003cT\u003e \u0026 {currentTarget: Element}) =\u003e any\n\ndeclare module 'delegated-events' {\n  export function fire\u003cK extends keyof CustomEventMap\u003e(target: Element, name: K, detail: CustomEventMap[K]): boolean\n  export function on\u003cK extends keyof CustomEventMap\u003e(\n    name: K,\n    selector: string,\n    listener: CustomDelegatedEventListener\u003cCustomEventMap[K]\u003e\n  ): void\n}\n\ndeclare global {\n  interface Document {\n    addEventListener\u003cK extends keyof CustomEventMap\u003e(\n      type: K,\n      listener: (this: Document, ev: CustomEvent\u003cCustomEventMap[K]\u003e) =\u003e unknown,\n      options?: boolean | AddEventListenerOptions\n    ): void\n  }\n  interface HTMLElement {\n    addEventListener\u003cK extends keyof CustomEventMap\u003e(\n      type: K,\n      listener: (this: HTMLElement, ev: CustomEvent\u003cCustomEventMap[K]\u003e) =\u003e unknown,\n      options?: boolean | AddEventListenerOptions\n    ): void\n  }\n}\n```\n\nNow TypeScript is able to type-check your events in both `delegated-events` callsites and the standard `addEventListener` callsites:\n\n```typescript\nfire(document.body, 'my-event:foo', {something: true})\non('my-event:foo', 'body', event =\u003e {\n  event.detail.something // typescript knows this is a boolean\n})\ndocument.addEventListener('my-event:foo', event =\u003e {\n  event.detail.something // typescript knows this is a boolean\n})\ndocument.body.addEventListener('my-event:foo', event =\u003e {\n  event.detail.something // typescript knows this is a boolean\n})\n```\n\n\n## Browser support\n\n- Chrome\n- Firefox\n- Safari 6+\n- Internet Explorer 9+\n- Microsoft Edge\n\nInternet Explorer requires polyfills for [`CustomEvent`][custom-event]\nand [`WeakMap`][weakmap].\n\n[custom-event]: https://github.com/krambuhl/custom-event-polyfill\n[weakmap]: https://github.com/Polymer/WeakMap\n\n## Development\n\n```\nnpm run bootstrap\nnpm test\nnpm run bench\nnpm run browser\n```\n\n## License\n\nDistributed under the MIT license. See LICENSE for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdgraham%2Fdelegated-events","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdgraham%2Fdelegated-events","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdgraham%2Fdelegated-events/lists"}