I've got an event listener that fires normally in FireFox, but somehow doesn't in Webkit based browsers.
function ProfilePictureFeedback(fileInput, target) {
fileInput.addEventListener('input', function() {
var profilePictureData = fileInput.files[0];
var reader = new FileReader();
reader.readAsBinaryString(profilePictureData);
reader.addEventListener("load", function () {
var result = btoa(reader.result);
target.style.background = 'url(data:image/jpeg;base64,' + result + ')';
target.style.backgroundSize = 'cover';
});
});
}
I've looked around for possible duplicate answers, one stating something about e.preventDefault();, but I have no idea how that exactly ties in to my problem.
Edit: Forgot to mention that no error messages are shown in the console.
Related
I'm working on a project that utilizes WebRTC for file transfers, recently someone reported an issue saying that transfers end prematurely for bigger files. I've found the problem, and my solution to that problem was to rely on the bufferedamountlow event to coordinate the sending of chunks. I've also stopped closing the connection when the sender thinks it's complete.
For some reason, though, in Safari that event does not fire.
Here is the relevant code:
const connection = new RTCPeerConnection(rtcConfiguration);
const channel = connection.createDataChannel('sendDataChannel');
channel.binaryType = 'arraybuffer';
channel.addEventListener('open', () => {
const fileReader = new FileReader();
let offset = 0;
const nextSlice = (currentOffset: number) => {
// Do asynchronous thing with FileReader, that will result in
// channel.send(buffer) getting called.
// Also, offset gets increased by 16384 (the size of the buffer).
};
channel.bufferedAmountLowThreshold = 0;
channel.addEventListener('bufferedamountlow', () => nextSlice(offset));
nextSlice(0);
});
The longer version of my code is available here.
While researching the issue, I've realized that on Safari, my connection.stcp is undefined. (Since I've switched to connection.sctp.maxMessageSize instead of 16384 for my buffer size.) I would assume the problem is related to that.
What could be the cause for this problem? Let me add that on Chrome and Firefox everything works just fine without any issues whatsoever.
The bufferedamountlow event is not required for the proper function of my code, I would like for it to work, though, to get more precise estimates of current progress and speed on the sending end of the file transfer.
After some investigation, it comes to me that Safari has issues with 0 as a value for the bufferedAmountLowThreshold property.
When set to a non-zero value, the code functions properly.
Checking the bufferedAmount inside of the nextSlice function also increases the speed at which the chunks are being sent:
const bufferSize = connection.sctp?.maxMessageSize || 65535;
channel.addEventListener('open', () => {
const fileReader = new FileReader();
let offset = 0;
const nextSlice = (currentOffset: number) => {
const slice = file.slice(offset, currentOffset + bufferSize);
fileReader.readAsArrayBuffer(slice);
};
fileReader.addEventListener('load', e => {
const buffer = e.target.result as ArrayBuffer;
try {
channel.send(buffer);
} catch {
// Deal with failure...
}
offset += buffer.byteLength;
if (channel.bufferedAmount < bufferSize / 2) {
nextSlice(offset);
}
});
channel.bufferedAmountLowThreshold = bufferSize / 2;
channel.addEventListener('bufferedamountlow', () => nextSlice(offset));
nextSlice(0);
});
We recently implemented the ability of our members to upload an image. It has worked fine for several months. Recently it has stopped working in Chrome, but continues to work in Firefox, Safari, and Internet Explorer.
The error message that Chrome is generating is as follows:
Uncaught TypeError: Failed to set the 'files' property on 'HTMLInputElement': The provided value is not of type 'FileList'.
The code section that is having the problem is as follows:
$(window).load(function()
{
var aFiletypesAllowed = ["png","gif","jpeg","jpg"];
var FileTypesPrintable = 'png, gif, jpeg, jpg"';
var form = document.getElementById('image-submit');
var options =
{
thumbBox: '.thumbBox',
imgSrc: ''
}
var cropper = $('.imageBox').cropbox(options);
$('#file').on('change', function()
{
var reader = new FileReader();
reader.onload = function(e)
{
options.imgSrc = e.target.result;
cropper = $('.imageBox').cropbox(options);
}
reader.readAsDataURL(this.files[0]);
this.files = [];
fileSize = this.files[0].size;
fileName = this.value;
})
$('#btnCrop').on('click', function()
{
//file validation
var filetype = fileName.split('.').pop().toLowerCase();
fileWidth = cropper.getWidth();
fileHeight = cropper.getHeight();
//check file type
if (aFiletypesAllowed.indexOf(filetype) < 0)
{
var filenameUsed = fileName.split('\\').pop();
jConfirm('Invalid Filetype', filenameUsed + ' is not an allowed file type. Allowed filetypes are ' + FileTypesPrintable, '', '', '', '', '', '', '', '', 180);
document.getElementById('file').value = '';
}
//check image dimensions -- at least one dimension must be over the limit to ensure a crop occurs, and the image is modified in some way
else if (fileWidth < 200 && fileHeight < 200)
{
jConfirm('Image too Small', 'Minimum allowed image size is 200 x 200. Your image is ' + fileWidth + ' x ' + fileHeight + '. Please choose a larger image.', '', '', '', '', '', '', '', '', 180);
document.getElementById('file').value = '';
}
//validation passed, perform crop
else
{
var img = cropper.getDataURL();
$('.cropped').html('<img id="cropped-img" src="'+img+'">'); //image to display
$('#cropped-to-send').val(img); //dataURL to upload
}
})
The error message appears to be generated by the line of code "this.files = [];". After that error occurs, another error is thrown by line "var filetype = fileName.split('.').pop().toLowerCase();". The error on that line is "fileName is not defined".
As stated above, this function ran fine for several months in Chrome, and continues to work fine in Firefox, Safari and Internet Explorer.
I don't really understand how this function works, because it was implemented by another programmer based on a library. However, while inspecting the code, the one line "this.files = [];" seems to be problematic. That initializes an empty array; then the next two lines attempt to access elements of that array for later use. Of course, they will be empty.
So on a hunch, I re-arranged the order of some of the lines. The lines from the above code are as follows:
reader.readAsDataURL(this.files[0]);
this.files = [];
fileSize = this.files[0].size;
fileName = this.value;
I re-arranged them as follows:
reader.readAsDataURL(this.files[0]);
fileSize = this.files[0].size;
fileName = this.value;
this.files = [];
To me the function of these lines is as follows:
perform the data read;
assign some values
set this.files to an empty array
After doing this, the function now appears to work properly in Chrome.
My question now is more like: why was it working at all before in any browser? It seems that clearing the array, then trying to use the array is doomed.
And the second mystery is why did it work for several months? I have verified that this particular code has not changed in that period.
I had similar problem. Removing the line this.files = []; seams to fix the error, and since reader.readAsDataURL(this.files[0]); has already been called I don't think it will cause any much harm to the file upload. I think the error occurs because it expects a value of type FileList but does not match []
var database = e.target.result;
var version = Number(database.version);
console.log("in onsuccess>>>>>>>>>>>>>>>>>>>>>>>>>> : "+dbName);
console.log(e);
database.close();
var secondRequest = indexedDB.open(dbName, (version+1));
console.log(secondRequest); // <-- error on this line
//console.log(secondRequest.result);
secondRequest.onupgradeneeded = function (e) {
console.log("in onupgradeneeded>>>>>>>>>>>>>>>>>>>>>>>>>>");
console.log(e);
var database = e.target.result;
//database.setVersion(12);
var objectStore = database.createObjectStore(storeName, {
keyPath: 'id'
});
};
secondRequest.onsuccess = function (e) {
console.log("000000000000000000000000000000");
e.target.result.close();
};
secondRequest.onerror = function(e){
console.log("Error ------------------- ");
console.log(e);
}
in above console I am getting following error in
console.log(secondRequest);
error:
IDBOpenDBRequest
error : [Exception: DOMException]
I have added listner
IDBOpenDBRequest.onerror = function(e){
}
But It is not going there. Help me if anybody have solution.
Although you have some of the core concepts down, your code is really hard to follow as is. To begin with, this e event assignment is undefined:
var database = e.target.result;
Where is the database open()? Where is dbName coming from?
Provide more of your failing code, preferably via jsfiddle, and we'll help you find a solution.
UPDATE: Here's a working example of what you're trying to do.
Output div:
<div id="idb_version"></div>
Code:
var db_name = 'myname',
database_open_request = window.indexedDB.open(db_name);
database_open_request.addEventListener('success', function (e) {
database = e.target.result;
database.close();
var second_database_open_request = window.indexedDB.open(db_name, database.version + 1);
second_database_open_request.addEventListener('upgradeneeded', function (e) {
database = e.target.result;
database.close();
window.document.getElementById("idb_version").innerHTML = database.version;
});
});
When not specifying a version param, you get a reference to the most recent version on success callback. Then I listen for a versionchange and increase the version by one. Run this over and over and you'll see the version increase one by one.
I was looking at the new HTML5 file API for showing a preview of an image to be uploaded. I googled for some code and almost every example had the same structure, almost the same code. I don't mind copying, particularly when it works, but I need to understand it. So I tried to understand the code but I am stuck with one area and need someone to explain that small part:
The code refers to a HTML form input field and when the file is selected shows the preview image in a img tag. Nothing fancy. Simple. Here it is after removing all the noise:
$('input[type=file]').change(function(e) {
var elem = $(this);
var file = e.target.files[0];
var reader = new FileReader();
//Part I could not understand starts here
reader.onload = (function(theFile) {
return function(e) {
var image_file = e.target.result
$('#img_id').attr('src',image_file);
};
})(file);
reader.readAsDataURL(file);
//Upto here
});
I think that reader.onload needs to be assigned a plain event handler, so I replaced the entire section marked above to:
reader.readAsDataURL(file);
reader.onload = function(e) {
var image_file = e.target.result;
//#img_id is the id of an img tag
$('#img_id').attr('src',image_file)
};
And it worked as I expected it to work.
QUESTION: What's the original code doing that the above simplified code is missing? I understand that it is a function expression returning a function and then calling it… but for what? There is just too much of the original code copied around under tutorials and what not on it but no good explanation. Please explain. Thanks
Sure.
The function-wrapped function, here serves the specific purpose of remembering which file it was you were looking at.
This might be less of a problem using your exact codebase, but if you had a multiple-upload widget, and you wanted to display a row of previews:
var my_files = [].slice.call(file_input.files),
file, reader,
i = 0, l = my_files.length;
for (; i < l; i += 1) {
file = my_files[i];
reader = new FileReader();
// always put the handler first (upside down), because this is async
// if written normally and the operation finishes first (ie:cached response)
// then handler never gets called
reader.onload = function (e) {
var blob_url = e.target.result,
img = new Image();
img.src = blob_url;
document.body.appendChild(img);
};
reader.readAsDataUrl(file);
}
That should all work fine. ...except...
And it's clean and readable.
The issue that was being resolved is simply this:
We're dealing with async-handlers, which means that the value of file isn't necessarily the same when the callback fires, as it was before...
There are a number of ways to potentially solve that.
Pretty much all of them that don't generate random ids/sequence-numbers/time-based hashes to check against on return, rely on closure.
And why would I want to create a whole management-system, when I could wrap it in a function and be done?
var save_file_reference_and_return_new_handler = function (given_file) {
return function (e) {
var blob_url = e.target.result,
file_name = given_file.name;
//...
};
};
So if you had that at the top of the function (above the loop), you could say:
reader = new FileReader();
reader.onload = save_file_reference_and_return_new_handler(file);
reader.readAsDataUrl(file);
And now it will work just fine.
Of course, JS people don't always feel compelled to write named functions, just to store one item in closure to remember later...
reader.onload = (function (current_file) {
return function (e) {
var blob_url = e.target.result,
file_name = current_file.name;
};
}(file));
I'm using FileReader API to read files on local.
<input type="file" id="filesx" name="filesx[]" onchange="readmultifiles(this.files)" multiple="" />
<script>
function readmultifiles(files) {
var ret = "";
var ul = document.querySelector("#bag>ul");
while (ul.hasChildNodes()) {
ul.removeChild(ul.firstChild);
}
for (var i = 0; i < files.length; i++) //for multiple files
{
var f = files[i];
var name = files[i].name;
alert(name);
var reader = new FileReader();
reader.onload = function(e) {
// get file content
var text = e.target.result;
var li = document.createElement("li");
li.innerHTML = name + "____" + text;
ul.appendChild(li);
}
reader.readAsText(f,"UTF-8");
}
}
</script>
If input includes 2 files:
file1 ---- "content1"
file2 ---- "content2"
I get this output:
file2__content1
file2__content2
How to fix code to display:
file1__content1
file2__content2
The problem is you're running the loop now but the callbacks you are setting are getting run later (when the events fire). By the time they run, the loop is over and remains at whatever the last value was. So it will always show "file2" in your case for the name.
The solution is to put the file name inside a closure with the rest. One way to do this is create an immediately-invoked function expression (IIFE) and pass the file in as a parameter to that function:
for (var i = 0; i < files.length; i++) { //for multiple files
(function(file) {
var name = file.name;
var reader = new FileReader();
reader.onload = function(e) {
// get file content
var text = e.target.result;
var li = document.createElement("li");
li.innerHTML = name + "____" + text;
ul.appendChild(li);
}
reader.readAsText(file, "UTF-8");
})(files[i]);
}
Alternately, you can define a named function and call it as normal:
function setupReader(file) {
var name = file.name;
var reader = new FileReader();
reader.onload = function(e) {
// get file content
var text = e.target.result;
var li = document.createElement("li");
li.innerHTML = name + "____" + text;
ul.appendChild(li);
}
reader.readAsText(file, "UTF-8");
}
for (var i = 0; i < files.length; i++) {
setupReader(files[i]);
}
Instead of using var, use let as the declared variable only be used in one loop.
for (let i = 0; i < files.length; i++) //for multiple files
{
let f = files[i];
let name = files[i].name;
alert(name);
let reader = new FileReader();
reader.onload = function(e) {
// get file content
let text = e.target.result;
let li = document.createElement("li");
li.innerHTML = name + "____" + text;
ul.appendChild(li);
}
reader.readAsText(f,"UTF-8");
}
Edit: Just use let instead of var in the loop. That fixes the issue OP had (but was only introduced in 2015).
Old answer (An interesting workaround):
While it is not exactly robust or future-proof, it is worth mentioning that this can also be achieved by adding a property to the FileReader object:
var reader = new FileReader();
reader._NAME = files[i].name; // create _NAME property that contains filename.
Then access it through e within the onload callback function:
li.innerHTML = e.target._NAME + "____" + text;
Why this works:
Even though the reader variable is replaced multiple times during the loop like i, the new FileReader object is unique and remains in memory. It is accessible within the reader.onload function through the e argument. By storing additional data in the reader object, it is kept in memory and accessible through reader.onload via e.target event argument.
This explains why why your output is:
file2__content1file2__content2
and not:
file1__content1file2__content2
The content is displayed correctly because e.target.result is a property within the FileReader object itself. Had FileReader contained a filename property by default, it could have been used and this whole mess avoided entirely.
A word of caution
This is called extending host objects (if I understand the difference between native objects...). FileReader is the host object that is being extended in this situation. Many professional developers believe doing this is bad practice and/or evil. Collisions may occur if _NAME ever becomes used in the future. This functionality isn't documented in any specification so it could even break in the future, and it may not work in older browsers.
Personally, I have not encountered any issues by adding additional properties to host objects. Assuming the property name is unique enough, browsers don't disable it, and future browsers don't change these objects too much, it should work fine.
Here are some articles that explain this quite well:
http://kendsnyder.com/extending-host-objects-evil-extending-native-objects-not-evil-but-risky/
http://perfectionkills.com/whats-wrong-with-extending-the-dom/
And some article on the problem itself:
http://tobyho.com/2011/11/02/callbacks-in-loops/
You can make a promise/callback for reading the file in the loop.
Promise-
fileBase64(file) {
return new Promise((resolve, reject) => {
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function() {
resolve(reader.result);
};
reader.onerror = function(error) {
reject(error);
};
});
}
I am calling this function on onClick
onClick = async () => {
for (var i = 0; i < this.state.company_bank_statement_array.length; i++) {
let file = document.getElementById(
this.state.company_bank_statement_array[i]
);
let fileData = await this.fileBase64(file.files[0]);
this.state.bankStatements.push({
data: fileData,
filename: file.files[0].name,
});
}
};
I had the same problem, solved it by using Array.from
let files = e.target.files || e.dataTransfer.files;
Array.from(files).forEach(file => {
// do whatever
})
I think the best way to solve this problem is by recursively call a function that reads the blob file. So in my case I solve the problem with the following snippet code, maybe is a little complicated but it works in any scenario that I tried.
Notice that, I didn't pass the array and index as arguments. You need to call them with the object they belong to.
//Initialize blobs
var foo = new Blob(["Lorem ipsum dolor sit amet, consectetur adipiscing elit."], {
type: 'text/plain'
});
var bar = new Blob(["Sed tristique ipsum vitae consequat aliquet"], {
type: 'text/plain'
});
//Initialize array and index
var arrayOfBlobs = [foo, bar];
var arrayIndex = 0;
function fileRead () {
var me = this;
if (this.arrayIndex < this.arrayOfBlobs.length) {
var reader = new FileReader();
function bindedOnload(event) {
console.log("bindedOnload called");
console.log("reader results: ", event.target.result);
this.arrayIndex++; //Incrument the index
this.fileRead(); //Recursive call
}
//By Binding the onload event to the local scope we
//can have access to all local vars and functions
reader.onload = bindedOnload.bind(me);
reader.readAsText(this.arrayOfBlobs[arrayIndex]);
} else {
//This will executed when finishing reading all files
console.log("Finished");
}
}
//Call the fileRead for the first time
fileRead();