Adding class to multiple elements same class Javascript?

Adding class to multiple elements same class Javascript?
0

#1

As you can see all images has “gallery__img” class, but the overlay is only showing up when I click the first image, if I click any img that isn’t the first child nothing will happen, why? What’s wrong with my code? This seems pretty basic but I wouldn’t know why this is happening.

<div class="gallery">
      <img src="img/hg-1.jpg" alt="Interior view" class="gallery__img">
      <img src="img/hg-2.jpg" alt="Interior view" class="gallery__img">
      <img src="img/hg-3.jpg" alt="Interior view" class="gallery__img">
      <img src="img/hg-4.jpg" alt="Interior view" class="gallery__img">
      <img src="img/hg-5.jpg" alt="Interior view" class="gallery__img">
      <img src="img/hg-6.jpg" alt="Interior view" class="gallery__img">
      <img src="img/hg-7.jpg" alt="Interior view" class="gallery__img">
      <img src="img/hg-8.jpg" alt="Interior view" class="gallery__img">
      <img src="img/hg-9.jpg" alt="Interior view" class="gallery__img">
      <img src="img/hg-10.jpg" alt="Interior view" class="gallery__img">
      <img src="img/hg-11.jpg" alt="Interior view" class="gallery__img">
      <img src="img/hg-12.jpg" alt="Interior view" class="gallery__img">
    </div>
//Open overlay
document.querySelector('.gallery__img').addEventListener('click', function () {
  document.querySelector('.overlay').classList.add('showOverlay');
});

// Close overlay
document.querySelector('#closeOverlay').addEventListener('click', function() {
  document.querySelector('.overlay').classList.remove('showOverlay');
});
.overlay {
  height: 100vh;
  width: 100%;
  padding: 0 5rem;
  background-color: black;
  display: flex;
  justify-content: center;
  align-items: center;
  position: fixed;
  top: 0;
  left: 0;
  z-index: 100;
  opacity: 0;
  visibility: hidden;
  @include transition;
}

.showOverlay {
  opacity: 1;
  visibility: visible;
}

#2

You think that querySelector selects all of the elements with class=“gallery__img”, but it does not. You can use querySelectorAll, but it returns a collection of all elements in the document that have class=“gallery__img”. Then you will need to iterate through this collecction to add the event listeners.

As I general rule of thumb, if I add the same event listener to more then 10 elements that are in the same parent container, then I instead add the event listener on the parent container and have the callback function do what it needs to do on the applicable child element. When you start adding a lot of event listeners, DOM performance can suffer.


#3

In case you need some code which accomplishes what you want, here it is.

const toggleOverlay = () =>document.querySelector('.overlay').classList.toggle('showOverlay');
const addClickEvent = elem => elem.addEventListener('click', toggleOverlay);

//Open overlay
document.querySelectorAll('.gallery__img').forEach(addClickEvent);

// Close overlay
document.querySelector('#closeOverlay').addEventListener('click', toggleOverlay);

#4

I’ve been using jQuery and I thought pure JS would be as easy, how naive. While this code here works:

document.querySelectorAll('.gallery__img').forEach(img => {
  img.addEventListener('click', function() {
    document.querySelector('.overlay').classList.add('showOverlay');
  })
});

I find your solution much cleaner and reusable, thanks.


#5

I still recommend my other suggestion (2nd paragraph of my last reply) . You would only need to use a single addEventListener call on the div with class=“gallery”.


#6

Following your advice I did this:

const addClickEvent = () => document.querySelectorAll('.gallery__img').forEach( () => document.querySelector('.overlay').classList.add('showOverlay') );

document.querySelector('.gallery').addEventListener('click', addClickEvent);

This works, but I am trying to do the following:

let homeIMG = document.querySelectorAll('.gallery__img');
let sectionIMG = document.querySelectorAll('.section-gallery__img');

const addClickEvent = el => el.forEach( () => document.querySelector('.overlay').classList.add('showOverlay'));

document.querySelector('.gallery').addEventListener('click', addClickEvent(homeIMG));
document.querySelector('.section-gallery').addEventListener('click', addClickEvent(sectionIMG));

And it’s giving me an error, as you can see I am just trying to use the same callback function with a different parameter to handle different sections.

Uncaught TypeError: Cannot read property ‘addEventListener’ of null” Why am I getting this error?


#7

Try:

document.querySelector('.gallery').addEventListener('click', function() {
  addClickEvent(homeIMG);
});

Apply this concept to both querySelectors. If this does not resolve your problem, post link to the code and I can take a look.


#8

It does work, which makes me wonder why if I do it like this:

document.querySelector('.gallery').addEventListener('click', addClickEvent(homeIMG));

It’s like if I was calling the function instead of triggering it with the click event, like your code does.

But something odd is happening, I have 2 html files (index.html and section.html), the structure of these files look like like this:

<!-- index.html -->
<div class="gallery">
      <img src="img/hg-1.jpg" alt="Interior view" class="gallery__img">
      <img src="img/hg-2.jpg" alt="Interior view" class="gallery__img">
      <img src="img/hg-3.jpg" alt="Interior view" class="gallery__img">
      <img src="img/hg-4.jpg" alt="Interior view" class="gallery__img">
      <img src="img/hg-5.jpg" alt="Interior view" class="gallery__img">
      <img src="img/hg-6.jpg" alt="Interior view" class="gallery__img">
      <img src="img/hg-7.jpg" alt="Interior view" class="gallery__img">
      <img src="img/hg-8.jpg" alt="Interior view" class="gallery__img">
      <img src="img/hg-9.jpg" alt="Interior view" class="gallery__img">
      <img src="img/hg-10.jpg" alt="Interior view" class="gallery__img">
      <img src="img/hg-11.jpg" alt="Interior view" class="gallery__img">
      <img src="img/hg-12.jpg" alt="Interior view" class="gallery__img">
    </div>
