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);
}
Dinesh P.R.
  • 6,936
  • 6
  • 35
  • 44
  • 3
    While I know that jQuery isn't the answer for everything, it *does* make these sorts of issues moot: `$('.coins').on('dragstart', handleDragStart);` – zzzzBov Sep 11 '12 at 03:57
  • Related: [Want to add "addEventListener" on multiple elements with same class](/q/51573435/4642212). – Sebastian Simon Mar 18 '22 at 00:22

8 Answers8

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);
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Be careful, `getElementsByClassName` returns a live node list. – Samuel Katz Jan 11 '15 at 08:27
  • 3
    @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. – jfriend00 Jan 11 '15 at 08:29
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!

Kris Selbekk
  • 7,438
  • 7
  • 46
  • 73
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.

Robbie Wxyz
  • 7,671
  • 2
  • 32
  • 47
Multiversum
  • 323
  • 3
  • 5
  • 7
    This doesn't use loop-control constructs and odd variables. – Multiversum Jul 03 '13 at 09:30
  • 9
    Even if you argue that it is still a loop, this is pretty badass. Thank you. – skribbz14 Nov 20 '15 at 22:07
  • 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. – Andrew Willems Feb 25 '16 at 04:55
  • 17
    Maybe it saves you a few keystrokes, but it costs the next developer an extra few minutes to understand. Net loss. – Kabir Sarin Jul 04 '16 at 09:32
  • `Array.prototype.forEach` is longer, but more efficient than `[].forEach`. – Константин Ван Oct 13 '16 at 08:41
  • 3
    FYI, it is 2018, and [NodeLists are iterable in most browsers](https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach#Browser_Compatibility). `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. – romellem May 25 '18 at 20:06
  • @romellem Except it’s `forEach`, not `foreach`. – Sebastian Simon Mar 18 '22 at 00:23
  • 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()`. – Jordan Bradford Apr 05 '22 at 14:36
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

arzhed
  • 428
  • 4
  • 10
Profesor08
  • 1,181
  • 1
  • 13
  • 20
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))
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".

Sebastian Simon
  • 18,263
  • 7
  • 55
  • 75
user414873
  • 741
  • 8
  • 20
  • Idiomatic pattern: `theParent.addEventListener("click", (`[`{ target }`](//developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment)`) => { const element = target.`[`closest`](//developer.mozilla.org/en/docs/Web/API/Element/closest)`(".coins"); if(element){ doTheThing(element); } });`. – Sebastian Simon Mar 18 '22 at 00:27
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;
    }
});
Duncan
  • 1,530
  • 15
  • 20
  • 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. – Andrew Willems Feb 25 '16 at 04:48
  • @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. :) – Duncan Feb 27 '16 at 11:26
0

You could also use prototyping

NodeList.prototype.addEventListener = function (type, callback) {
    this.forEach(function (node) {
        node.addEventListener(type, callback);
    });
};
Jon Allen
  • 335
  • 1
  • 9