Variable undefined in XMLHTTPResponse.onload scope - javascript

This is most likely something I'm overlooking but I can't figure out exactly what the issue is in this situation.
Upload: function (callback) {
var formData, xhr, file,
xhrLoaded = function () {
if (callback) {
callback(this.status, this.response, file);
}
};
if (this._files.length < 1) {
return false;
}
file = this._files.pop();
while (file) {
formData = new FormData();
formData.append("file", file);
xhr = new XMLHttpRequest();
xhr.open("POST", this.options.uploadUrl);
xhr.onload = xhrLoaded;
xhr.send(formData);
file = this._files.pop();
}
}
Why is it the case that when the xhr onload event handler runs file is undefined? At first I thought it was because onload is getting called out of the current scope, but that didn't seem right based on my understanding of javascript scoping. So as a test I checked this on a simple page.
(function () {
var test = "This is a test", testFunc = function () { console.log(test); };
window.onload = testFunc;
})();
and the variable test has the correct value. This tells me it has something to do with the file variable changing. So tried to add var localFile = file to the xhrLoaded method to see if it would keep the correct value in that case but it doesn't appear to be the case.

Related

JavaScript sql.js - is callback correct?

So i keep getting [object Object] where actually the result of a query should be. My question is, whether my callback is incorrect, because I have a hard time understanding it, but think i did it correct.
HTML-part:
<button type="button" id="input_search" onclick="getSearchFromSQL()">Search</button>
JS-part:
function getSearchFromSQL() {
document.getElementById('output').innerHTML = contents;
}
getSearchAsync(function (result) {
contents = result;
});
function getSearchAsync(callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', 'test123.sqlite3', true);
xhr.responseType = 'arraybuffer';
xhr.onload = function (e) {
var uInt8Array = new Uint8Array(this.response);
db = new SQL.Database(uInt8Array);
callback(db.exec("SELECT * FROM data"));
};
xhr.send();
}
I want to add, that my IDE tells me, that "SQL" is not declared as a global variable - not sure if there is a problem, since sql.js is implemented in the html-file.

FileReader.onload can not return successful in time

I want to get the image file stream and pass them to the background like asp.net,but every time I try to fire the onload event ,it always accomplish after the programming passed.
I tried to use setTimeout to prevent it and let it processing and waiting it Success ,but it failed .
the comment of below explains which step I failed.thanks.
$("#goodsImage").change(function (e) {
if ($("#goodsImage").val() == "")
{
alert("please choose the image you want to upload");
return;
}
var filepath = $("#goodsImage").val();
//$("#goodsImage").val();
var extStart=filepath.lastIndexOf(".");
var ext=filepath.substring(extStart,filepath.length).toUpperCase();
if(ext!=".BMP"&&ext!=".PNG"&&ext!=".GIF"&&ext!=".JPG"&&ext!=".JPEG"){
alert("only images could be uploaded");
return;
}
readFile(e.target.files[0]);
setTimeout(function () {
//I want to use setTimeOut to delay it
}, 1000);
//always undefined!!!
if ($("#hidImageStream").val() != "")
{
$.post("#Url.Action("UploadGoodImage")", { localPath: readerRs }, function (e)
{
if (e) {
$("#ImagePreviewUrl").val(e.data);
}
else {
}
});
}
});
function readFile(file) {
var reader = new FileReader();
reader.onload = readSuccess;
//always success after post request accomplished.
function readSuccess(evt) {
document.getElementById("hidImageStream").
value = evt.target.result;
};
reader.readAsText(file);
}
here is a few tips
<-- use accept and only allow image mimetype. It can accept extension to -->
<input type="file" name="pic" accept="image/*">
Also reading the file as readAsText is a horrible idea for binary. (All doe i saw you change to base64).
readAsDataURL isn't that grate either since it's ~3x larger upload and needs more cpu/memory.
Spoofing the filename is very easy so best is to actually test if it's a image
$("#goodsImage").change(function() {
// We use `this` to access the DOM element instead of target
for (let file of this.files) {
// Test if it's a image instead of looking at the filename
let img = new image
img.onload = () => {
// Success it's a image
// upload file
let fd = new FormData
fd.append('file', file)
$.ajax({
url: 'http://example.com',
data: fd,
processData: false, // so jquery can handle FormData
type: 'POST',
success: function( data ) {
alert( data )
}
})
}
img.onerror = () => {
// only images dude
}
img.src = URL.createObjectURL(file)
}
})
I have solved the question ,just put the post request into the onloaded funciton .
function readFile(file) {
var reader = new FileReader();
reader.onloadend = readSuccess;
//reader.onloadend = function (e) {
// console.log(e.target.result);
//};
function readSuccess(evt) {
$.post("#Url.Action("UploadGoodImage")", { localPath: evt.target.result}, function (e) {
if (e.IsSuccess) {
$("#ImagePreviewUrl").val(e.data);
}
else {
alert("Fail!");
}
});
}
reader.readAsDataURL(file);
}

File reader execute multiple times in javascript

I'm trying to load images in to page for preview before uploading with javascript.
I have following code:
holder.onclick = function(event) {
function chooseFile(name) {
var chooser = $(name);
chooser.unbind('change');
chooser.change(function(evt) {
function loadFile(file, callback) {
var reader = new FileReader();
(reader.onload = function(file) {
console.log(f);
var output = document.createElement('input');
output.type = 'image';
output.classList.add('image-responsive');
output.classList.add('col-xs-12');
output.name = f;
output.id = f;
output.src = reader.result;
var x = document.getElementById('OrigName');
x.appendChild(output);
return callback(output);
})(f = file.name);
reader.readAsDataURL(file);
}
for (var i = 0; i < evt.target.files.length; i++) {
console.log(i);
var file = evt.target.files[i];
loadFile(file, function(output) {
// console.log(output);
});
}
});
chooser.trigger('click');
}
chooseFile('#fileDialog');
}
Problem is, whenever i load image, code inside reader.onload method execute twice, and in console i 2x result of console.log(f) and 2 errors that 'localhost/null is not found'.
When i remove (f=file.name), script execute as it should be, but then i don't have file.name variable inside reader scope.
EDIT:
Here's JSFiddle of my problem:
https://jsfiddle.net/onedevteam/udmz34z0/6/
Can someone help me fix this?
Problem is, whenever i load image, code inside reader.onload method execute twice
This is because in your code you have this.
(reader.onload = function(file) {
//...
//...
})(f = file.name); // <---- self executing function.
reader.readAsDataURL(file);
Here you are using "Self Executing function" for the reader.onload, So what happens is it will execute once when it hits this line of code, And again when reader.readAsDataURL(file) has completed reading. So remove the "self executing function " and you logic will run only once
When i remove (f=file.name), script execute as it should be, but then i don't have file.name variable inside reader scope.
to get the file name just add it in a variable and use it like this.
var fileName = file.name;
reader.onload = function() {
//...
//...
output.name = fileName ;
output.id = fileName ;
}; // <-- self executing function REMOVED
Also I feel there is no need to save the file name into a variable because the variable file passed into function is sufficient to get the job done. So below would be the final code as per my suggestion.
function loadFile(file, callback) {
var reader = new FileReader();
reader.onload = function() {
console.log(file.name); //
var output = document.createElement('input');
output.type = 'image';
output.classList.add('image-responsive');
output.classList.add('col-xs-12');
output.name = file.name; //
output.id = file.name; //
output.src = reader.result;
var x = document.getElementById('OrigName');
x.appendChild(output);
return callback(output);
};
reader.readAsDataURL(file);
}
You're calling reader.onload at least twice. You have this function inside another function loadFile(), and you call it immediately (which is why you only see this behavior when you have (f=file.name) there), but then also inside the chooser.change function you have that for-loop that calls loadFile(). Perhaps ou could set the file.name variable somewhere other than (f=file.name) and then make reader.onload not execute automatically.
The way you have your code structured, your onload handler will be executed twice, once when you define it, and then again when the "load" event fires. When you wrap a function definition inside parens:
(reader.onload = function (file) { ... })(f = filename)
you're saying "define this function and execute it immediately."
What you really want is a function that returns a function, like this:
function makeOnLoadHandler (filename) {
return function (file) {
// ... do whatever you need to with file and filename
};
}
reader.onload = makeOnLoadHandler(someFileName);
The outer function, makeOnLoadHandler(), creates a closure around your filename variable, and when the inner function handles the reader's load event, it will see the filename that you passed in when you called makeOnLoadHandler.