<!-- Popup window -->
  <div class="overlay">
    <button class="overlay__close-icon" id="closeOverlay">&times;</button>
    <img src="img/prev.png" alt="Previous button" class="overlay__btn">
    <img src="img/hg-1.jpg" alt="Interior view" class="overlay__img">
    <img src="img/next.png" alt="Next button" class="overlay__btn">
  </div>
<!-- Script -->
 <script src="app.js"></script>
<!-- section.html -->
  <div class="section-gallery">
      <img src="img/5east-1.jpg" alt="Interior view" class="section-gallery__img">
      <img src="img/5east-2.jpg" alt="Interior view" class="section-gallery__img">
      <img src="img/5east-3.jpg" alt="Interior view" class="section-gallery__img">
      <img src="img/5east-4.jpg" alt="Interior view" class="section-gallery__img">
      <img src="img/5east-5.jpg" alt="Interior view" class="section-gallery__img">
      <img src="img/5east-6.jpg" alt="Interior view" class="section-gallery__img">
      <img src="img/5east-7.jpg" alt="Interior view" class="section-gallery__img">
      <img src="img/5east-8.jpg" alt="Interior view" class="section-gallery__img">
      <img src="img/5east-9.jpg" alt="Interior view" class="section-gallery__img">
    </div>
<!-- Popup window -->
  <div class="overlay">
    <button class="overlay__close-icon" id="closeOverlay">&times;</button>
    <img src="img/prev.png" alt="Previous button" class="overlay__btn">
    <img src="img/hg-1.jpg" alt="Interior view" class="overlay__img">
    <img src="img/next.png" alt="Next button" class="overlay__btn">
  </div>
<!-- Script -->
 <script src="app.js"></script>

The JS so far:

//Open overlay
let homeIMG = document.querySelectorAll('.gallery__img');
let sectionIMG = document.querySelectorAll('.section-gallery__img');

const addClickEvent = el => el.forEach( () => document.querySelector('.overlay').classList.add('showOverlay') );

document.querySelector('.gallery').addEventListener('click', function() {
  addClickEvent(homeIMG)
});

document.querySelector('.section-gallery').addEventListener('click', function() {
  addClickEvent(sectionIMG)
});

// Close overlay
document.querySelector('#closeOverlay').addEventListener('click', function() {
  document.querySelector('.overlay').classList.remove('showOverlay');
});

I keep getting this message in the console: “Uncaught TypeError: Cannot read property ‘addEventListener’ of null” It’s like a conflict with the files, this is probably a noobie mistake but I’m failing to understand of why this conflict is happening.


#9

When you use querySelector and no element can be found based on the selector criteria, it returns null, so that is why you would get that error message.

Since all of this code is being used by both files, when it runs in the index.html page, querySelector(’.section-gallery’) will return null and in the section.html page, querySelector(’.gallery’) will return null. You will need to know which page you are currently on and perform the applicable querySelector based on the page.

OR

You could assign the result of the document.querySelector(’.gallery’) to a variable and check if it is null or not. If it is not null, then use the addEventListener method on it. If it is null then you can write document.querySelector(’.section-gallery’).addEventListener without issues.


#10

Yes… I solved it like this:

if (document.querySelector('.gallery')) {
  document.querySelector('.gallery').addEventListener('click', function() {
    addClickEvent(homeIMG);
  });
} else {
  document.querySelector('.section-gallery').addEventListener('click', function() {
    addClickEvent(sectionIMG);
  });
}

The thing is that when I use jQuery I don’t get this conflict, but the more I use pure JS the more I see all the differences. Doing this project with pure JS is going to be fun and frustrating for me. Would you say this was a fine solution?


#11

How about replacing:

let homeIMG = document.querySelectorAll('.gallery__img');
let sectionIMG = document.querySelectorAll('.section-gallery__img');

const addClickEvent = el => el.forEach( () => document.querySelector('.overlay').classList.add('showOverlay') );

if (document.querySelector('.gallery')) {
  document.querySelector('.gallery').addEventListener('click', function() {
    addClickEvent(homeIMG);
  });
} else {
  document.querySelector('.section-gallery').addEventListener('click', function() {
    addClickEvent(sectionIMG);
  });
}

// Close overlay
document.querySelector('#closeOverlay').addEventListener('click', function() {
  document.querySelector('.overlay').classList.remove('showOverlay');
});

with:

const addClickEvent = el => el.forEach(() => document.querySelector('.overlay').classList.add('showOverlay'});

let query = document.querySelector('.gallery');
let images = 'gallery__img';
if (!query) {
  query = document.querySelector('.section-gallery');
  images = 'section-' + images;
}

const imageElems = document.querySelectorAll("." + images);
query.addEventListener('click', () => addClickEvent(imageElems));

// Close overlay
document.querySelector('#closeOverlay').addEventListener('click', function() {
  document.querySelector('.overlay').classList.remove('showOverlay');
});

This DRYs up your original code.


#12

I actually just looked at this code and realize it does not do anything different than the following:

const addClickEvent = () => document.querySelector('.overlay').classList.add('showOverlay');

Why? Because you the querySelector is selecting the same element (div with class=“overlay” over and over.

If that is all you want to do, then I really do not understand the point of all that code you wrote (and I refactored).


#13

Yeah you are right about that, I originally coded everything in a different and less efficient way and I just left it there as I was just testing. But with your solution I may not need that function at all, instead I could just do this:

// Open overlay
query.addEventListener('click', () => {
  document.querySelector('.overlay').classList.add('showOverlay');
  document.querySelector('body').style.overflow = 'hidden';
});

This way I have everything inside one function.