Skip to content

Simplify bootstrapDelegationHandler using Element.closest #41116

@frkly

Description

@frkly

Prerequisites

Proposal

Hello Bootstrap team,

I have a suggestion to simplify and potentially optimize the bootstrapDelegationHandler function in event-handler.js.

Current Behavior

function bootstrapDelegationHandler(element, selector, fn) {
return function handler(event) {
const domElements = element.querySelectorAll(selector)
for (let { target } = event; target && target !== this; target = target.parentNode) {
for (const domElement of domElements) {
if (domElement !== target) {
continue
}
hydrateObj(event, { delegateTarget: target })
if (handler.oneOff) {
EventHandler.off(element, event.type, selector, fn)
}
return fn.apply(target, [event])
}
}
}
}

The bootstrapDelegationHandler function is invoked when the third argument of the EventHandler.on method, handler, is a selector string. It is widely used to detect components like carousel, collapse, dropdown, modal, offcanvas, and tab based on their data attributes. In these cases, the first argument of bootstrapDelegationHandler, element, is typically set to document.

For example:

EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {

As a result, Bootstrap executes multiple DOM selector queries against the entire DOM tree every time an event is triggered to check if the event is related to a specific component. For instance, with click events in the default bundle (loading all components), this can result in 11 DOM queries, causing noticeable delays depending on the DOM tree size and the user's device.

Motivation and context

Proposed Implementation

I suggest replacing the current DOM traversal logic with the more efficient Element.closest method, which simplifies the implementation and improves performance. Here's the proposed code:

function bootstrapDelegationHandler(element, selector, fn) {
  return function handler(event) {
    const target = event.target.closest(selector);

    if (target && element.contains(target)) {
      hydrateObj(event, { delegateTarget: target });

      if (handler.oneOff) {
        EventHandler.off(element, event.type, selector, fn);
      }

      return fn.apply(target, [event]);
    }
  };
}

The Element.closest method is widely supported in modern browsers, aligning with Bootstrap's browser compatibility requirements.
https://caniuse.com/element-closest

If there are any potential side effects or edge cases I might have overlooked, I would greatly appreciate your feedback.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions