JS function returns null - help - javascript

I am trying to modify the example http://www.html5rocks.com/en/tutorials/file/dndfiles/#toc-reading-files to make function handleFileSelect(evt) return reader.result; I mean to make function return base64 for image or so.
I tried to write it with function but it returns null only :(
So my question is how to make the function return base64?
As for now I tried to write this snippet...
function handleFileSelect(evt) {
var files = evt.target.files; // FileList object
// Loop through the FileList and render image files as thumbnails.
for (var i = 0, f; f = files[i]; i++) {
// Only process image files.
if (!f.type.match('image.*')) {
continue;
}
var reader = new FileReader();
// Closure to capture the file information.
reader.onload = (function(theFile) {
return function(e) {
// Render thumbnail.
var span = document.createElement('span');
span.innerHTML = ['<img class="thumb" src="', e.target.result,
'" title="', theFile.name, '"/>'].join('');
document.getElementById('list').insertBefore(span, null);
};
})(f);
// Read in the image file as a data URL.
reader.readAsDataURL(f);
}
return reader.result;
}
All useful comments are appreciated :)

I don't know the FileReader object in detail, but it looks like it reads data from a URL asynchronously. That means that when your function returns reader.result, the FileReader object isn't done reading the file yet. That doesn't finish until the onload callback is called (or some other error condition occurs).
So, your function returns while the reading is still happening asynchronously. Thus the result has not yet been determined. The result would be available on side the onload callback or (I'm guessing), inside other callbacks that would signify error conditions (onabort, onerror, etc..).

Related

Access element from JavaScript FileReader

I am trying to read a file with in JavaScript with the File APIs.
I have the following code
function handleFileSelect(evt) {
var files = evt.target.files; // FileList object
var fileBlobs = [];
var reader = new FileReader();
for (var i = 0, f; f = files[i]; i++) {
fileBlobs.push(reader.readAsBinaryString(f));
console.log(reader)
}
}
It logs me this object
FileReader {onloadend: null, onerror: null, onabort: null, onload: null, onprogress: null…}
error: null
onabort: null
onerror: null
onload: null
onloadend: null
onloadstart: null
onprogress: null
readyState: 2
result: "lat, lng, popup↵13.47262306516617336, 52.47896324136591062, 55↵13.40861762468653673, 52.54336741663770027, 44↵13.29255442595013115, 52.51117712705399754, 33↵13.38642907198692988, 52.44880630287082113, 22"
__proto__: FileReader
How can I access only the result part?
NOT tested, but i think it should work :)
var file = document.getElementById('element-id').files;
var fr = new FileReader();
var storeBlobs = [];
fr.onload = function(e) {
storeBlobs.push(e.target.result);
};
for(var i = 0, l = file.length; i < l; i++) {
var content = fr.readAsText(file[i]);
}
File returns an array of objects which can be then manipulated trough FileReader object with onload method. If user loads more than one file we want to process all of it, and we will do that with for loop. I emphasize that this code is not tested, but it should work.
We can, if we assume that user will load one file only make file object to behave like this with replacing first line of code with this:
var file = document.getElementById('element-id').files[0];
You need to handle the load event of the FileReader. In the handler, access reader.result or this.result:
reader.onload = function() {
var contents = this.result;
};
Reading the files is an asynchronous operation. So, you need to create a separate FileReader for each file. If you want to reuse the same FileReader, you can't use a for loop. Instead, you must wait to read the next file until after the current file is processed.

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.

Callback function after reading multiple files

