Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Attach listener to multiple children elements

Im trying to get rid of jquery from my page and rewriting some functionalities to pure js. There are 2 lists with class work, containing few li elements. Each li element should have an action on click to add class 'active' to it. In jquery it is very simple:

$('.work li').on('click', function () {
            var that = $(this);
            that.parent().find('li.active').removeClass('active');
            $(this).addClass('active');
        })

Is there a nicer solution in pure js rather than making something like this with nested loops:

    var lists = document.getElementsByClassName('work');
    for(var i=0; i<lists.length; i++){
       var children = lists[i].querySelectorAll('li');
       for(var j=0; j<children.length;j++){
        children[j].addEventListener();
       }
    }
like image 612
mjanisz1 Avatar asked Dec 23 '14 13:12

mjanisz1


People also ask

How do you add an event listener to multiple elements?

Adding event listener to multiple elements To add the event listener to the multiple elements, first we need to access the multiple elements with the same class name or id using document. querySelectorAll() method then we need to loop through each element using the forEach() method and add an event listener to it.

How do you add an event listener to multiple elements in react JS?

Adding an Event Listener to Multiple Elements Using a for...of Loop + IIFE. Open the console and click any of the buttons. The same event listener is added to each button using a for...of loop along with an immediately invoked function that passes the current element of the loop back into the function.

Can I add event listener to all elements of class?

To add an event listener to all elements with class: Use the document. querySelectorAll() method to select the elements by class. Use the forEach() method to iterate over the collection of elements.

Can elements have multiple listeners?

We can add multiple event listeners for different events on the same element. One will not replace or overwrite another. In the example above we add two extra events to the 'button' element, mouseover and mouseout.


2 Answers

There are 2 lists with class work, containing few li elements. Each li element should have an action on click to add class 'active' to it.

You could create that entire functionality by adding an event listener to all lis returned by the querySelectorAll. The querySelectorAll returns a nodeList and not an array, so need to map it in order to iterate it. However, note that we are still iterating the set.

Example Snippet:

var lists = document.querySelectorAll(".work li"), 
    doSomething = function() {
        [].map.call(lists, function(elem) { elem.classList.remove("active") });
        this.classList.add("active");
    };
[].map.call(lists, function(elem) {
    elem.addEventListener("click", doSomething, false);
});
li { cursor: pointer; }
.active { background-color: yellow; }
<ul class="work"><li>One</li><li>Two</li><li>Three</li></ul>
<ul class="work"><li>Four</li><li>Five</li><li>Six</li></ul>

In fact you could also use event delegation and add event listener only on the ul and then use the event.target to handle your routine. This is better than adding an event listener to each and every li, in case there are many of them. There will be only as many handlers as uls.


Is there a nicer solution in pure js rather than making something like this with nested loops

Not really. You have to iterate over the set of elements anyway. Nicer in terms of jazzy, yes. Nicer in terms of avoiding the loops, no. Nicer in terms of efficiency, well yes. Compare the above vanilla javascript code with the jQuery one in your question:

$('.work li').on('click', function () { // internally will iterate and add listener to each li
    var that = $(this);
    that.parent().find('li.active').removeClass('active'); // go to parent and then find li
    $(this).addClass('active');
});

.

like image 50
Abhitalks Avatar answered Oct 03 '22 12:10

Abhitalks


querySelectorAll() is quite powerfull tool.

I've made a JSFiddle for you.

Code can still be enough nice and readable:

for (var item of document.querySelectorAll(".work li")) {
 item.addEventListener("click", function (evt) {
     evt.target.classList.add("active");
 }, false);
}

Another way is (if you still want to iterate using forEach):

Array.prototype.forEach.call(document.querySelectorAll(".work li"), function(item) {
   item.addEventListener("click", function (evt) {
        evt.target.classList.add("active");
   }, false);
});

Here (JSFiddle) we are interating through a NodeList returned by querySelectorAll(".work li") by calling the Array.prototype.forEach method on it.

And here (Jsfiddle) is the full example, that toggles the class:

Array.prototype.forEach.call(document.querySelectorAll(".work li"), function(item) {
   item.addEventListener("click", function (evt) {
        evt.target.toggleActive = !evt.target.toggleActive;
       evt.target.classList[!!evt.target.toggleActive ? 'add' : 'remove']("active");
   }, false);
});

And finally if there should be only one active element at a time (Jsfiddle):

var currentActiveElement;
Array.prototype.forEach.call(document.querySelectorAll('.work li'), function(item) {
item.addEventListener('click', function(evt) {
  currentActiveElement && currentActiveElement.classList.remove('active');
  currentActiveElement = evt.target;
  currentActiveElement.classList.add('active');
 }, false);
});
like image 33
Alexander Arutinyants Avatar answered Oct 03 '22 13:10

Alexander Arutinyants