简体   繁体   中英

Changing source of image inside a class on click Cannot set property 'src' of undefined

I am trying to do a really simple code where every time I click on an image it changes the source. I Want also to display this image in hover but that would be the next step after I can make it work on click.

This is my HTML

 <div class="seats-row-A">
                <img id="A1" class="seat-unique" src="chair.svg" >
                <img id="A2" class="seat-unique" src="chair.svg" >
                <img id="A3" class="seat-unique" src="chair.svg" >
                <img id="A4" class="seat-unique"  src="chair.svg" >
                <img id="A5" class="seat-unique" src="chair.svg" >
                <img id="A6" class="seat-unique" src="chair.svg" >
                <img id="A7"  class="seat-unique" src="chair.svg">
</div>

This is my JS

seatsUnclicked= document.getElementsByClassName("seat-unique");
for(var i = 0; i < seatsUnclicked.length; i++)
    {        
        seatsUnclicked[i].onclick = function(){
        seatsUnclicked[i].src = "chairhover.svg";
    }

}

This is typical problem of the scope of the variable.In your case onclick event will occur sometime in future.By that time the for-loop have finished its execution and the value of i is set to the length of the html collection.

Before let one of the solution was to create an Immediately invoking function expression and pass the value of the i , This value will be accepted as a parameter.In this way with closure the value of i is preserved.

In ES6 with new keyword let there is no need of creating a closure and IIFE

 var seatsUnclicked = document.getElementsByClassName("seat-unique"); //SOLUTION 1 for (var i = 0; i < seatsUnclicked.length; i++) { (function(x) { seatsUnclicked[x].onclick = function() { seatsUnclicked[x].src = "chairhover.svg"; } }(i)) } //SOLUTION 2 for (let i = 0; i < seatsUnclicked.length; i++) { seatsUnclicked[i].onclick = function() { seatsUnclicked[i].src = "chairhover.svg"; } } 
 <div class="seats-row-A"> <img id="A1" class="seat-unique" src="chair.svg"> <img id="A2" class="seat-unique" src="chair.svg"> <img id="A3" class="seat-unique" src="chair.svg"> <img id="A4" class="seat-unique" src="chair.svg"> <img id="A5" class="seat-unique" src="chair.svg"> <img id="A6" class="seat-unique" src="chair.svg"> <img id="A7" class="seat-unique" src="chair.svg"> </div> 

You can choose to use event propagation instead, then you do not need to attach an event handler to all separate images

 let seatContainer = document.querySelectorAll('.seats-row-A')[0]; // use event-propagation catch all clicks on seats-row-a, and handle clicks that wait seatContainer.addEventListener('click', function(e) { if (!e.target.src) { // no image return; } if (e.target.src.includes('chair.svg')) { e.target.src = 'chairhover.svg'; } else { e.target.src = 'chair.svg'; } }); 
 <div class="seats-row-A"> <img id="A1" class="seat-unique" src="chair.svg" > <img id="A2" class="seat-unique" src="chair.svg" > <img id="A3" class="seat-unique" src="chair.svg" > <img id="A4" class="seat-unique" src="chair.svg" > <img id="A5" class="seat-unique" src="chair.svg" > <img id="A6" class="seat-unique" src="chair.svg" > <img id="A7" class="seat-unique" src="chair.svg"> </div> 

Or, you can create i as a block scoped variable , using let as declaration as opposed to your current var (function scoped variable)

for (let i = 0; i < seatsUnclicked.length; i++) {        
    seatsUnclicked[i].onclick = function() {
        seatsUnclicked[i].src = "chairhover.svg";
    }
}

Or you could use the event argument , that points to your element (a bit like the event propagation in the first example)

function handleElementClicked( e ) {
  if (e.target.src.includes('chair.svg')) {
    e.target.src = 'chairhover.svg';
  } else {
    e.target.src = 'chair.svg';
  }
}

for (var i = 0; i < seatsUnclicked.length; i++) {        
    seatsUnclicked[i].onclick = handleElementClicked;
}

Or, you could bind your indexer to your calling function

for (var i = 0; i < seatsUnclicked.length; i++) {        
    seatsUnclicked[i].onclick = function( index ) {
        seatsUnclicked[index].src = "chairhover.svg";
    }.bind( null, i );
}

The getElementsByClassName() method returns a collection of all elements in the document with the specified class name, as a NodeList object.

To avoid unecessary class names and inline onClick js, you can use querySelectorAll instead and add a click listener to each one like this:

let seatsUnclicked = document.querySelectorAll("img");

function changeImage() {
this.src="chairhover.svg";
}

seatsUnclicked.forEach(img => {
img.addEventListener('click', changeImage);
})

jsFiddle: https://jsfiddle.net/AndrewL64/866vf8a1/

Couple of things I observed:

  1. You have to use setAttribute to set the attribute

  2. inside the function you used : seatsUnclicked[i].src = "chairhover.svg";

    here i is always equal = seatsUnclicked.length

    so it gives undefined element. so you get error cannot set the property of undefined.

  3. user event.currentTarget.setAttribute:

Check this https://jsfiddle.net/hgfeh9zw/

<div class="seats-row-A">
<img id="A1" class="seat-unique" 
src="http://icons.iconarchive.com/icons/paomedia/small-n-flat/64/sign-check-icon.png" >
<img id="A2" class="seat-unique" src="http://icons.iconarchive.com/icons/paomedia/small-n-flat/64/sign-check-icon.png" >
<img id="A3" class="seat-unique" src="http://icons.iconarchive.com/icons/paomedia/small-n-flat/64/sign-check-icon.png" >
<img id="A4" class="seat-unique"  src="http://icons.iconarchive.com/icons/paomedia/small-n-flat/64/sign-check-icon.png" >
<img id="A5" class="seat-unique" src="http://icons.iconarchive.com/icons/paomedia/small-n-flat/64/sign-check-icon.png" >
<img id="A6" class="seat-unique" src="http://icons.iconarchive.com/icons/paomedia/small-n-flat/64/sign-check-icon.png" >
<img id="A7"  class="seat-unique" src="http://icons.iconarchive.com/icons/paomedia/small-n-flat/64/sign-check-icon.png">
</div>
<script> 
  window.onload= function(){
  seatsUnclicked= document.getElementsByClassName("seat-unique");
   for(var i = 0; i < seatsUnclicked.length; i++)
   { 
      seatsUnclicked[i].onclick = function(event){
      event.currentTarget.setAttribute('src', 'https://www.gravatar.com/avatar/b43bbab9d16d030a8744f17ca9c1da8d?s=32&d=identicon&r=PG');
    }
  }

 }

  </script>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM