I am trying to do something like this:
JavaScript:
function readMultipleFiles(evt) {
//Retrieve all the files from the FileList object
var files = evt.target.files;
window.array = []
if (files) {
for (var i = 0, f; f = files[i]; i++) {
var r = new FileReader();
r.onload = (function (f) {
return function (e) {
var contents = e.target.result;
window.array.push(contents);
};
})(f);
r.readAsText(f);
}
alert(window.array);
} else {
alert("Failed to load files");
}
}
document.getElementById('fileinput').addEventListener('change', readMultipleFiles, false);
HTML:
<input type="file" id="fileinput" multiple />
So that I can read local files locally.
However, instead I want to store the file contents in an array of this format:
[file1Contents, file2Contents, file3Contents... etc]
How would I go about this? I tried setting a global variable (e.g. window.fileContents) and updating inside the on.load, but that did not work
Thanks
I'm not exactly sure what you're looking to do, but I'll give it a swing. I see that you're already reading the file as text.. could you not just shove that into the array there? I've modified your code below:
var masterFileArray = []; // where I will store the contents
function readMultipleFiles(evt) {
//Retrieve all the files from the FileList object
var files = evt.target.files;
window.array = []
if (files) {
for (var i = 0, f; f = files[i]; i++) {
var r = new FileReader();
r.onload = (function (f) {
return function (e) {
var contents = e.target.result;
window.array.push(contents);
masterFileArray.append({name:f.name, contents: contents}); // storing as object
};
})(f);
r.readAsText(f);
}
console.log(masterFileArray);
} else {
alert("Failed to load files");
}
}
document.getElementById('fileinput').addEventListener('change', readMultipleFiles, false);
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 am trying to post javascript array of object in an ajax call but i get string value "[]". When i try to console.log the array lenght it says zero.
Following is the code i am using
var masterFileArray = []; // where I will store the contents
function readMultipleFiles(evt) {
//Retrieve all the files from the FileList object
var files = evt.target.files;
if (files) {
for (var i = 0, f; f = files[i]; i++) {
var r = new FileReader();
r.onload = (function (f) {
return function (e) {
var contents = e.target.result;
masterFileArray.push({name:f.name, contents: contents, type:f.type, size:f.size}); // storing as object
};
})(f);
r.readAsText(f);
}
console.log(masterFileArray);
new Ajax.Request('fileupload.php', {
method: 'post',
parameters: {files: JSON.stringify(masterFileArray)},
onSuccess: function(transport){
var response = transport.responseText;
console.log(response);
}
});
} else {
alert('Failed to load files');
}
}
document.getElementById('upfiles').addEventListener('change', readMultipleFiles, false);
Thats how it looks like on inspection
What i am doing wrong? Any help would be appreciated, Thank you.
You can post after reading finished,
Here introduced left_loaded_count to get the status of reading.
Try like this.
var masterFileArray = []; // where I will store the contents
var left_loaded_count = 0;
function readMultipleFiles(evt) {
//Retrieve all the files from the FileList object
var files = evt.target.files;
if (files) {
for (var i = 0, f; f = files[i]; i++) {
var r = new FileReader();
r.onload = (function (f) {
return function (e) {
var contents = e.target.result;
masterFileArray.push({name:f.name, contents: contents, type:f.type, size:f.size}); // storing as object
left_loaded_count -= 1;
if(left_loaded_count == 0)
{
console.log(masterFileArray);
new Ajax.Request('fileupload.php', {
method: 'post',
parameters: {files: JSON.stringify(masterFileArray)},
onSuccess: function(transport){
var response = transport.responseText;
console.log(response);
}
});
}
};
})(f);
left_loaded_count += 1;
r.readAsText(f);
}
} else {
alert('Failed to load files');
}
}
document.getElementById('upfiles').addEventListener('change', readMultipleFiles, false);
readAsText() is an asynchronous operation, but you proceed with the AJAX call right away instead of waiting for the read operations to finish. That's why your console.log(masterFileArray) prints an empty array, when it runs none of the operations have finished and the array is still empty.
The best way to solve this is to wrap each file read operation in a promise and then proceed with the AJAX call once all these promises resolve.
Get rid of var masterFileArray = [] and change your code within the if (files) { ... } block to this:
Promise.all(files.map(function(f) {
return new Promise(function(resolve) {
var r = new FileReader();
r.onload = function (e) {
var contents = e.target.result;
resolve({name:f.name, contents: contents, type:f.type, size:f.size}); // resolve promise with object
};
r.readAsText(f);
});
})).then(function(masterFileArray) {
// All promises have resolved and their results have been collected in masterFileArray
console.log(masterFileArray);
new Ajax.Request('fileupload.php', {
method: 'post',
parameters: {files: JSON.stringify(masterFileArray)},
onSuccess: function(transport){
var response = transport.responseText;
console.log(response);
}
);
});
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/>
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.
I have a list of files I need to save and in addition to the name I need to send the readAsDataURL to the server as well.
The problem is I am not sure how to do it with the async nature of readAsDataURL. Because to save the DATAURL to the array I need to look up the name of the file which is in the files list. and I cannot pass the file to the async method of readAsDataURL. How do you write this properly to work? The end result is I want a list of files sent to the server in one JSZip file.
function saveFileList(files)
{
for (var i = 0, file; file = files[i]; i++) {
var fr = new FileReader();
fr.onload = function(e){
if (e.target.readyState == FileReader.DONE) {
var tt = e.target.result.split(",")[1];
//update the record in the list with the result
}
};
var pp = fr.readAsDataURL(file);
}
If you have FileList and you need to get array of base64 string, you need do this
export async function fileListToBase64(fileList) {
// create function which return resolved promise
// with data:base64 string
function getBase64(file) {
const reader = new FileReader()
return new Promise(resolve => {
reader.onload = ev => {
resolve(ev.target.result)
}
reader.readAsDataURL(file)
})
}
// here will be array of promisified functions
const promises = []
// loop through fileList with for loop
for (let i = 0; i < fileList.length; i++) {
promises.push(getBase64(fileList[i]))
}
// array with base64 strings
return await Promise.all(promises)
}
use it like this
const arrayOfBase64 = await fileListToBase64(yourFileList)
You need another function around it, so you can pass the file in. Try this:
var reader = new FileReader();
reader.onload = (function(theFile) {
return function(e) {
if(reader.readyState == FileReader.DONE)
alert(theFile.name); // The file that was passed in.
}
};
})(file);
reader.readAsDataURL(file);
An alternative to Russell G's answer:
var reader = new FileReader();
reader.onload = function(event){
payload = event.target.result;
var filename = file.name, filetype = file.type;//etc
//trigger a custom event or execute a callback here to send your data to server.
};
reader.onerror = function(event){
//handle any error in here.
};
reader.readAsDataURL(file);