Turn XMLhttpRequest into a function fails: asynchronity or other?

I try to turn an XMLHttpRequest into a function such
var getImageBase64 = function (url) { // code function
var xhr = new XMLHttpRequest(url);
... // code to load file
... // code to convert data to base64
return wanted_result; // return result of conversion
}
var newData = getImageBase64('http://fiddle.jshell.net/img/logo.png'); // function call
doSomethingWithData($("#hook"), newData); // reinjecting newData in wanted place.
I'am successful to load the file, and to convert to base64. I'am however consistenly failling to get the result as an output :
var getImageBase64 = function (url) {
// 1. Loading file from url:
var xhr = new XMLHttpRequest(url);
xhr.open('GET', url, true); // url is the url of a PNG image.
xhr.responseType = 'arraybuffer';
xhr.onload = function(e) {
if (this.status == 200) { // 2. When loaded, do:
console.log("1:Response?> " + this.response); // print-check xhr response
var imgBase64 = converterEngine(this.response); // converter
}
}
xhr.send();
return xhr.onload(); // <fails> to get imgBase64 value as the function's result.
}
console.log("4>>> " + getImageBase64('http://fiddle.jshell.net/img/logo.png') ) // THIS SHOULD PRINT THE BASE64 CODE (returned resukt of the function getImageBase64)
See Fiddle here.
How to make it works so it return the new data as output ?
Solution: my final implementation is visible here, and on JS: how to load a bitmap image and get its base64 code?.
Asynchronous calls in JavaScript (like xhr) can't return values like regular functions. The common pattern used when writing asynchronous functions is this:
function asyncFunc(param1, param2, callback) {
var result = doSomething();
callback(result);
}
asyncFunc('foo', 'bar', function(result) {
// result is what you want
});
So your example translated looks like this:
var getImageBase64 = function (url, callback) {
var xhr = new XMLHttpRequest(url);
... // code to load file
... // code to convert data to base64
callback(wanted_result);
}
getImageBase64('http://fiddle.jshell.net/img/logo.png', function(newData) {
doSomethingWithData($("#hook"), newData);
});
When you use xhr.onload your actually defining a function for JS to call when it loads, hence the value of xhr.onload is the function not the output of the function. Returning xhr.onload() will call that function and return the output, but your onload function has no return statement and hence no output. Additionally you are calling xhr.onload synchronously as you set up the object, and hence it won't have any data to process.
I suggest you add a callback parameter to your function like so, this will execute when the data has loaded.
function getImageBase64( url, callback) {
// 1. Loading file from url:
var xhr = new XMLHttpRequest(url);
xhr.open('GET', url, true); // url is the url of a PNG image.
xhr.responseType = 'arraybuffer';
xhr.callback = callback;
xhr.onload = function(e) {
if (this.status == 200) { // 2. When loaded, do:
console.log("1:Response?> " + this.response); // print-check xhr response
var imgBase64 = converterEngine(this.response); // converter
this.callback(imgBase64);//execute callback function with data
}
}
xhr.send();
}
Then you would use it like so
var myCallBack = function(data){
alert(data);
};
getImageBase64('http://fiddle.jshell.net/img/logo.png', myCallBack);

How do I write FileReader test in Jasmine?

