addEventListener on NodeList [duplicate]
Asked Answered
A

8

41

Does NodeList support addEventListener. If not what is the best way to add EventListener to all the nodes of the NodeList. Currently I am using the code snippet as show below, is there a better way to do this.

var ar_coins = document.getElementsByClassName('coins');
for(var xx=0;xx < ar_coins.length;xx++)
{
        ar_coins.item(xx).addEventListener('dragstart',handleDragStart,false);
}
Allspice answered 11/9, 2012 at 3:15 Comment(2)
While I know that jQuery isn't the answer for everything, it does make these sorts of issues moot: $('.coins').on('dragstart', handleDragStart);Unilobed
Related: Want to add "addEventListener" on multiple elements with same class.Parmenter
G
48

There is no way to do it without looping through every element. You could, of course, write a function to do it for you.

function addEventListenerList(list, event, fn) {
    for (var i = 0, len = list.length; i < len; i++) {
        list[i].addEventListener(event, fn, false);
    }
}

var ar_coins = document.getElementsByClassName('coins');
addEventListenerList(ar_coins, 'dragstart', handleDragStart); 

or a more specialized version:

function addEventListenerByClass(className, event, fn) {
    var list = document.getElementsByClassName(className);
    for (var i = 0, len = list.length; i < len; i++) {
        list[i].addEventListener(event, fn, false);
    }
}

addEventListenerByClass('coins', 'dragstart', handleDragStart); 

And, though you didn't ask about jQuery, this is the kind of stuff that jQuery is particularly good at:

$('.coins').on('dragstart', handleDragStart);
Gillette answered 11/9, 2012 at 3:44 Comment(2)
Be careful, getElementsByClassName returns a live node list.Grecism
@SalmanPK - that shouldn't matter here. The result of getElementsByClassName() is used immediately and not stored so there is no opportunity for it to change while it is being used.Gillette
Z
27

The best I could come up with was this:

const $coins = document.querySelectorAll('.coins')
$coins.forEach($coin => $coin.addEventListener('dragstart', handleDragStart));

Note that this uses ES6 features, so please make sure to transpile it first!

Zippel answered 3/11, 2015 at 5:55 Comment(3)
Delicious... :-)Pruter
Super-clean - and two years on, less need for transpiling with more browser ES6 support.Auckland
Thanks @okay56k, I updated the answer to reflect that.Zippel
A
15

There actually is a way to do this without a loop:

[].forEach.call(nodeList,function(e){e.addEventListener('click',callback,false)})

And this way is used in one of my one-liner helper libraries - nanoQuery.

Auxin answered 31/1, 2013 at 12:53 Comment(8)
This doesn't use loop-control constructs and odd variables.Auxin
Even if you argue that it is still a loop, this is pretty badass. Thank you.Resolvable
In my use case, this method only saves 5 keystrokes relative to the traditional for-loop. However, I love not having to define any iteration variable...and, hey, saving 5 keystrokes is still something.Magisterial
Maybe it saves you a few keystrokes, but it costs the next developer an extra few minutes to understand. Net loss.Hooded
Array.prototype.forEach is longer, but more efficient than [].forEach.Tegan
FYI, it is 2018, and NodeLists are iterable in most browsers. nodeList.foreach(el => el.addEventListener('click', callback)) should be adequate for most people. And if not, ES6 allows us to easily "spread" an array-like object into an actually array. So something like this: [...nodeList].foreach should work as well.Saddle
@Saddle Except it’s forEach, not foreach.Parmenter
Besides its terseness and cleverness in avoiding writing a loop, this one-liner also avoids requiring a NodeList.prototype.forEach() polyfill for older browsers because even IE9 supports Array.prototype.forEach().Othelia
I
7

The simplest example is to add this functionality to NodeList

NodeList.prototype.addEventListener = function (event_name, callback, useCapture)
{
    for (var i = 0; i < this.length; i++)
    {
      this[i].addEventListener(event_name, callback, useCapture);
    }
};

Now you can do:

document.querySelectorAll(".my-button").addEventListener("click", function ()
{
    alert("Hi");
});

In the same way, you can do a forEach loop

NodeList.prototype.forEach = function (callback)
{
    for (var i = 0; i < this.length; i++)
    {
      callback(this[i], i);
    }
};

Using:

document.querySelectorAll(".buttons").forEach(function (element, id)
{
    input.addEventListener("change", function ()
    {
        alert("button: " + id);
    });
});

EDIT : note that NodeList.prototype.forEach has existed ever since november 2016 in FF. No IE support though

Incisure answered 12/4, 2016 at 9:20 Comment(0)
S
6

in es6, you can do a array from nodelist, using Array.from, e.g.

ar_coins = document.getElementsByClassName('coins');
Array
 .from(ar_coins)
 .forEach(addEvent)

function addEvent(element) {
  element.addEventListener('click', callback)
}

or just use arrow functions

Array
  .from(ar_coins)
  .forEach(element => element.addEventListener('click', callback))
Slackjawed answered 7/12, 2016 at 13:35 Comment(1)
Your arrow function needs a closing parenthesis at the very end to work. Cheers.Davedaveda
O
3

Another solution is to use event delegation. You just use addEventListener to the closest parent of the .coins elements and use event.target in the callback to check if the click was really on an element with the class "coins".

Outcome answered 31/12, 2018 at 19:8 Comment(1)
Idiomatic pattern: theParent.addEventListener("click", ({ target }) => { const element = target.closest(".coins"); if(element){ doTheThing(element); } });.Parmenter
H
2

I suppose another option would be to define addEventListener on NodeList using Object.defineProperty. That way you can treat the NodeList as you would a single Node.

As an example, I created a jsfiddle here: http://jsfiddle.net/2LQbe/

The key point is this:

Object.defineProperty(NodeList.prototype, "addEventListener", {
    value: function (event, callback, useCapture) {
        useCapture = ( !! useCapture) | false;
        for (var i = 0; i < this.length; ++i) {
            if (this[i] instanceof Node) {
                this[i].addEventListener(event, callback, useCapture);
            }
        }
        return this;
    }
});
Honeybunch answered 1/2, 2014 at 13:3 Comment(2)
I upvoted this answer because (1) it answers the question, (2) it is different than the other answers, (3) it is clever, and (4) it may be what someone is looking for. However, I have to say that anything that modifies the prototype of one of the core Javascript objects makes me nervous.Magisterial
@AndrewWillems Thanks! And I understand the general concern of modifying native prototypes, but I view it as similar to extending a class from a lib you don't own. :)Honeybunch
N
0

You could also use prototyping

NodeList.prototype.addEventListener = function (type, callback) {
    this.forEach(function (node) {
        node.addEventListener(type, callback);
    });
};
Nombles answered 14/12, 2018 at 10:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.