Question

I was looking at the new HTML5 file API for showing a preview of an image to be uploaded. I googled for some code and almost every example had the same structure, almost the same code. I don't mind copying, particularly when it works, but I need to understand it. So I tried to understand the code but I am stuck with one area and need someone to explain that small part:

The code refers to a HTML form input field and when the file is selected shows the preview image in a img tag. Nothing fancy. Simple. Here it is after removing all the noise:

 $('input[type=file]').change(function(e) {
     var elem = $(this);
     var file = e.target.files[0];
     var reader = new FileReader();

   //Part I could not understand starts here
   reader.onload = (function(theFile) {
     return function(e) {
        var image_file = e.target.result            
        $('#img_id').attr('src',image_file);
     };
   })(file);
   reader.readAsDataURL(file);
   //Upto here
 });

I think that reader.onload needs to be assigned a plain event handler, so I replaced the entire section marked above to:

    reader.readAsDataURL(file);
    reader.onload = function(e) {
      var image_file = e.target.result;         
      //#img_id is the id of an img tag 
       $('#img_id').attr('src',image_file)
    };

And it worked as I expected it to work.

QUESTION: What's the original code doing that the above simplified code is missing? I understand that it is a function expression returning a function and then calling it… but for what? There is just too much of the original code copied around under tutorials and what not on it but no good explanation. Please explain. Thanks

Was it helpful?

Solution

Sure.
The function-wrapped function, here serves the specific purpose of remembering which file it was you were looking at.

This might be less of a problem using your exact codebase, but if you had a multiple-upload widget, and you wanted to display a row of previews:

var my_files = [].slice.call(file_input.files),

    file, reader,

    i = 0, l = my_files.length;


for (; i < l; i += 1) {
    file = my_files[i];
    reader = new FileReader();

    // always put the handler first (upside down), because this is async
    // if written normally and the operation finishes first (ie:cached response)
    // then handler never gets called
    reader.onload = function (e) {
        var blob_url = e.target.result,
            img = new Image();

        img.src = blob_url;
        document.body.appendChild(img);
    };
    reader.readAsDataUrl(file);
}

That should all work fine. ...except... And it's clean and readable.

The issue that was being resolved is simply this:

We're dealing with async-handlers, which means that the value of file isn't necessarily the same when the callback fires, as it was before...

There are a number of ways to potentially solve that.
Pretty much all of them that don't generate random ids/sequence-numbers/time-based hashes to check against on return, rely on closure.

And why would I want to create a whole management-system, when I could wrap it in a function and be done?

var save_file_reference_and_return_new_handler = function (given_file) {
    return function (e) {
        var blob_url  = e.target.result,
            file_name = given_file.name;

        //...
    };
};

So if you had that at the top of the function (above the loop), you could say:

reader = new FileReader();
reader.onload = save_file_reference_and_return_new_handler(file);
reader.readAsDataUrl(file);

And now it will work just fine.

Of course, JS people don't always feel compelled to write named functions, just to store one item in closure to remember later...

reader.onload = (function (current_file) {
    return function (e) {
        var blob_url  = e.target.result,
            file_name = current_file.name;
    };
}(file));
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top