3

The problem is breaking my mind. Can someone help me? In the <script> tag in my html file I have this:

window.ondragover = function(e){return false;}
window.ondragenter = function(e){return false;}
window.ondrop = function(e){
    var files = e.target.files || e.dataTransfer.files;
    for (var i = 0, file; file = files[i];i++){
        var img = document.createElement('img');
        img.height = 200;
        img.width = 200;
        img.style.background = 'grey';
        document.body.appendChild(img);
        var reader = new FileReader();
        reader.onload = function(){
            img.src = reader.result;
        }
        reader.readAsDataURL(file);
    }
    return false;
}

but when I drop several image files on the browser, only the last image file is loaded and displayed in the last img element, others stay grey.

2
  • 1
    Have a feeling this is going to be due to your use of img within the loop. Since reader.onload is async, the for loop has already completed and img points to the last one Commented Jul 10, 2017 at 13:15
  • in the 1st loop img is asigned to a new element and then its src is asigned to results of new reader that was created in the same loop. next loop: img var is reasigned to a new element and its src is asigned to the result of another new reader. is it wrong? Commented Jul 11, 2017 at 4:09

1 Answer 1

11

As @chazsolo mentioned:

Have a feeling this is going to be due to your use of img within the loop. Since reader.onload is async, the for loop has already completed and img points to the last one

You can fix this by using let instead of var within the loop (let - MDN). This will give each img and reader a block scope within the loop, allowing the async reader method to still access the actual value from that specific loop run.

window.ondragover = function(e){return false;}
window.ondragenter = function(e){return false;}
window.ondrop = function(e){
    var files = e.target.files || e.dataTransfer.files;
    debugger;
    for (var i = 0, file; file = files[i];i++){
        let img = document.createElement('img');
        img.height = 200;
        img.width = 200;
        img.style.background = 'grey';
        document.body.appendChild(img);
        let reader = new FileReader();
        reader.onload = function(){
            img.src = reader.result;
        }
        reader.readAsDataURL(file);
    }
    return false;
}

Update: var vs let

So why is it not working as suspected with var? I try to explain the difference of let and var with a few practical examples.

Variable declarations, wherever they occur, are processed before any code is executed.

This leads us to the following example (don't mind the error in the end, which is produced by the snipped plugin):

Declaration with var

/**
In this example, 'a' is declared after it is used. This doesn't matter, as the 
declaration of 'a' will be processed before running the code. This means a is 
initialized with 'undefined' but is valid. Also the scope of a lies within the 
execution context, that's why it is even available outside of the loop. 
**/
console.log("---------");
console.log("Example Declaration var");
console.log("---------");
for (var i = 0; i < 2; i++) {
  console.log(a); // a is declared but undefined on the 1st run, afterwards it get redeclared and owns the value from the last run.
  var a = i;
}
console.log(a); // a is even available out here as still same execution context.
We see, that on every re declaration of a the value of the a before, is kept. It is not a new "instance".

So what's happening if we use a async function within the loop?

Async function with var

/**
This example shows you the effects, if you use a async function within a loop. 
As the loop will be executed way under 100 miliseconds (which is the time out 
of the async function), c will have the same value for all exections of the 
async mehtod, which is the value assigned by the last run of the loop.
**/
console.log("---------");
console.log("Example effects async var");
console.log("---------");
for (var i = 0; i < 2; i++) {
  var c = i;
  setTimeout(function() {
    console.log(c); //var does redeclare, therefor c will be modified by the next loop until the last loop.
  }, 100);
}

Exactly, always the same output (adapted to your problem, always the same img element and file)

Let's see what's happening with let

Declaration with let

/**
In this example, 'b' is declared after it is used with let. let will be processed 
during runtime. This means 'b' will not be declared when used. This is an invalid 
state. let will give a strict context within the loop. It will be not available 
outside. let has a similar behavior as a declaration in Java.
**/
console.log("---------");
console.log("Example Declaration let");
console.log("---------");
for (var i = 0; i < 2; i++) {
  try {
    console.log(b); //b is not declared yet => exception
  } catch (ex) {
    console.log("Exception in loop=" + ex);
  }
  let b = i;
  console.log("Now b is declared:"+b);
}
try {
  console.log(b); // b is not available out here as the scope of b is only the for loop. => exception
} catch (ex) {
  console.log("Exception outside loop=" + ex);
}
console.log("Done");
A lots of exceptions are thrown, as let has a more specific scope. Which leads to more intentional coding.

Finally, we see what happens when we use let and a async function within the loop.

Async function with let

/**
This example shows you the effects, if you use a async function within a loop. 
As the loop will be executed way under 100 milliseconds (which is the time out 
of the async function). let declares a new variable for each run of the loop, 
which will be untouched by upcoming runs.
**/
console.log("---------");
console.log("Example effects async let");
console.log("---------");
for (var i = 0; i < 2; i++) {
  let d = i;
  setTimeout(function() {
    console.log(d); //let does not redeclare, therefor d will not be modified by the next loop
  }, 100);
}

Conclusion

In your example, you always end up with the last assigned img element and the last assigned file. Your doing the same operation as many times as you have file in your array for the only the last file.

Sign up to request clarification or add additional context in comments.

5 Comments

Thank you!!! It did work! js looks simple, but is confusing, I fail ro undersend why var is not working))))
@Herr Derb Thank you for so detailed explanation! Now I see how it works. Is it right: every new function declaration (in each loop) gets the reference to the same img variable AND THEN (at the real call to the function) asignment to src property (of the same img var) happens? Then functions, that return functions, and are called at declaration make sense=) so I can make it work with just vars like this: reader.onload = (function(theImg){return function(e){theImg.src=reader.result;};})(img); It does the same as let, right?
Yes, this way you can "bind" the current variable value to the returning function. This would be the solution, if you would like to avoid let.
So if I use a funcstion that returns a new handler function, and is called in the loop with img var as argument to create a new local context variable unique to the returned handler function in the current loop, that would do the same as let, correct?
I think var creates global values, while let creates local values. an explanation can be found here: codeburst.io/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.