Returning a variable from closure - javascript

How can I get the return value of that.whatever back from the closure? Instead of:
this.setCanvas = function(files){
var numItems = files.length - 1;
this.items = {};
var i = 0;
for(i=0;i<=numItems;i++)
{
var file = files[i];
var reader = new FileReader();
reader.onload = (function(i) {
return function(e) {
var something = that.whatever();
items[i] = something;
};
})(i);
reader.readAsDataURL(file);
}
console.log(items);
}
I need items[i] defined. If I console.log items[i] outside of the closure it is undefined.

The problem isn't the closure; it's the callback. Whatever needs to use the value of that.whatever needs to be executed in the callback.

You can augment your code to keep track of the number of files loaded. This way, when the last file has been loaded, you can invoke an ultimate completion handler:
this.setCanvas = function(files) {
var numItems = files.length - 1;
var itemsLoaded = 0; // Initialize to zero
var items = [];
var i = 0;
for(i=0;i<=numItems;i++) {
var file = files[i];
var reader = new FileReader();
reader.onload = (function(i) {
return function(e) {
var something = that.whatever();
items[i] = something;
if(++itemsLoaded == numItems) {
// At this point all files will have been loaded.
allLoaded();
}
};
})(i);
reader.readAsDataURL(file);
}
function allLoaded() {
// Now we can analyze the results
console.log(items);
}
}
I also changed items to be an Array instead of an Object.
Also if you want to be a little more clever, you could decrement numItems and check for zero instead of creating a new itemsLoaded variable.

There are a couple things you need to change here. First set this.items to an array. Next assign this to self so it can be referenced in the closure. Next assign the something to self.items[i] instead of items[i]. Finally use this.items in the console.log
this.setCanvas = function(files){
var self = this;
var numItems = files.length - 1;
this.items = [];
var i = 0;
for(i=0;i<=numItems;i++)
{
var file = files[i];
var reader = new FileReader();
reader.onload = (function(i) {
return function(e) {
var something = that.whatever();
self.items[i] = something;
};
})(i);
reader.readAsDataURL(file);
}
console.log(this.items);
}

Related

Loop over uploaded files and return an array of file signatures