I'm trying to make this test work, but I couldn't get my head around how to write a test with FileReader. This is my code
function Uploader(file) {
this.file = file;
}
Uploader.prototype = (function() {
function upload_file(file, file_contents) {
var file_data = new FormData()
file_data.append('filename', file.name)
file_data.append('mimetype', file.type)
file_data.append('data', file_contents)
file_data.append('size', file.size)
$.ajax({
url: "/upload/file",
type: "POST",
data: file_contents,
contentType: file.type,
success: function(){
// $("#thumbnail").attr("src", "/upload/thumbnail");
},
error: function(){
alert("Failed");
},
xhr: function() {
myXhr = $.ajaxSettings.xhr();
if(myXhr.upload){
myXhr.upload.addEventListener('progress',showProgress, false);
} else {
console.log("Upload progress is not supported.");
}
return myXhr;
}
});
}
return {
upload : function() {
var self = this,
reader = new FileReader(),
file_content = {};
reader.onload = function(e) {
file_content = e.target.result.split(',')[1];
upload_file(self.file, file_content);
}
}
};
})();
And this is my test
describe("Uploader", function() {
it("should upload a file successfully", function() {
spyOn($, "ajax");
var fakeFile = {};
var uploader = new Uploader(fakeFile);
uploader.upload();
expect($.ajax.mostRecentCall.args[0]["url"]).toEqual("/upload/file");
})
});
But it never gets to reader.onload.
The problem here is the use of reader.onload which is hard to test. You could use reader.addEventListener instead so you can spy on the global FileReader object and return a mock:
eventListener = jasmine.createSpy();
spyOn(window, "FileReader").andReturn({
addEventListener: eventListener
})
then you can fire the onload callback by yourself:
expect(eventListener.mostRecentCall.args[0]).toEqual('load');
eventListener.mostRecentCall.args[1]({
target:{
result:'the result you wanna test'
}
})
This syntax changed in 2.0. Code below gives an example based on Andreas Köberle's answer but using the new syntax
// create a mock object, its a function with some inspection methods attached
var eventListener = jasmine.createSpy();
// this is going to be returned when FileReader is instantiated
var dummyFileReader = { addEventListener: eventListener };
// pipe the dummy FileReader to the application when FileReader is called on window
// this works because window.FileReader() is equivalent to new FileReader()
spyOn(window, "FileReader").and.returnValue(dummyFileReader)
// your application will do something like this ..
var reader = new FileReader();
// .. and attach the onload event handler
reader.addEventListener('load', function(e) {
// obviously this wouldnt be in your app - but it demonstrates that this is the
// function called by the last line - onloadHandler(event);
expect(e.target.result).toEqual('url');
// jasmine async callback
done();
});
// if addEventListener was called on the spy then mostRecent() will be an object.
// if not it will be null so careful with that. the args array contains the
// arguments that addEventListener was called with. in our case arg[0] is the event name ..
expect(eventListener.calls.mostRecent().args[0]).toEqual('load');
// .. and arg[1] is the event handler function
var onloadHandler = eventListener.calls.mostRecent().args[1];
// which means we can make a dummy event object ..
var event = { target : { result : 'url' } };
// .. and call the applications event handler with our test data as if the user had
// chosen a file via the picker
onloadHandler(event);
I also faced similar problem and was able to achieve it without use of addeventlistener. I had used onloadend, so below is what I did.
My ts file had below code:-
let reader = new FileReader();
reader.onloadend = function() {
let dataUrl = reader.result;
// Some working here
};
reader.readAsDataURL(blob);
My spec file (test) case code :-
let mockFileReader = {
result:'',
readAsDataURL:(blobInput)=> {
console.log('readAsDataURL');
},
onloadend:()=> {
console.log('onloadend');
}
};
spyOn<any>(window, 'FileReader').and.returnValue(mockFileReader);
spyOn<any>(mockFileReader, 'readAsDataURL').and.callFake((blobInput)=> {
// debug your running application and assign to "encodedString" whatever
//value comes actually after using readAsDataURL for e.g.
//"data:*/*;base64,XoteIKsldk......"
mockFileReader.result = encodedString;
mockFileReader.onloadend();
});
This way you have mocked the FileReader object and returned a fake call to your own "readAsDataURL". And thus now when your actual code calls "reasAsDataURL" your fake function is called in which you are assigning an encoded string in "result" and calling "onloadend" function which you had already assigned a functionality in your code (.ts) file. And hence it gets called with expected result.
Hope it helps.
I think the best way is to use the real FileReader (don't mock it), and pass in a real File or Blob. This improves your test coverage and makes your tests less brittle.
If your tests don't run in IE, you can use the File constructor, e.g.
const fakeFile = new File(["some contents"], "file.txt", {type: "text/plain"});
If you need to be compatible with IE, you can construct a Blob and make it look like a file:
const fakeFile = new Blob(["some contents"]);
fakeFile.name = "file.txt";
fakeFile.type = "text/plain";
The FileReader can read either of these objects so there is no need to mock it.
i found easiest for myself to do next.
mock blob file
run reader.onload while in test environment.
as result - i do not mock Filereader
// CONTROLLER
$scope.handleFile = function (e) {
var f = e[0];
$scope.myFile = {
name: "",
size: "",
base64: ""
};
var reader = new FileReader();
reader.onload = function (e) {
try {
var buffer = e.target.result;
$scope.myFile = {
name: f.name,
size: f.size,
base64: XLSX.arrayBufferToBase64(buffer)
};
$scope.$apply();
} catch (error) {
$scope.error = "ERROR!";
$scope.$apply();
}
};
reader.readAsArrayBuffer(f);
//run in test env
if ( typeof jasmine == 'object') {reader.onload(e)}
}
//JASMINE TEST
it('handleFile 0', function () {
var fileContentsEncodedInHex = ["\x45\x6e\x63\x6f\x64\x65\x49\x6e\x48\x65\x78\x42\x65\x63\x61\x75\x73\x65\x42\x69\x6e\x61\x72\x79\x46\x69\x6c\x65\x73\x43\x6f\x6e\x74\x61\x69\x6e\x55\x6e\x70\x72\x69\x6e\x74\x61\x62\x6c\x65\x43\x68\x61\x72\x61\x63\x74\x65\x72\x73"];
var blob = new Blob(fileContentsEncodedInHex);
blob.type = 'application/zip';
blob.name = 'name';
blob.size = 11111;
var e = {0: blob, target: {result: {}}};
$scope.handleFile(e);
expect($scope.error ).toEqual("");
});
I struggled to figure out how to test onloadend when it gets called from readAsDataURL.
Here is a dump of what I ended up with.
Production code:
loadFileDataIntoChargeback(tempFileList) {
var fileNamesAndData = [];
for (var i = 0, f; f = tempFileList[i]; i++) {
let theFile = tempFileList[i];
var reader = new FileReader();
reader.onloadend = ((theFile) => {
return (fileData) => {
var insertionIndex = this.chargeback.fileList.length;
this.chargeback.fileList.push({ FileName: theFile.name, Data: fileData.target.result, FileType: theFile.type });
this.loadFilePreviews(theFile, insertionIndex);
}
})(f);
reader.readAsDataURL(f);
}
this.fileInputPath = "";
}
Test code:
describe('when the files are loaded into the chargeback', () => {
it('loads file previews', () => {
let mockFileReader = {
target: { result: '' },
readAsDataURL: (blobInput) => {},
onloadend: () => {}
};
spyOn(chargeback, "loadFilePreviews");
spyOn(window, 'FileReader').and.returnValue(mockFileReader);
spyOn(mockFileReader, 'readAsDataURL').and.callFake((blobInput) => {
mockFileReader.onloadend({ target: { result: "data:image/jpeg;base64,/9j/4QAYRXh" } });
});
var readFileList = chargeback.getArrayFromFileInput([getImageFile1()]);
chargeback.loadFileDataIntoChargeback(readFileList);
expect(chargeback.loadFilePreviews).toHaveBeenCalled();
});
});

Categories