I am doing something similar to http://www.html5rocks.com/en/tutorials/file/dndfiles/
What I'm doing is Im reading the contents of the selected files one at a time to validate that their lines pass some regex test. After done validating all files, I need to update (enable / disable) some buttons accordingly hence the call back function
Is it possible to have a call back function which will do something after everything is read?
HTML:
<input type="file" id="files" name="files[]" multiple />
Javascipt:
<script>
function handleFileSelect(evt) {
var files = evt.target.files; // FileList object
// files is a FileList of File objects. List some properties.
var validArray = [];
for (var i = 0, f; f = files[i]; i++) {
//Create new file reader
var r = new FileReader();
//On load call
r.onload = (function (f) {
return function (e) {
var contents = e.target.result;
var lines = contents.split('\n');
for(var i=0; i<lines.length; i++){
//Validate regex of line here
//If line does not pass, append file name to validArray and break
}
};
})(f);
r.readAsText(f);
}
}
document.getElementById('files').addEventListener('change', handleFileSelect, false);
</script>
Came here looking for a similar answer. I wanted to call a function after all files were loaded and processed. The solution provided by #Snuffleupagus did not work for me because the function was called after all the files were read, but before they had finished being processed in the onload function. I found a solution around this as follows (not sure if it is the 'cleanest' but it works for me).
var processedCount=0; // global variable
var totalFiles = 0; // global variable
function handleFileSelect(evt) {
var files = evt.target.files; // FileList object
totalFiles = files.length; // important
// files is a FileList of File objects. List some properties.
for (var i = 0, f; f = files[i]; i++) {
//Create new file reader
var r = new FileReader();
//On load call
r.onload = (function(theFile){
return function(){
onLoadHandler(this,theFile);
onLoadEndHandler();
};
})(f);
r.readAsText(f);
}
}
function onLoadEndHandler(){
processedCount++;
if(processedCount == totalFiles){
// do whatever - this code will run after everything has been loaded and processed
}
}
I tried to use r.onloadend but it was called too soon. I believe because my function 'onLoadHandler' takes a few seconds to process each file and onloadend is called when the file is done being loaded but before the code within 'onload' has finished running.
Absolutely. Callbacks are just passed as any other normal argument would be, so we'll end up adding another argument to handleFileSelect and changing the event listener to an anonymous function that calls handleFileSelect with the extra argument.
I set up a fiddle to give you a quick working demo.
function handleFileSelect(evt, cb) {
var files = evt.target.files; // FileList object
// files is a FileList of File objects. List some properties.
var output = [];
for (var i = 0, f; f = files[i]; i++) {
output.push('<li><strong>'+ escape(f.name) + '</strong>');
}
document.getElementById('list').innerHTML = '<ul>' + output.join('') + '</ul>';
if(cb) cb();
}
document.getElementById('files').addEventListener('change', function(e){handleFileSelect(e, function(){alert('all done');})}, false);​
Breaking it down - added an extra argument to handleFileSelect and at the end added if(cb) cb();. That just checks to see if cb exists, if it does, run it as a function.
Then when we go to bind the event handler instead of passing a reference to handleFileSelect we use an anonymous function - this lets us pass our extra argument.
The anonymous function inside of the anonymous function is just our callback, it could be a reference to a function if you'd rather.
A really clean way to do this is to use async.js reduce method. Async.js gives many nice ways to deal with multiple callbacks. You could use reduce to iterate through the array of file names, and build a reduced value which is an array of the valid lines:
<input type="file" id="files" name="files[]" multiple />
<script type='text/javascript' src='https://github.com/caolan/async/raw/master/lib/async.js'/>
<script>
var isValidLine = function(text){
// todo: implement
}
function handleFileSelect(evt) {
var files = evt.target.files; // FileList object
// reduce by starting with empty array in second argument -
// this gets built up with the valid array lines
async.reduce(files, [], function(validLinesSoFar, file, callback){
var r = new FileReader();
// Read file here:
r.onload = function (f) {
var contents = f.target.result;
var lines = contents.split('\n');
for(var i=0; i<lines.length; i++){
if isValidLine(lines[i])
validLinesSoFar.push(lines[i]);
}
callback(null, validLinesSoFar);
};
r.readAsText(file);
}, function(err, validLines){
// gets called after every file iterated through
// result is entire valid array
// do something here with valid array
});
}
document.getElementById('files').addEventListener('change', handleFileSelect, false);
</script>
I would take a look at jQuery's deferred object
Also a very relevant question that might be applicable to you.
How to fire a callback function after a for-loop is done in Jquery?

Examine contents of Javascript variable after some action

I have a function that is supposed to read from a file into a variable
I want to know the validity of the reads and was wondering if there was any way I could examine the contents of the variable after the upload action has been performed.
function handleFileSelect(evt) {
var files = evt.target.files; // FileList object
// Loop through the FileList
for (var i = 0, f; f = files[i]; i++) {
var reader = new FileReader();
parser=new DOMParser();
// Closure to capture the file information.
reader.onload = (function(theFile) {
return function(e) {
// Print the contents of the file
// var span = document.createElement('span');
xmlDoc=parser.parseFromString(e.target.result,"text/xml");
try{
DistributomeXML_Objects=xmlDoc.documentElement.childNodes;
}catch(error){
DistributomeXML_Objects=xmlDoc.childNodes;
}
//document.getElementById('list').insertBefore(span, null);
};
})(f);
// Read in the file
//reader.readAsDataText(f,UTF-8);
reader.readAsText(f);
}
//xmlDoc.getElementsByTagName("distributome").item(0).appendChild(node);
traverseXML(false, null, DistributomeXML_Objects, distributome.nodes, distributome.edges, distributome.references, distributomeNodes, referenceNodes);
}
I want to check if xmlDoc is valid. What would be a good way to do this without using print statements.
You can use the console and log variables content using the
console.log("my variable content",variable);
you can see it in the browser console using firebug or the native console of chrome or opera...

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