0


Beginner here.
I have a small image gallery, with two classes declared in CSS:
imagem: selected image, with border
imagem-activa: all the others, without border

How can i use JavaScript to add/remove the classes when the user clicks one image? I already know how to do it in jQuery, but want to learn also pure JS.

HTML

     <div class="exercicio">
        <h2 class="titulo">
            Exercício 4
        </h2>
        <img src="img/imagem1.jpg" class="imagem imagem-activa" />
        <img src="img/imagem2.jpg" class="imagem" />
        <img src="img/imagem3.jpg" class="imagem" />
        <img src="img/imagem4.jpg" class="imagem" />

    </div>

CSS

.imagem{border:5px solid #000;opacity:0.5;cursor: pointer;}
.imagem-activa{border:5px solid #ff0066;opacity:1;}

JS
This part i can't make it work. Can someone help me?

    var imagens = document.getElementsByClassName("imagem");
    var imagemActual = 0;
    for(var i = 0; i < imagens.length; i++) {
      imagens[i].addEventListener("click", function() {
          imagens[imagemActual].classList.remove("imagem-activa");
          this.classList.add("imagem-activa");
          imagemActual = i;
      });
    }

This is my working jQuery solution

    $(".imagem").click(function() {
      $(".imagem").removeClass("imagem-activa");
      $(this).addClass("imagem-activa");
    });
fields
  • 59
  • 1
  • 10

1 Answers1

1

So, what is happening here is that anytime any of your click handlers is invoked, your variable imageActual gets set to the current value of i which will always be 4 because this is the final value of i after the for loop. For the first click, you might not run into any errors and you might get the expected result. But as soon as any of your event listeners has run, the value of imagemActual will be 4 and that will cause an error on any future invocation of your event listeners because imagens[4] will be undefined.

There are a few ways to solve this.

1) bind

You can bind the (temporary) value of i inside the loop to the event listener function:

var imagens = document.getElementsByClassName("imagem");
var imagemActual = 0;
for(var i = 0; i < imagens.length; i++) {
  imagens[i].addEventListener("click", function(index) {
      imagens[imagemActual].classList.remove("imagem-activa");
      this.classList.add("imagem-activa");
      imagemActual = index;
  }.bind(imagens[i], i));
}
.imagem {
  width: 20px;
  height: 20px;
  background-color: blue;
}

.imagem.imagem-activa {
  background-color: red;
}
<div class="exercicio">
    <h2 class="titulo">
        Exercício 4
    </h2>
    <img src="" class="imagem imagem-activa" />
    <img src="" class="imagem" />
    <img src="" class="imagem" />
    <img src="" class="imagem" />
</div>

2) let

If you can use ES6, you can use let which will make sure that your counting variable is a unique instance for every value that is applied:

const imagens = document.getElementsByClassName("imagem");
let imagemActual = 0;
for(let i = 0; i < imagens.length; i++) {
  imagens[i].addEventListener("click", function() {
      imagens[imagemActual].classList.remove("imagem-activa");
      this.classList.add("imagem-activa");
      imagemActual = i;
  });
}
.imagem {
  width: 20px;
  height: 20px;
  background-color: blue;
}

.imagem.imagem-activa {
  background-color: red;
}
<div class="exercicio">
    <h2 class="titulo">
        Exercício 4
    </h2>
    <img src="" class="imagem imagem-activa" />
    <img src="" class="imagem" />
    <img src="" class="imagem" />
    <img src="" class="imagem" />
</div>
glutengo
  • 388
  • 3
  • 13
  • i appreciate your answer. Your code works. But i don't really undestand it. I'll have to study .bind() and closures. Thank you – fields Dec 08 '17 at 22:45
  • 1
    thanks for your vote. The thing is that the event listener is called after the loop has finished. Therefore the value of your counting variable will always be the same. In order to make sure that your event listener functions know the temporary counting value, you need to bind it like I've shown or store it in another variable inside your loop. I do think that things are more intuitive / straightforward with ES6 and let. I've also added a fiddle for that. – glutengo Dec 08 '17 at 23:08
  • 1
    just discovered the let option :) and noticed you also posted it. And yes, let it's much more intuitive (at least for a beginner) – fields Dec 08 '17 at 23:12