I want to loop over files selected for upload, get the file signature and return a array of file signatures. The listOfFileSignatures array is empty outside the readFirstFourBytes function. Is their a way to make it accessible globally?
var listOfFileSignatures = [];
var totalSize;
var uploadedFiles = document.getElementById("notes").files;
for (file of uploadedFiles) {
var blob = file;
var fileReader = new FileReader();
fileReader.onloadend = function readFirstFourBytes(e) {
var arr = (new Uint8Array(e.target.result)).subarray(0, 4);
var fileSignature = "";
for (var i = 0; i < arr.length; i++) {
fileSignature += arr[i].toString(16);
};
listOfFileSignatures.push(fileSignature);
console.log(listOfFileSignatures); // Array(3) [ "ffd8ffdb", "ffd8ffe0", "47494638" ]
};
fileReader.readAsArrayBuffer(blob);
};
console.log(listOfFileSignatures); // Array []
Heres the output
You can declare listOfFileSignatures globally, but the signatures are computed asynchronously, so the list will be empty directly after the for loop. FileReader is always asynchronous, so you can't avoid that. One possibility to handle this is to check if the list is full inside onloadend (listOfFileSignatures.length == uploadedFiles.length) and then do what you want there.
A nicer approach is to use promises, like this:
var uploadedFiles = document.getElementById("notes").files;
Promise.all([...uploadedFiles].map(file => new Promise((resolve, reject) => {
var blob = file;
var fileReader = new FileReader();
fileReader.onloadend = function readFirstFourBytes(e) {
var arr = (new Uint8Array(e.target.result)).subarray(0, 4);
var fileSignature = "";
for (var i = 0; i < arr.length; i++) {
fileSignature += arr[i].toString(16);
};
resolve(fileSignature);
};
fileReader.readAsArrayBuffer(blob);
}))).then(function(listOfFileSignatures) {
// this will be called once, when all results are collected.
console.log(listOfFileSignatures);
});
Additionally, reading all bytes and then select just the first 4 byte is inefficient. Improved version:
Promise.all([...uploadedFiles].map(file => new Promise((resolve, reject) => {
var blob = file;
var fileReader = new FileReader();
fileReader.onloadend = function readFirstFourBytes(e) {
var arr = new Uint8Array(e.target.result);
var fileSignature = "";
for (var i = 0; i < arr.length; i++) {
fileSignature += arr[i].toString(16);
};
resolve(fileSignature);
};
fileReader.readAsArrayBuffer(blob.slice(0, 4));
}))).then(function(listOfFileSignatures) {
// this will be called once, when all results are collected.
console.log(listOfFileSignatures);
});
fileReader.onload is asynchronous, console.log (listOfFileSignatures); is called before files have been read
one option is to create an external function that returns a promise, returning the listOfFileSignatures array
function getListFile() {
return new Promise((resolve, reject) => {
var blob = file;
var fileReader = new FileReader();
fileReader.onloadend = function readFirstFourBytes(e) {
var arr = (new Uint8Array(e.target.result)).subarray(0, 4);
var fileSignature = "";
for (var i = 0; i < arr.length; i++) {
fileSignature += arr[i].toString(16);
};
listOfFileSignatures.push(fileSignature);
resolve(listOfFileSignatures);
};
}
}

reader.onload function cant read value neither call function

im using reader.onload event to get contents of csv file,
problem is the value displays in console.log() but not in DOM i.e via binding
dropped(event: UploadEvent) {
this.files = event.files;
console.log(this.files)
for (const droppedFile of event.files) {
// Is it a file?
if (droppedFile.fileEntry.isFile) {
const fileEntry = droppedFile.fileEntry as FileSystemFileEntry;
fileEntry.file((file: File) => {
// Here you can access the real file
console.log(droppedFile.relativePath, file);
const fileToRead = file;
const fileReader = new FileReader();
fileReader.onload = this.onFileLoad;
fileReader.readAsText(fileToRead);
console.log(this.tempval) /// undefined
});
}
}}
and onFileLoad function is as follows
onFileLoad(fileLoadedEvent) {
const textFromFileLoaded = fileLoadedEvent.target.result;
this.csvContent = textFromFileLoaded;
var allTextLines = this.csvContent.split(/\r\n|\n/);
var headers = allTextLines[0].split(',');
var lines = [];
for ( var i = 0; i < allTextLines.length; i++) {
// split content based on comma
var data = allTextLines[i].split(',');
if (data.length == headers.length) {
var tarr = [];
for ( var j = 0; j < headers.length; j++) {
tarr.push(data[j]);
}
lines.push(tarr);
}
}this.tempval = linesconsole.log(this.tempval) // printing value
};
unfortunately data inside this.tempval is not accessible anywhere
not in html DOM or inside dropped() funtion. except inside onFileLoad()
im just new to typescript
thanks in advance

Unable to pass a variable to a function

I want to pass the current index to a function reading images via FileApi and showing preview of them:
for(var i = 0, f; f = files[i]; i++) {
if (!f.type.match("image.*")) {
continue;
}
var reader = new FileReader();
reader.onload = (function(theFile) {
return function(e) {
var dv = document.createElement("div");
//.............
};
})(f);
reader.readAsDataURL(f);
}
I've tried this:
//.......
var reader = new FileReader();
reader.onload = (function(theFile, i2) {
//i2 is defined here
return function(e) {
var dv = document.createElement("div");
//i2 isn't defined here
};
})(f, i);
But as you can see, i2 isn't defined. How to fix that?
If you organize the wrapper function(s) a bit, everything can be properly scoped. Here I wrap basically the whole loop body in a self-calling function. The onload handler is then just a plain old function assignment. In your production code you can remove the dummy onload.
var files = ['one', 'two', 'three'];
for (var i = 0, f; f = files[i]; i++) {
(function(i, f) {
var reader = new FileReader();
reader.onload = function() {
var dv = document.createElement("div");
console.log(f + i);
};
// dummy onload
(function(r) {
setTimeout(r.onload, 1000 + (i + 1) * 333);
})(reader);
})(i, f)
}

FileReader's onloadend event is never triggered

I'm trying to make a small snippet to preview images before uploading them:
$.fn.previewImg=function($on){
var input = this;
try{
if (this.is("input[type='file']")) {
input.change(function(){
var reader = new FileReader();
reader.onloadend = function(){
for (var i = 0; i < $on.length; i++) {
if (/img/i.test($on[i].tagName)) $on[i].src = reader.result;
else $on[i].style.bakgroundImage = "url("+reader.result+")";
}
};
});
}else throw new exception("Trying to preview image from an element that is not a file input!");
}catch(x){
console.log(x);
}
};
I'm calling it like:
$("#file").previewImg($(".preview_img"));
but the onloadend function is never called.
FIDDLE
Actually , you got to specify the file and instruct the fileReader to read it.
Below is the corrected code.
$.fn.previewImg=function($on){
var input = this;
try{
if (this.is("input[type='file']")) {
input.change(function(evt){
var reader = new FileReader();
console.log("Input changed");
reader.onloadend = function(){
console.log("onloadend triggered");
for (var i = 0; i < $on.length; i++) {
if (/img/i.test($on[i].tagName)) $on[i].src = reader.result;
else $on[i].style.bakgroundImage = "url("+reader.result+")";
}
};
//get the selected file
var files = evt.target.files;
//instruct reader to read it
reader.readAsDataURL(files[0]);
});
}else throw new exception("Trying to preview image from an element that is not a file input!");
}catch(x){
console.log(x);
}
};
$("#file").previewImg($(".preview_img"));

Get file name in javascript

Hi I have a input file which takes multiple files and the tag is given the Id = fileToUpload
and here goes the code:
var input = document.getElementById('filesToUpload');
for (var x = 0; x < input.files.length; x++) {
oFReader = new FileReader();
oFReader.readAsDataURL(input.files[x]);
oFReader.onload = function (oFREvent) {
imageSrc = oFREvent.target.result;
console.log("source:" +imageSrc);
name = oFREvent.target.name;
console.log("name:" +name);
};
}
Here I am able to get the source of the image but I am not able to get the name of the file which is selected for uploading. I am doing the right way or this is not a right way to get a file name.
You want to get the name from the original filelist, not the target of the FileReader's onload event. The FileReader object doesn't have a name property and the target of the onload event is the FileReader, not the file.
EDIT
Getting the name file loaded into the FileReader turns out to be kinda tricky! I came up with two ways which you can see in this fiddle.
First way just seems plain wrong - add a name property to your new FileReader() instance and then access it via evt.target. Works in FF and Chrome anyway.
var input = document.getElementById('filesToUpload');
input.addEventListener("change", soWrongButItSeemsToWork, false);
function soWrongButItSeemsToWork () {
var filelist = this.files;
for (var x = 0; x < filelist.length; x++) {
oFReader = new FileReader();
oFReader.name = filelist[x].name;
console.log("name outside:", oFReader.name);
oFReader.onload = function (oFREvent ) {
imageSrc = oFREvent.target.result;
console.log('name inside:', oFREvent.target.name);
img = document.createElement("img");
img.src = imageSrc;
document.body.appendChild(img);
};
oFReader.readAsDataURL(filelist[x]);
}
}
Use a closure as suggested by http://www.htmlgoodies.com/beyond/javascript/read-text-files-using-the-javascript-filereader.html (at the bottom). Something like:
var input2 = document.getElementById('fileinput');
input2.addEventListener("change", readMultipleFiles, false);
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) { // WOOHOO!
var dataUri = e.target.result,
img = document.createElement("img");
img.src = dataUri;
document.body.appendChild(img);
console.log( "Got the file.n"
+"name: " + f.name + "\n"
+"type: " + f.type + "\n"
+"size: " + f.size + " bytes\n"
);
};
})(f);
r.readAsDataURL(f);
}
} else {
alert("Failed to load files");
}
}
Good article on MDN here https://developer.mozilla.org/en-US/docs/Using_files_from_web_applications
Try this code work perfectly:
var input = document.getElementById('filesToUpload');
for (var x = 0; x < input.files.length; x++) {
oFReader = new FileReader();
oFReader.readAsDataURL(input.files[x]);
oFReader.onload = function (oFREvent) {
imageSrc = oFREvent.target.result;
console.log("source:" +imageSrc);
name = imageSrc.replace(/^.*[\\\/]/, '');
console.log("name:" +name);
};
}
I have did a work around for this and here is the example given:
var input = document.getElementById('filesToUpload');
for (var x = 0; x < input.files.length; x++) {
oFReader = new FileReader();
oFReader.readAsDataURL(input.files[x]);
var index = 0;
oFReader.onload = function (oFREvent) {
imageSrc = oFREvent.target.result;
console.log("source:" +imageSrc);
//name = oFREvent.target.name;
name = input.files[index++].name;
console.log("name:" +name);
};
}
Each time I iterate over the reader object then I increment the index so that it indexs to the next fine in the array.

Categories

Resources