Native JS equivalent to jQuery delegation
Asked Answered
A

7

20

What is the native implementation for event delegation on dynamically created dom elements?

I tried looking at the jQuery source but I can't follow the .on method.

Note: Currently I attach the event handlers after the dom elements are created, which seems pretty standard but I like the way jQuery .on handles dynamically created elements events with this syntax $( document ).on( "click", ".selector", handler );.

Asset answered 11/8, 2014 at 16:44 Comment(6)
Possible duplicate of #1687796Justus
Not a useful post already viewed that. It has outdated link references and no clear examples of what I'm trying to figure out. I know what event delegation is I want to know an example of how to do it nativelyAsset
On the contrary, the question is still relevant. The highest rated answer does give you an example. Could you clarify your question then? From the question I thought you wanted to know about event delegation using plain old JavaScript.Justus
Sorry I shouldn't say unuseful..it was useful. I was looking for more of a direct translation of the .on jquery method ( delegation protion ) to native if that exists in a "simple" form.Asset
@GregBurghardt: It's not a duplicate, because it doesn't ask for a native implementation, but it's a good introduction to understand what event delegation is about.Durwyn
Possible duplicate of Modify `target` of mouseEvent object for Event DelegationAdrastus
D
30

What happens is basically this:

// $(document).on("click", <selector>, handler)
document.addEventListener("click", function(e) {
    for (var target=e.target; target && target!=this; target=target.parentNode) {
    // loop parent nodes from the target to the delegation node
        if (target.matches(<selector>)) {
            handler.call(target, e);
            break;
        }
    }
}, false);

However, e.currentTarget is document when the handler is called, and e.stop[Immediate]Propagation() will work differently. jQuery abstracts over that (including call order) a lot.

I've used the .matches() method, which is not yet standard but already available under different names in modern browsers. You might use a custom predicate to test elements instead of a selector. And addEventListener is obviously not oldIE-compatible.

Durwyn answered 11/8, 2014 at 16:58 Comment(2)
So lets say I had a list (ul) and items (li) which are being added dynamically. I'd add an event listener to the ul using this approach and subsequent lis that were generated would run the handler?Asset
Yes, because it's not the <li> that runs the handler but the listener that you've attached to the <ul> - to which any event in the list bubbles.Durwyn
R
4

This should do it for you on a regular HTML element:

HTMLElement.prototype.on = function(event, selector, handler) {
    this.addEventListener(event, function(e) {
        let target = e.target;
        if (typeof(selector) === 'string') {
            while (!target.matches(selector) && target !== this) {
                target = target.parentElement;
            }

            if (target.matches(selector))
                handler.call(target, e);
        } else {
                selector.call(this, e);
        }
    });
};
Ret answered 5/10, 2017 at 22:31 Comment(2)
Do not change the prototype #smooshgate is a proof that it never ends well.Instruction
@Instruction Smooshgate is proof that you can change prototypes and if you're popular the rest of the world has to conform forever. It's 2018 and MooTools still controls JS function names. Seriously though: change prototypes in your own code, just be aware if what will happen in on get standardized later,Hovey
S
4

Event delegation can be achieved even without iterating through all ancestors of event target.

$(document).on("click", selector, handler);

can be written in NativeJS as:

document.addEventListener("click", event => {
  var el = document.querySelector(selector);
  if (el && el.contains(event.target)) {
    handler.call(el, event);
  }
});
Sev answered 12/1, 2019 at 7:17 Comment(2)
This doesn't work if selector should return more than one node e.g. is a class selector.Cabriole
@StephenBlair You are right. In that case, you may use document.querySelectorAll(selector).forEach.Sev
G
1

Felt like doing some code golfing ;)

108 bytes (based on @Bergi)

(e,d,g,h,b)=>e.addEventListener(d,c=>{for(d=e,b=c.target;b!=d;)b.matches(g)?h.call(d=b,c,b):b=b.parentNode})

Working demo:

window.$on = (e,d,g,h,b)=>e.addEventListener(d,c=>{for(d=e,b=c.target;b!=d;)b.matches(g)?h.call(d=b,c,b):b=b.parentNode})

$on(document.body, 'click', '.clickable', (evt, matched) => {
  console.log(matched)
})
<div class="wrapper">
  <div class="not-clickable rect">
    not clickable
  </div>
  <div class="clickable rect">
    clickable
  </div>
  <div class="clickable rect">
    clickable
    <div class="child">child element</div>
  </div>
</div>

<style>
.rect {
  width: 100px;
  height: 100px;
  background: red;
  border: 1px solid;
  margin: 10px;
  float: left;
}

.child {
  background: gray;
}
</style>
Guadalupe answered 26/9, 2020 at 15:49 Comment(0)
S
1

Instead of this JQuery

$(document).on('click', '.your-selector', function() {
    // do something
});

I use this often in pure javascript

document.addEventListener('click', function(e) {
    // e.target can be a sub element of the one we want
    const el = e.target ? e.target.closest('.your-selector') : null;
    if (el) {
        // do something
    }
});

Tip: "closest" in javascript includes the element itself in the first place, and then starts to go up in the DOM

Seersucker answered 18/3, 2022 at 14:14 Comment(0)
S
0

Pure javascript code;

document.addEventListener("click", function (event) {
  if (event.target.classList.contains("yourClass1")) {
    //do something
  }
  if (event.target.classList.contains("yourclass2")) {
    //do something
  }
  if (event.target.id == "yourId") {
     //do something
  }
});
Seam answered 30/9, 2023 at 19:7 Comment(0)
K
-1

For example, we have such an html structure.

<ul>
    <li>
        item 1
        <button class="btn-edit"><i class="fas fa-pencil"></i></button>
    </li>
    <li>
        item 3
        <button class="btn-edit"><i class="fas fa-pencil"></i></button>
    </li>
    <li>
        item 3
        <button class="btn-edit"><i class="fas fa-pencil"></i></button>
    </li>
</ul>

Note that there is an i tag inside the button.

If you want to capture exactly the right item for the Click event, you need a function like the one below.

function delegateClick(selector, callback){

    let selectorItems = document.querySelectorAll(selector);

    document.addEventListener('click', function(e){

        let clickedItems = e.path;

        for(let clickedItem of clickedItems){
            for(let selectorItem of selectorItems){
                if(clickedItem === selectorItem){

                    callback(clickedItem);

                }
            }
        }

    });
}

This offers a very similar use to jquery.

delegateClick('.btn-edit', (elm) => {

    //access the clicked DOM element
    elm;       

});
Karajan answered 20/11, 2019 at 5:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.