Skip to content Skip to sidebar Skip to footer

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 hav

Solution 1:

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; }
<ulclass="work"><li>One</li><li>Two</li><li>Three</li></ul><ulclass="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 livar that = $(this);
    that.parent().find('li.active').removeClass('active'); // go to parent and then find li
    $(this).addClass('active');
});

.

Solution 2:

querySelectorAll() is quite powerfull tool.

I've made a JSFiddle for you.

Code can still be enough nice and readable:

for (var item ofdocument.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);
});

Solution 3:

You could actually attach click event listener on wrapper element and filter out clicked li element using event.target (or for IE event.srcElement).

Try out below snippet (not production code by any means, it should be enhanced especially for browser compatibility). Good side of this solution is that you don't have to watch for new elements dynamically added to wrapper element...

functionclickHandler(e) {
  var elem, evt = e ? e : event;
  if (evt.srcElement) {
    elem = evt.srcElement;
  } elseif (evt.target) {
    elem = evt.target;
  }

  // Filter out li tagsif (elem && ('LI' !== elem.tagName.toUpperCase())) {
    returntrue;
  }

  console.log('You clicked: ' + elem.innerHTML)
  returntrue;
}

var lists = document.getElementsByClassName('work');
for (var i = 0; i < lists.length; i++) {
  var children = lists[i].addEventListener('click', clickHandler);
}
<ulclass="work"><li>1.1</li><li>1.2</li><li>1.3</li><li>1.4</li></ul><ulclass="work"><li>2.1</li><li>2.2</li><li>2.3</li><li>2.4</li></ul><ulclass="work"><li>3.1</li><li>3.2</li><li>3.3</li><li>3.4</li></ul>

Solution 4:

Sure. Why not just make better use of document.querySelectorAll? It does use the syntax of css, so we can grab both in one go.

Perhaps something like this:

{
 liList = document.querySelectorAll('.work li');
 var i, n = liList.length;
 for (i=0; i<n; i++)
  liList[i].addEventListener(eventName, functionName, useCapture);
}

Solution 5:

I too was looking for a concise but useful solution to this.

This is the best approach I came up with:

tag = document.querySelectorAll('.tag');

tag.forEach(element => {
  element.addEventListener("mouseover", aFunction);
});

functionaFunction(){
  this.style.background = "grey";
}

querySelectorAll is a powerful method, a foreach is always much shorter and easier to use when the use-case is there. I prefer naming the functions and adding to them, although I suppose you could easily replace that with another arrow function within the foreach if you prefer

Post a Comment for "Attach Listener To Multiple Children Elements"