2

I'm building a JavaScript plugin which will bolt onto other websites.

The whole thing is written with pure JS but there's one bit I haven't been able to get away from jQuery with:

var key = "some_key";
var selector = "#my_input, input[name=my_input], .someInputs";
jQuery(document).on('change', selector, function() {
    doSomething(key, this.value);
});

The reason I want to avoid jQuery is that I expect this JS to be included in a wide range of sites, many of which won't have jQuery. Some will have other frameworks already installed such as Mootools, some will have old versions of jQuery where .on() isn't supported, etc.

That, and I am ideally trying to keep it very lightweight, so adding in jQuery just for this tiny task seems excessive.

AstroCB
  • 12,337
  • 20
  • 57
  • 73
Adam Nicholson
  • 320
  • 3
  • 10
  • 1
    https://developer.mozilla.org/en-US/docs/Web/API/EventTarget.addEventListener – mc01 Jul 28 '14 at 17:40
  • @mc01: Thanks for the comment - I'm aware of how to add event listeners with pure JS - should have mentioned that the part I was struggling with was mostly to do with finding elements matching the selector across different browsers, but minitech's answer solves that a treat – Adam Nicholson Jul 28 '14 at 21:17

1 Answers1

7

Here’s some futuristic JavaScript that does exactly the same thing:

var key = "some_key";
var selector = "#my_input, input[name=my_input], .someInputs";

document.addEventListener('change', function (e) {
    if (e.target.matches(selector)) {
        doSomething(key, e.target.value);
    }
});

However, several browsers only support it with a prefix, so it’ll be closer to this:

var matches = (function () {
    var names = ['matches', 'matchesSelector', 'mozMatchesSelector', 'webkitMatchesSelector', 'msMatchesSelector'];

    for (var i = 0; i < names.length; i++) {
        var name = names[i];

        if (name in HTMLElement.prototype) {
            return HTMLElement.prototype[name];
        }
    }

    return null;
})();

var key = "some_key";
var selector = "#my_input, input[name=my_input], .someInputs";

document.addEventListener('change', function (e) {
    if (matches.call(e.target, selector)) {
        doSomething(key, e.target.value);
    }
});

Assuming the selector isn’t dynamic and you need delegation, you can still do the verbose, manual check:

var key = "some_key";

document.addEventListener('change', function (e) {
    var target = e.target;

    if (target.id === 'my_input' ||
            target.nodeName === 'INPUT' && target.name === 'my_input' ||
            (' ' + target.className + ' ').indexOf(' someInputs ') !== -1) {
        doSomething(key, target.value);
    }
}, false);

As @T.J. Crowder points out, although this works for input elements, you’ll need to check an element’s parents in many cases. Here’s some even more futuristic JavaScript to accomplish the task:

function* ascend(element) {
    do {
        yield element;
    } while ((element = element.parentNode));
}

var key = "some_key";
var selector = "#my_input, input[name=my_input], .someInputs";

document.addEventListener('change', function (e) {
    var match = Array.from(ascend(e.target)).find(x => x.matches(selector));

    if (match) {
        doSomething(key, match.value);
    }
});

If you smashed Firefox Nightly and Chrome together, this would work in that browser. We don’t have that, but feel free to shim Array.prototype.find!

Ry-
  • 218,210
  • 55
  • 464
  • 476
  • 1
    +1 and works for the likely elements the OP has. Just a note: This doesn't handle one important bit of event delegation, which is triggering even the event originated on a *descendant* element of the element that matches the selector. E.g., `.on("click", "div", ...)` fires with `this = div` when you click the **`span`** here:
    foo
    `. To do that bit, you have to check `e.target`, then (if it doesn't match), its parent node, and *its* parent node, etc., until you reach the element you hooked the event on.
    – T.J. Crowder Jul 28 '14 at 17:51
  • 1
    @T.J.Crowder: Good point! I’ve added some code that I wish worked *today* for that. =D – Ry- Jul 28 '14 at 17:58
  • 2
    LOL! But beware, if you're not rooted in `document`, you'll go out of bounds. Alternately, of course, there's...a boring old `for` loop. *(Gasp!!)* ;-) `for (var node = e.target; !matches.call(node, selector); node = node.parentNode) { if (node === this) { return; } } doSomething(key, node.value);` (formatted: http://pastie.org/9427419). (You might need to check against `document.documentElement` rather than `document` if rooting in `document`. I never root in `document`, I use `document.body`.) – T.J. Crowder Jul 28 '14 at 18:05
  • @minitech: Great help thanks. Had to also add `true` as the third `addEventListener()` parameter as some sites using this had their own change event listeners preventing the event from bubbling up to the `document` level where my listener is – Adam Nicholson Jul 28 '14 at 23:35