5

I have a set of items like this:

<label for="Cadenza-1" class="cars">
    <input type="checkbox" value="1" alt="Cadenza" id="Cadenza-1" name="vehicles[]">
    <img src="img/bill_murray_173x173.jpg">
    <span>Cadenza</span>
</label>

there's about 13 of them. I want to add a class to the label when clicked. However, the click event fires twice. right now I'm debugging the click event then I'll add the class:

var cars = document.getElementsByClassName('cars');

for(var c = 0;c < cars.length; c++){
    cars[c].addEventListener("click", function(e){
        selectVehicle(cars[c],e);
    },false);
}

function selectVehicle(el,e) {
    console.log(e);
    e.stopPropagation();
}

The console.log fires twice.

Pointy
  • 405,095
  • 59
  • 585
  • 614
dcp3450
  • 10,959
  • 23
  • 58
  • 110

6 Answers6

11

Try adding preventDefault after your stopPropogation:

function selectVehicle(el,e) {
    console.log(e);
    e.stopPropagation();
    e.preventDefault();
}

I believe it is best to place console.log(e) after the stopPropogation & preventDefault though. You will also then need to implement functionality to set the checkbox to checked since this would prevent that from happening.

mattferderer
  • 894
  • 9
  • 17
6

When the <span> or the <img> receives a "click" event, that'll bubble up the DOM to the <label> element, and your event handler will be called. The <label> then triggers another "click" event on the <input> element, and that also bubbles up to the <label>.

You can check in the handler to see whether the <input> was clicked:

function selectVehicle(el,e) {
    if (e.target.tagName !== "INPUT") return;

    // do stuff
}

Alternatively, you could just add the "click" handler only to the <input> itself.

Now you're also going to notice that your code isn't working because you've hit a common problem with binding event handlers inside loops. The problem is that the variable c will have as its value the length of the cars list by the time the event handlers actually run. There are a few ways of dealing with that; one is to loop with forEach() instead of a for loop:

[].forEach.call(cars, function(car) {
    car.addEventListener("click", function(e){
        selectVehicle(car,e);
    }, false);
});
Pointy
  • 405,095
  • 59
  • 585
  • 614
3

You are adding the event listener to the label, you should add the event listener to the checkbox because the label behavior copy the same of the input assigned in for.

Please note that if you click just in the checkbox the callbacks works fine, this is because the event on the label is raised by the checkbox.

The right way to do that is to add the event listener only for the checkbox or adding prevent default in the setlectVehicle callback.

Fabricio
  • 3,248
  • 2
  • 16
  • 22
1

You are not required to preventDefault or stopPropagation, but just to add listner on the input element.

cars[c].children[0].addEventListener("click", function(e) {

Try this. It is working as expected.

Additionally, you are not required to use Id's with label's for if the label element encloses the required input/other elements

var cars = document.getElementsByClassName('cars');

for (var c = 0; c < cars.length; c++) {
  cars[c].children[0].addEventListener("click", function(e) {
    selectVehicle(cars[c], e);
  }, false);
}

function selectVehicle(el, e) {
  console.log(e);

}
<label class="cars">
  <input type="checkbox" value="1" alt="Cadenza" name="vehicles[]">
  <img src="img/bill_murray_173x173.jpg">
  <span>Cadenza</span>
</label>
<label class="cars">
  <input type="checkbox" value="1" alt="Cadenza" name="vehicles[]">
  <img src="img/bill_murray_173x173.jpg">
  <span>Cadenza 2</span>
</label>
Sujeet Jaiswal
  • 1,071
  • 7
  • 12
0

Or you can simply leave your javascript code as it is. and put the input element outside the label like this:

<label for="Cadenza-1" class="cars">
       <img src="img/bill_murray_173x173.jpg">
       <span>Cadenza</span>
</label>
<input type="checkbox" value="1" alt="Cadenza" adenza-1" name="vehicles[]">

And this is your Javascript UNTOUCHED:

var cars = document.getElementsByClassName('cars');

for(var c = 0;c < cars.length; c++){
    cars[c].addEventListener("click", function(e){
        selectVehicle(cars[c],e);
    },false);
}

function selectVehicle(el,e) {
    console.log(e);
    e.stopPropagation();
}
SomeOne
  • 51
  • 4
0

I found just one solution that work to me: to force that the addEventListener to be called once. I Answered in this question: https://stackoverflow.com/a/76873072/8122578

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Aug 13 '23 at 03:11
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/late-answers/34821902) – Harrison Aug 14 '23 at 09:06