25

I need to read some csv files, given by the user. Files are passed to the page/script using a drag and drop div, that handles the file drop as follows:

function handleFileDrop(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    var files = evt.dataTransfer.files; // FileList object.
    ...
}

I need to parse each file with a csv library that converts it into an array, but I also need to keep track of the file name I'm currently parsing. Here's the code I use to parse each file:

for(var x = 0; x < files.length; x++){
    var currFile = files[x];
    var fileName = currFile.name;
    var reader = new FileReader();
    reader.onload = (function(theFile){
        return function(e){
            var csvArr = CSV.csvToArray( e.target.result, ";", true );
            console.log(csvArr); 
        };
    })(currFile);   
    reader.readAsText(currFile);
}

Until this, everything works great. What I need is to also pass the filename to the reader.onload event, eg:

reader.onload = (function(theFile){
    return function(e){

       ***** I need to have fileName value HERE *****

    };
})(currFile); 

Is possible? How can I do this? Thanks in advance for any help, best regards

BeNdErR
  • 17,471
  • 21
  • 72
  • 103

2 Answers2

56

Try the following:

var reader = new FileReader();
reader.onload = (function(theFile){
    var fileName = theFile.name;
    return function(e){
        console.log(fileName);
        console.log(e.target.result);
    };
})(currFile);   
reader.readAsText(currFile);

Here, you're creating a new fileName variable each time a file is passed to the outer method. You're then creating a function which has access that variable (due to the closure) and returning it.

Graham
  • 6,484
  • 2
  • 35
  • 39
  • Genius! Thanks so much, I was pulling my hair out trying to do this. I think I'm starting to understand closures now! – Fiver Apr 28 '14 at 12:52
  • 4
    How is it possible to get both the filename and the reader.result value ? – PatriceG Dec 05 '14 at 13:01
  • I'm having trouble following the code, where is 'theFile' coming from ? I can see where currFile is defined but what does 'theFile' relate to ? – NiallJG May 06 '16 at 22:21
  • 1
    @NiallJG If you look at what is assigned to the `onload` property, we're actually executing a function and passing in `currFile`. Inside that function, `theFile` is just a different name for the file that we've passed in. This allows us to store a reference to that file's name, which can be accessed once the file contents are actually read. – Graham May 09 '16 at 09:10
  • @sandoval31 Using the `e.target.result` property - I've updated the answer to reflect this. – Graham May 09 '16 at 09:13
  • Is this considered closure? I don't t think that I have seen such syntax before (with `currFile` as argument in the end) and I just recently read about closures and this seems similiar. – Jānis May 27 '16 at 13:54
  • 1
    @John The inner function is a closure, as it retains a reference to the `fileName` variable after it is returned. The syntax you're looking at is often refered to as an [Immediately-invoked Function expression](https://en.wikipedia.org/wiki/Immediately-invoked_function_expression) or IIFE for short. – Graham May 30 '16 at 07:11
  • Hey @Graham, first, this is a beautiful answer. If you have a list of files, does that mean you need to reassign onLoad for every file since we are passing the currFile to the closure? Thank you! – Antuan Feb 15 '19 at 21:53
  • 1
    @Antuan If you have an array of files, and are reading them sequentially, then it might be better to take a different approach and create a counter variable that refers to the current array index. Then you'd just have one `onload` that would be able to access the original array of files and therefore the names. See https://codepen.io/anon/pen/jdXJRY?editors=1011 as a basic example. If the files are large, then you may insetad consider a FileReader for each file and read them inside Workers to avoid locking the browser. – Graham Feb 18 '19 at 13:19
  • @Graham thank you so much for taking the time to reply and for making an example for me. I ended up having an onLoad for each file ~10 files, but I'll switch to this approach. It's more elegant. Thanks again! – Antuan Feb 18 '19 at 17:08
2

Use Blob API

Using the new File / Blob API it can be done much easier.

Your second code block - also solving your problem - could be rewritten as:

for (let file of files){
 (new Blob([file])).text().then(x=>console.log(file.name,x));
}

The Blob API uses Promises instead of Events like the Filereader API, so much less troubles with closures. Also the Arrow function (x=>) and iterator for of, make the code much more concise.

Check support.

Use Fetch API

Also using the Fetch API Response Object it can be done more easy.

for (let file of files){
 (new Response(file)).text().then(x=>console.log(file.name,x));
}

Note of the missing Array [] in the constructor.

Also check support.

Janghou
  • 1,613
  • 1
  • 21
  • 30