I can't manage to get both the result of the filereader and some parameters in a onload function. This is my code:
HTML of control:
<input type="file" id="files_input" multiple/>
Javascript function:
function openFiles(evt){
var files = evt.target.files;
for (var i = 0; i < files.length; i++) {
var file=files[i];
reader = new FileReader();
reader.onload = function(){
var data = $.csv.toArrays(this.result,{separator:'\t'});
};
reader.readAsText(file);
}
}
Add event:
files_input.addEventListener("change", openFiles, false);
I use the filereader.result, in the onload function. If I use a parameter, like file, for this function, I can't not access to the result anymore. For example I'd like to use file.name in the onload function. How to resolve this issue ?
Try wrapping your onload function in another function. Here the closure gives you access to each file being processed in turn via the variable f:
function openFiles(evt){
var files = evt.target.files;
for (var i = 0, len = files.length; i < len; i++) {
var file = files[i];
var reader = new FileReader();
reader.onload = (function(f) {
return function(e) {
// Here you can use `e.target.result` or `this.result`
// and `f.name`.
};
})(file);
reader.readAsText(file);
}
}
For a discussion of why a closure is required here see these related questions:
JavaScript closure inside loops – simple practical example
Javascript infamous Loop issue?
You should use closure at 'onload' handler.
Example: http://jsfiddle.net/2bjt7Lon/
reader.onload = (function (file) { // here we save variable 'file' in closure
return function (e) { // return handler function for 'onload' event
var data = this.result; // do some thing with data
}
})(file);
Use
var that = this;
to access external variables in the function scope.
function(){
that.externalVariable //now accessible using that.___
}
My scenario - Using Angular 9.
I struggled with this for a long time, I just couldn't seem to get it to work.
I found the following to be a really elegant solution to access external variables inside a function() block.
public _importRawData : any[];
importFile(file){
var reader = new FileReader();
reader.readAsArrayBuffer(file);
var data;
var that = this; //the important bit
reader.onloadend = await function(){
//read data
that._importRawData = data; //external variables are now available in the function
}
One of the important parts in the above code is the var keyword, which scopes variables outside the function block.
However, when I accessed the value of data after the function block, it was still undefined as the function executed after the other code. I tried async and await, but could not get it to work. And I could not access data outside of this function.
The saving grace was the var that = this line.
Using that allows external variables to be accessed inside the function. So I could set that variable inside the function scope and not worry about when the code gets executed. As soon as it has been read, it is available.
For the original question the code would be:
function openFiles(evt){
var files = evt.target.files;
for (var i = 0; i < files.length; i++) {
var file=files[i];
var that = this; //the magic happens
reader = new FileReader();
reader.onload = function(){
var data = $.csv.toArrays(this.result,{separator:'\t'});
that.file.name //or whatever you want to access.
};
reader.readAsText(file);
}
}
Event handling is asynchronous and thus they pick up the latest value of all the enclosed local variables(i.e. closure). To bind a particular local variable to the event, you need to follow the code suggested by users above or you can look at this working example:-
http://jsfiddle.net/sahilbatla/hjk3u2ee/
function openFiles(evt){
var files = evt.target.files;
for (var i = 0; i < files.length; i++) {
var file=files[i];
reader = new FileReader();
reader.onload = (function(file){
return function() {
console.log(file)
}
})(file);
reader.readAsText(file);
}
}
#Using jQuery document ready
$(function() {
files_input.addEventListener("change", openFiles, false);
});
For Typescript;
for (var i = 0; i < files.length; i++) {
var file = files[i];
var reader = new FileReader();
reader.onload = ((file: any) => {
return (e: Event) => {
//use "e" or "file"
}
})(file);
reader.readAsText(file);
}
As the variable file is within the scope, you may use the file variable without passing it to function.
function openFiles(evt){
var files = evt.target.files;
for (var i = 0; i < files.length; i++) {
var file=files[i];
reader = new FileReader();
reader.onload = function(){
alert(file.name);
alert(this.result);
};
reader.readAsText(file);
}
}
files_input.addEventListener("change", openFiles, false);
<input type="file" id="files_input" multiple/>
Related
I'm trying to make a file input that can handle multiple CSV files being uploaded at the same time. I loop through each file, run it through some data cleaning functions and then put it into a global array. My problem is that the array doesn't appear to update despite the fact that it appears updated when I console.log it.
Here is a recreation of my problem.
My HTML:
<input type="file" id="myInput" multiple>
And my code:
GLOBALARR = [];
$('#myInput').on('change',function(e) {
files = e.target.files;
for (var i = 0; i < files.length; i++) {
var reader = new FileReader();
reader.readAsText(files[i]);
reader.onload = function(loadEvent) {
var csv = loadEvent.target.result;
pushFileContentsToArray(csv);
}
}
checkArray();
});
function pushFileContentsToArray(csv) {
GLOBALARR.push(csv);
}
function checkArray() {
console.log(GLOBALARR);
console.log(GLOBALARR.length);
}
Notice that the console.log(GLOBALARR) outputs the updated array, but the console.log(GLOBALARR.length) outputs 0 as the length. When I try to work with the elements in the array, I get undefined errors and whatnot, as if the array is still empty.
Can someone help me understand what is going on?
onload is an async operation, so you're calling checkArray() before the file has been read. To fix this, move the checkArray() call to just after the pushFileContentsToArray() call:
$('#myInput').on('change', function(e) {
files = e.target.files;
for (var i = 0; i < files.length; i++) {
var reader = new FileReader();
reader.readAsText(files[i]);
reader.onload = function(loadEvent) {
var csv = loadEvent.target.result;
pushFileContentsToArray(csv);
checkArray();
}
}
});
Obviously this is going to perform this logic for every file you read. If you want to only call checkArray() once all files have been read you could create your own Promise and resolve it after onload has fired for all files, something like this:
$('#myInput').on('change', function(e) {
let files = e.target.files;
let filesRead = 0;
for (var i = 0; i < files.length; i++) {
var reader = new FileReader();
reader.readAsText(files[i]);
reader.onload = function(loadEvent) {
var csv = loadEvent.target.result;
pushFileContentsToArray(csv);
if (++filesRead === files.length);
checkArray();
}
}
});
I try to read files uploaded by a input type file (multiple). The code is the following:
$(document).ready(function() {
$('#convert').on('click', function() {
var files=$('#files')[0].files;
if (!files) return;
for (var i=0; i<files.length; i++) {
var file=files[i];
fr = new FileReader();
fr.onload = (function(received) {
var note=$(fr.result);
});
fr.readAsText(file);
}
});
});
Now my problem is:
The "onload" - function is called even before the file is loaded. note never has any content. But when I put a breakpoint just before the note - line and wait a while, note gets the content.
So it seems, the onload()-event is called too early. What can I do about that?
(Browser is Chrome)
I replaced fr.result by this.result:
fr.onload = (function() {
var note=$(this.result);
});
That did the trick
You are making fr a global variable and at the time the onload fires it will be a different object than you expect due to the loop
Try wrapping it in an IIFE to create a closure and making fr a local variable
for (var i = 0; i < files.length; i++) {
(function(file) {
var fr = new FileReader();
fr.onload = function(received) {
var note = $(fr.result);// not sure what intent is here
};
fr.readAsText(file);
})(files[i])
}
I am doing a simple file text upload using FileReader.
var filesInput = document.getElementById("txtImport");
for (var i = 0; i < filesInput.files.length; i++) {
current = filesInput.files[i];
var reader = new FileReader();
reader.onload = function(file) {
return function(e) {
console.log('e', e) // not logging
}
}(current)
}
Upon reading FileReader onload with result and parameter, I need to use closure so as to not lose the scope inside the loop. When I click the button to trigger the upload, why is the log not coming up? Why isn't the function firing?
You need to call one of the readAs___ methods of the FileReader:
https://developer.mozilla.org/en-US/docs/Web/API/FileReader
If you're reading multiple files parallel, you need a separate reader for each.
Also, the parameter the event handler receives is an event object, not the contents of the file. Those will be in reader.result.
for (var i = 0; i < filesInput.files.length; i++) {
let reader = new FileReader();
reader.onload = function(event) {
console.log(reader.result);
}
reader.readAsText(filesInput.files[i]);
}
Looking at HTML FileReader. I am struggling to extract the data from the fileReader. All examples I see use the data directly from the inbuilt FileReader function. I'm trying to pull the data out of the filereader and store it in the parent 'class'. However I have been unsuccessful.
function constructTS(name){
// Variables
this.name = name;
this.csv = "";
}
constructTS.prototype.load = function(files){
if (window.FileReader) {
// FileReader are supported.
var reader = new FileReader(); //Exe#1
reader.onload = function(e){ //Exe#2
var csv = reader.result //Exe#5
var allTextLines = csv.split(/\r\n|\n/); //Exe#6
var lines = []; //Exe#7
while (allTextLines.length) {
lines.push(allTextLines.shift().split(','));
}; //Exe#8
this.lines = lines; //Exe#9
};
var x = reader.readAsText(files); //Exe#3
} else {
alert('FileReader yeah.. browser issues.');
};
alert(reader.lines[0]); //Exe#4
};
The this in this.lines = lines; refers to the Filereader class and not the constructTS class. Thus the information is not stored. I also find it a little strange how it runs the entire function and then only reads in the file afterwards. I guess that is helpful for web functionality. Any idea how I can load the data into the class?
In JavaScript this references the closure context:
this in the reader.onload is the context of the onload method, so reader.
In your situation :
function constructTS(name){
// Variables
this.name = name;
this.csv = "";
}
constructTS.prototype.load = function(files){
var that = this; //keep a reference on the current context
if (window.FileReader) {
// FileReader are supported.
var reader = new FileReader(); //Exe#1
reader.onload = function(e){ //Exe#2
var csv = reader.result //Exe#5
var allTextLines = csv.split(/\r\n|\n/); //Exe#6
var lines = []; //Exe#7
while (allTextLines.length) {
lines.push(allTextLines.shift().split(','));
}; //Exe#8
that.lines = lines; //Exe#9
};
var x = reader.readAsText(files); //Exe#3
} else {
alert('FileReader yeah.. browser issues.');
};
alert(reader.lines[0]); //Exe#4
};
To understand this in JavaScript, there's a lot of resources, like https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/this
this refers to your unnamed callback function here. Try this pattern:
var outer = this;
reader.onload = function(e){
...
outer.lines = lines;
}
// you can use outer.lines or this.lines here now (they're the same)
In this javascript/jquery code I attempt to read multiple files and store them in a dictionary.
function handleFileSelect(evt) {
var files = evt.target.files; // FileList object
var f, filename;
for (var i = 0; i<files.length; i++) {
f = files[i];
filename = escape(f.name);
if (filename.toLowerCase().endsWith(".csv")) {
var reader = new FileReader();
// Closure to capture the file information.
reader.onload = (function(e) {
var text = reader.result;
var arrays = $.csv.toArrays(text);
frequencies[filename] = arrays;
generateMenuFromData();
});
// Read in the image file as a data URL.
reader.readAsText(f);
}
}
}
I read only the .csv files. I want to run generateMenuFromData(); only on the last time the reader.onload function runs.
I can't find a good way to do this properly. Does anyone know how?
Thanks.
Increase a counter inside the event handler. If it is the same the length of the array, execute the function. A more structured approach would be to use promises, but in this simple case it would suffice:
function handleFileSelect(evt) {
var files = evt.target.files;
var f, filename, loaded = 0;
for (var i = 0; i<files.length; i++) {
f = files[i];
filename = escape(f.name);
if (filename.toLowerCase().endsWith(".csv")) {
var reader = new FileReader();
reader.onload = (function(filename, reader) {
return function(e) {
frequencies[filename] = $.csv.toArrays(reader.result);
loaded += 1; // increase counter
if (loaded === files.length) {
// execute function once all files are loaded
generateMenuFromData();
}
};
}(filename, reader)); // <-- new scope, "capture" variable values
reader.readAsText(f);
}
}
}
Now, your real problem might be that you are creating a closure inside the loop. That means when the load event handlers are called, filename and reader will refer to the values the variable had in the last iteration of the loop. All handlers share the same variables.
See also Javascript closure inside loops - simple practical example.