0

I hate jQuery. Unfortunately, it's used a lot in one of my work's largest projects and I cringe every time I have to deal with it. I'm currently in the long overdue process of upgrading a lot of the JavaScript (and migrating to TypeScript) and there's one piece of jQuery I'm not so sure how to best replace.

The method in question would be jQuery's on(events [, selector] [, data] [, handler ]) method. It allows you to create event listeners for a selector, before any elements matching the selector have been added to the DOM. As far as jQuery methods go, this one isn't bad.

To replace it, my first thought would be to use something like this:

function on(event, selector, handler) {
    addEventListener(event, ev => {
        if (ev.target.matches(selector)) {
            handler()
        }
    }
}

That doesn't look too efficient in my opinion and I'm unsure if it's the best way to replace the method. Am I correct in my thoughts and if so, what would be the best way to replace the method?

Spedwards
  • 4,167
  • 16
  • 49
  • 106
  • First thing that I notice: the functioning of jQuery's `on` depends on the `this` object (the jQuery object on which `on` is called). I don't see this covered in your attempt. – trincot May 18 '22 at 11:21
  • @trincot Sorry, I should have been more precise in my question. If I was to actually implement what I wrote, I would of course bind it to the targeted element so `this` could be used within the handler. I'm not trying to replicate jQuery, but find the best way to replace it without impacting too much. – Spedwards May 18 '22 at 11:23
  • It goes further than you say. `addEventListener` should be called on the target element, not (necessarily) on the `window` object. You would need to translate whatever is selected as jQuery object to an equivalent implementation (an argument? Or are you going to use `call(thisArg, ...)`? – trincot May 18 '22 at 11:26
  • @trincot I would have used `handler.bind(ev.target, ev)` more than likely. Bind it to the target and pass through the event. – Spedwards May 18 '22 at 11:28
  • I think you miss my previous point. You cover the handler -- not how you call `addEventListener`. – trincot May 18 '22 at 11:30
  • The way our code currently calls `on` is attached to the `document`. (`$(document).on(...)`) I could call `addEventListener` from the document instead of the window instead. Not a huge difference. If that doesn't answer your point, I'm not following. In my implementation I could add a target arg but it's fine to assume document for now I think. – Spedwards May 18 '22 at 11:37
  • 1
    For what it is worth, this is the [implementation of `on` from jQuery](https://github.com/jquery/jquery/blob/main/src/event.js#L34). It uses other jQuery stuff, so it can't be adopted directly, but it might be useful to get an idea of how it woks. – Rashad Saleh May 18 '22 at 11:39
  • You should probably limit the scope. Make an analysis of how jQuery's `on` is called by your current code, so you can exclude some features (such as namespacing, `events` being an object, call chaining, use of `data` argument, ...etc, ...etc). If you can filter it down to some specific uses, you may have a chance to not rewrite the full jQuery implementation of that method (BTW: you can check the jQuery implementation). – trincot May 18 '22 at 11:43

1 Answers1

1

matches will not work in all cases. For example, consider the following snippet. Clicking on the span element within the button will not trigger the handler.

function on(event, elem, selector, handler) {
    elem.addEventListener(event, ev => {
        if (ev.target.matches(selector)) {
            handler()
        }
    })
}
on('click', document.querySelector('#test'), 'button', () => console.log('Clicked'));
<div id="test">
    <button>Click Me <i class="icon icon-something">&rarr;</i></button>
</div>

This can be fixed by replacing matches with closest. I have also updated the handler invocation to set this to the matched target element.

function on(event, elem, selector, handler) {
    elem.addEventListener(event, ev => {
        const target = ev.target.closest(selector);
        if (target) {
            handler.apply(target, arguments)
        }
    })
}
on('click', document.querySelector('#test'), 'button', function () { console.log('Clicked ' + this.tagName);});
<div id="test">
    <button>Click Me <i class="icon icon-something">&rarr;</i></button>
</div>
Joyce Babu
  • 19,602
  • 13
  • 62
  • 97