简体   繁体   中英

Javascript equivalent to $.on

As somebody who ( unfortunately ) learned more of jQuery than raw javascript I am just now taking the time to replace all of my code with raw javascript . No, it's not needed, but it's an easier way for me to learn. A problem I am facing is converting all of my $(document).on with raw javascript . My website is a "single-page application" and most of my actual HTML is in different files which are called via Ajax requests. So, my question is, how would I look for an event fired from dynamically loaded content? I am assuming I would have to add an onclick event to them, but how is it that jQuery does it without needing an onclick event ?

Binding handlers in native API is done using addEventListener() .

To emulate jQuery's event delegation, you could fairly easily create a system that uses the .matches() method to test the selector you give.

function delegate(el, evt, sel, handler) {
    el.addEventListener(evt, function(event) {
        var t = event.target;
        while (t && t !== this) {
            if (t.matches(sel)) {
                handler.call(t, event);
            }
            t = t.parentNode;
        }
    });
}

There are probably some tweaks to be made, but basically it's a function that takes the element to bind to, like document , the event type, a selector and the handler.

It starts on the e.target and traverses up the parents until it gets to the bound element. Each time, it checks to see if the current element matches the selector, and if so, it invokes the handler.

So you'd call it like this:

delegate(document, "click", ".some_elem", function(event) {
    this.style.border = "2px dashed orange";
});

Here's a live demo that also adds dynamic elements to show that new elements are picked up as well.

 function delegate(el, evt, sel, handler) { el.addEventListener(evt, function(event) { var t = event.target; while (t && t !== this) { if (t.matches(sel)) { handler.call(t, event); } t = t.parentNode; } }); } delegate(document, "click", ".some_elem", function(event) { this.parentNode.appendChild(this.cloneNode(true)); this.style.border = "2px dashed orange"; }); 
 <div> <p class="some_elem"> <span> CLICK ME </span> </p> </div> 


Here's a shim to add a bit more support for .matches() .

if (!Element.prototype.matches) {
  Element.prototype.matches = 
    Element.prototype.matchesSelector || 
    Element.prototype.webkitMatchesSelector ||
    Element.prototype.mozMatchesSelector ||
    Element.prototype.msMatchesSelector || 
    Element.prototype.oMatchesSelector || 
    function(s) {
        var matches = (this.document || this.ownerDocument).querySelectorAll(s),
            i = matches.length;
        while (--i >= 0 && matches.item(i) !== this) {}
        return i > -1;            
    };
}

Here is a javascript equivalent to on()

jQuery

$(document).on('click', '#my-id', callback);

function callback(){
   ...handler code here
}

Javascript

document.addEventListener('click', function(event) {
    if (event.target.id == 'my-id') {
      callback();
    }
});
function callback(){
   ...handler code here
}

With this approach, the idea is to make use of event.target . Of course, as the selector changes, your code will have to get more involved

In modern browsers, you can use Element.closest() to simplify replication of jQuery's .on() method as well as ensure that you capture event bubbling from children of the targeted element (a nuance that some other implementations overlook). Older browsers, including IE, would require a polyfill for this to work.

 const on = (element, event, selector, handler) => { element.addEventListener(event, e => { if (e.target.closest(selector)) { handler(e); } }); } on(document, 'click', '#test', e => { console.log('click'); }); 
 <button id="test"> Clickable <i>Also Clickable</i> </button> 

Another approach for modern browsers would be something like this:

 const on = (selector, event, handler, element=document) => { element.addEventListener(event, (e) => { if(e.target.matches(selector)) handler(e); }); }; // click will work for each selector on('[type="button"], .test, #test','click', e => { alert(e.target.innerHTML); }); // click event will work for nested .test3 element only on('.test3','click', e => { alert(e.target.innerHTML); },document.querySelector('.test2')); 
 <div id="test"> test </div> <div class="test"> test 1 </div> <div class="test"> test 2 </div> <button type="button"> go </button> <div class="test3"> test 3 outer </div> <div class="test2"> <div class="test3"> test 3 inner </div> test 2 </div> 

I would offer a very small improvement over the fantastic accepted answer :

function add_event(el, name, callback, selector) {
    if (selector === undefined) {
        el.addEventListener(name, callback);
    }
    else {
        el.addEventListener(name, function(event) {
            var t = event.target;
            while (t && t !== this) {
                if (t.matches(selector)) {
                    callback.call(t, event);
                }
                t = t.parentNode;
            }
        });
    }
}

By switching the last 2 parameters around, you can recover the default addEventListener behavior when you leave out the selector.

I want to add and suggest a simple jQuery like on method: It's similar to @benvc answer with an improvement which is binding the handler to this pointing to the attached element. The solution uses Element.closest which is widely supported. If you want to support older browsers you can add a polyfill

//The on() function:
const on = (ele, type, selector, handler) => {
    ele.addEventListener(type, (event) => {
        let el = event.target.closest(selector);
        if (el) handler.call(el, event); //The element is bind to this
    });
};


//Example:
on(document, 'click', '.any-selector', function(event) {
     console.log(this, event.target);
     // this         -> The .any-selector element.
     // event.target -> The firing element a descendent of any-selector
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM