HTML5 Javascript FileReader - extract data into parent function - javascript

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)

Related

Why am I missing elements in my array from array.from()

I am currently building a large database from an imported XML document, about 13k items or so. When I do
window.onload = function() {
var fileInput = document.getElementById('fileInput');
var fileDisplayArea = document.getElementById('fileDisplayArea');
fileInput.addEventListener('change', function(e) {
var file = fileInput.files[0];
var textType = /text.*/;
if (file.type.match(textType)) {
var reader = new FileReader();
reader.onload = function(e) {
let text = reader.result;
let parser = new DOMParser();
xmlDoc = parser.parseFromString(text,"application/xml");
getReportList(xmlDoc);
}
reader.readAsText(file);
} else {
fileDisplayArea.innerText = "File not supported!";
}
});
}
function isGroupTag(arr) {
return arr.nodeName === "Group";
}
function getReportList(xml) {
// Creates an array containing all reports
let reportList = Array.from(xmlDoc.children[0].children).filter(isGroupTag);
allReports = reportList.map((currElement, index) => {
return getReport(xml, index);
});
}
I only get back about 2100? I checked my program using a much smaller XML document and every thing was loaded fine. Any ideas as to why I would be getting such a small return back?
My first thought was that the XML document wasn't loaded completely yet so when I did Array.from() it was building the array with what it had...not sure though. Any help would be great.
EDIT:
I just finished testing loading in a few more XML documents. Instead of one massive file I loaded in the yearly ones and everything shows up. Still not sure why I can't just do the massive one.

FileReader onload with result and parameter

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/>

How to run code on last iteration of html5 read file method?

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.

Passing file contents to outside variable

I'm using a FileReader and the HTML file dialog to read a file in my script. How do I pass this file's contents out of the FileReader.onload function?
function readFileData(evt) {
var file = evt.target.files[0];
var reader = new FileReader();
reader.onload = function(e) {
var contents = e.target.result;
}
reader.readAsText(file);
}
document.getElementById('file').addEventListener
('change', readFileData, false);
/* I want to access the contents here */
I tried sticking returns in the readFileData and onload functions, but I'm not sure what they return to.
I assume that you know, its async and all.
So, the short answer is: No, you can not do that.
However, if you want the contents to be globally accessible for any future calls, you could something like this:-
var contents;// declared `contents` outside
function readFileData(evt) {
var file = evt.target.files[0];
var reader = new FileReader();
reader.onload = function(e) {
contents = e.target.result; //<-- I removed the `var` keyword
}
reader.readAsText(file);
}
document.getElementById('file').addEventListener('change', readFileData, false);
var reasonableTimeToWaitForFileToLoad = 100000;
console.log(contents); //`contents` access first attempt: prints undefined
setTimeout(function() {
console.log(contents);//`contents` access second attempt: prints the contents 'may be if the time allows.'
}, reasonableTimeToWaitForFileToLoad);
var contents;
function readFileData(evt) {
var file = evt.target.files[0];
var reader = new FileReader();
reader.onload = function(e) {
contents = e.target.result;
}
reader.readAsText(file);
reader.onloadend=function(e) {
console.log(contents)
}
}
document.getElementById('file').addEventListener('change', readFileData, false);
This is a scoping issue. When you're declaring contents within the onload, it's no longer available after that function has run. You need to declare contents outside of that scope first.
var contents;
function readFileData(evt) {
var file = evt.target.files[0];
var reader = new FileReader();
reader.onload = function(e) {
contents = e.target.result;
}
reader.readAsText(file);
}
document.getElementById('file').addEventListener
('change', readFileData, false);
//make changes here
//contents should have the correct value
Firstly, you have to realize that reading a file with a FileReader is an asynchronous task, and you cannot work with it in a synchronous manner.
There are many ways to handle this, but many of them are not suited for recommendations ;)
I would do it one of these 2 ways:
1: you can call a function from within the onload event handler and pass the file contents as a parameter
2: you can trigger an event from within the onload event handler and pass the file contents as event data
Just declare contents outside both functions and assign to it inside the inner function:
var contents;
var outer_fn = function() {
var inner_fn = function() {
contents = '42';
}
inner_fn();
}
outer_fn();
// here, `contents' is '42'
I faced a similar challenge and this is what I used to solve the issue.
var contents;
function readFileData(evt) {
var file = evt.target.files[0];
var reader = new FileReader();
reader.onload = function(e) {
contents = e.target.result;
}
reader.readAsText(file);
//calling to access the 'contents' variable
accessFileContents();
}
document.getElementById('file').addEventListener('change', readFileData, false);
var wait4file2load = 1000;
/* To access 'contents' here */
function accessFileContents(){
setTimeout(function(){
console.log(contents);
}, wait4file2load);
}
It won't give undefined value since we are calling it after the file is completely uploaded.
I had a similar problem in Angular 7 (typescript), and this is how I solved my problem.
What I wanted to do was to access the base64 conversion that was happening inside fileReader -> reader.onload
Then pass that parameter to another method where I could convert it to a JSON object then post it to the API seeing I want to post another parameter as well in the post. (not added in this code)
What I did first was to declare what I potentially needed to access outside the Method that.
base: any = null;
base64File: any = null;
fileToTest: any = null;
Then I converted the pdf to base64 when the upload event fired
convertToBase64 (e){
this.fileToTest = e.target.files[0];
var reader = new FileReader();
reader.onload = () => {
this.base64File = reader.result.slice(28);
};
reader.onerror = function (error) {
console.log('Error: ', error);
}.bind(this.base64File);
reader.readAsDataURL(this.fileToTest);
return this.base64File;
}
Finally access the base64 file in the other method
onSubmit() {
console.log("base 64 file is visible", this.base64File);
var base =
{
"FileBase64": this.base64File,
"Path": "document",
"FileType": ".pdf"
};
console.log("JSON object visible", base);
this.uploadService.base64Post(base);
}
Everything works now, and hopefully maybe this can help someone else finding themselves with the same problem.
Using Angular 7, code is in the component file, and the post function is in the Service file. Without my comments the code is exactly like this in the component file.

HTML5 File API: get File object within FileReader callback

With the new File API in Javascript you can read files in Javascript to create dataURLs to show clientside pictures clientside. I'm wondering if you can reach the File object within the FileReader's onload callback.
I will illustrate this with an example:
var div = document.createElement('div');
div.ondrop = function(e) {
e.preventDefault();
e.stopPropagation();
var files = e.dataTransfer.files;
for ( var i=0; i<files.length; i++ ) {
var file = files[i]; // this is the file I want!!
var filereader = new FileReader();
filereader.onload = function(e) {
this; // the FileReader object
e.target; // the same FileReader object
this.result; // the dataURL, something like data:image/jpeg;base64,.....
var img = document.createElement('img');
img.src = this.result;
img.title = file.fileName; // This won't be working
document.appendChild(img);
}
}
return false;
}
What I could do - what I do right now - is wrap the contents of the for loop in a function and execute it to create a new scope and keep a file in that scope like so:
for ( var i=0; i<files.length; i++ ) {
var _file = files[i]; // this is the file I want!!
(function(file) {
// do FileReader stuff here
})(_file);
}
I was just wondering... Maybe I'm missing something. Is there a way to get the File object from within the onload function of the FileReader? Both this and e.target are the FileReader object and not the File. Is there something in this or e that is the File?? I can't find it :(
Thanks a bunch.
PS. A fiddle: http://jsfiddle.net/rudiedirkx/ZWMRd/1/
I already found a way. Maybe not better than the scope wrapper, but I think it's neater:
for ( var i=0; i<files.length; i++ ) {
var file = files[i]; // this is the file I want!!
var filereader = new FileReader();
filereader.file = file;
filereader.onload = function(e) {
var file = this.file; // there it is!
// do stuff
}
}
There is now a much easier (apparently faster) way (sync!) to get a file's data URL:
img.src = URL.createObjectURL(file);
Demo on http://jsfiddle.net/rudiedirkx/ZWMRd/8/show/ of both methods, and the original problem illustrated (drag multiple images and check the title tooltips).
I don't think this.file is still supported. When I try to run the answer code, this.file is undefined whereas if I run the code from the question I get the expected results. I think you have to use the closure (at least this is how they do it on html5rocks (Example)).

Categories

Resources