Native JS equivalent to jQuery delegation

后端 未结 5 1179
予麋鹿
予麋鹿 2020-12-03 08:02

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

相关标签:
5条回答
  • 2020-12-03 08:32

    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);
            }
        });
    };
    
    0 讨论(0)
  • 2020-12-03 08:37

    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);
      }
    });
    
    0 讨论(0)
  • 2020-12-03 08:38

    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;       
    
    });
    
    0 讨论(0)
  • 2020-12-03 08:53

    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.

    0 讨论(0)
  • 2020-12-03 08:53

    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>

    0 讨论(0)
提交回复
热议问题