Corruption with FileReader into FormData - javascript

I'm building an ajax file uploader, which is possible thanks to the new FormData interface. It works if I use the original file, but if I convert the file to a binary string and then to a blob, the resulting file is corrupted. What am I doing wrong?
<!DOCTYPE html>
<html>
<body>
<form method=post enctype=multipart/form-data id=form>
<input id=file type=file name=file>
<button id=1>1</button>
<button id=2>2</button>
</form>
<script>
var d = document;
function $(id) { return d.getElementById(id); };
function xhr(fd) {
var x = new XMLHttpRequest();
x.open('post', '/', true);
x.send(fd);
};
$(1).addEventListener('click', function(e) {
e.preventDefault();
var file = $('file').files[0];
var fd = new FormData();
fd.append('file', file);
xhr(fd);
}, false
);
$(2).addEventListener('click', function(e) {
e.preventDefault();
var file = $('file').files[0];
var fr = new FileReader();
fr.onload = function(e) {
var bb = new (window.BlobBuilder ||
window.MozBlobBuilder || window.WebKitBlobBuilder)()
bb.append(e.target.result);
var b = bb.getBlob(file.type);
var fd = new FormData();
fd.append('file', b);
xhr(fd);
};
fr.readAsBinaryString(file);
}, false
);
</script>
</body>
</html>
Blob
BlobBuilder
FileReader
FormData
edited to include links

I got it working in Firefox / Chrome by converting the string into a Uint8Array:
var result = e.target.result;
var l = result.length
var ui8a = new Uint8Array(l)
for (var i = 0; i < l; i++)
ui8a[i] = result.charCodeAt(i);
bb.append(ui8a.buffer)

I'm not familiar with most of these objects, but I noticed that you reference file.type in the fr.onload function of your second event listener. Is file in scope at that point? Shouldn't you be using e instead?

Related

File Upload Using jQuery not working in IE

I'm having a difficult time trying to get the below code to work in IE. The code works as expected in Firefox, Chrome, and Edge; but not in IE. I would ignore it not working in IE, but it's the default browser used at work.
The code is written to upload multiple files into a specific SharePoint document library. I got the code from this post https://social.msdn.microsoft.com/Forums/office/en-US/bb590f35-da1b-4905-baa0-fb85a275abf6/multiple-files-upload-in-document-library-using-javascript-object-model?forum=appsforsharepoint. It's the last post, and it does work great in the mentioned browsers. Any suggestions on how to get it to work in IE will greatly be appreciated. Thank you in advance.
Script is below:
jQuery(document).ready(function() {
fileInput = $("#getFile");
SP.SOD.executeFunc('sp.js', 'SP.ClientContext', registerClick);
});
function registerClick() {
//Register File Upload Click Event
jQuery("#addFileButton").on('click', readFile);
}
var arrayBuffer;
function readFile() {
//Get File Input Control and read th file name
var element = document.getElementById("getFile");
var fileCount = element.files.length;
var filesUploaded = 0;
for (var i = 0; i < fileCount; i++) {
let file = element.files[i];
var reader = new FileReader();
reader._NAME = element.files[i].name
reader.onload = function(e) {
let fileactualName = e.target._NAME;
uploadFile(e.target.result, fileactualName);
}
reader.onerror = function(e) {
alert(e.target.error);
}
reader.readAsArrayBuffer(file);
}
}
function uploadFile(arrayBuffer, fileName) {
//Get Client Context,Web and List object.
var clientContext = new SP.ClientContext();
var oWeb = clientContext.get_web();
var oList = oWeb.get_lists().getByTitle('Comms Shared Files');
//Convert the file contents into base64 data
var bytes = new Uint8Array(arrayBuffer);
var i, length, out = '';
for (i = 0, length = bytes.length; i < length; i += 1) {
out += String.fromCharCode(bytes[i]);
}
var base64 = btoa(out);
//Create FileCreationInformation object using the read file data
var createInfo = new SP.FileCreationInformation();
createInfo.set_content(base64);
createInfo.set_url(fileName);
//Add the file to the library
var uploadedDocument = oList.get_rootFolder().get_files().add(createInfo)
//Load client context and execcute the batch
clientContext.load(uploadedDocument);
clientContext.executeQueryAsync(QuerySuccess, QueryFailure);
}
function QuerySuccess() {
alert('File Uploaded Successfully.');
}
function QueryFailure(sender, args) {
console.log('Request failed with error message - ' + args.get_message());
}
In SharePoint 2010, we can use SharePoint designer to open the v4.master(defualt), and add "IE=11" in "X-UA-Compatible".
<meta http-equiv="X-UA-Compatible" content="IE=8,IE=11"/>
In SharePoint 2013/2016/2019/online, we can use REST API to upload the files to document library with jQuery code.
<input id="inputFile" type="file" multiple="multiple"/>
<input id="uploadDocumentButton" type="Button" value="Upload Document">
<script src="https://code.jquery.com/jquery-1.12.4.min.js" type="text/javascript"></script>
<script type="text/javascript">
var libraryTitle="DL";
$(function(){
$("#uploadDocumentButton").click(function () {
if (document.getElementById("inputFile").files.length === 0) {
alert("Select a file!");
return;
}
for(var i = 0; i < document.getElementById("inputFile").files.length; i++){
var file = document.getElementById("inputFile").files[i];
uploadFileSync(libraryTitle, file.name, file);
}
alert("upload complete.");
});
});
function uploadFileSync(folderUrl, filename, file){
var reader = new FileReader();
reader.onloadend = function(evt){
if (evt.target.readyState == FileReader.DONE){
var buffer = evt.target.result;
var completeUrl =_spPageContextInfo.webAbsoluteUrl
+ "/_api/web/GetFolderByServerRelativeUrl('"+folderUrl+"')/Files/add(url='" + filename + "',overwrite=true)";
$.ajax({
url: completeUrl,
type: "POST",
data: buffer,
async: false,
processData: false,
headers: {
"accept": "application/json;odata=verbose",
"X-RequestDigest": $("#__REQUESTDIGEST").val(),
"content-length": buffer.byteLength
},
complete: function (data) {
//alert("upload complete.");
//console.log(data.responseJSON.d.ServerRelativeUrl);
},
error: function (err) {
alert('failed');
}
});
}
};
reader.readAsArrayBuffer(file);
}
</script>

How to to base 64 encode in two images in same file

Here i have two form fields , in this i want to convert the base 64 encode from image,and after that i want to pass in JSON Format to next URL, i tried like this but i am not getting ,some time i am getting one filed value (encoded value) uncaught error property_img , i don't know how to do anyone help me pls
var files = document.getElementById('floorplan_img').files;
if (files.length > 0) {
var file = files[0];
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function() {
console.log(reader.result);
var base64 = reader.result;
var str = base64;
var arr = str.split(",");
var floor_img = arr[1];
console.log(floor_img);
};
reader.onerror = function(error) {
console.log('Error: ', error);
};
}
var files1 = document.getElementById('upload_properties').files;
if (files1.length > 0) {
var file1 = files1[0];
var reader1 = new FileReader();
reader1.readAsDataURL(file1);
reader1.onload = function() {
var base64_img = reader1.result;
var str_64 = base64_img;
var arr_str = str_64.split(",");
var property_img = arr_str[1];
console.log(property_img);
};
reader1.onerror = function(error) {
console.log('Error: ', error);
};
}
<input type="file" id="floorplan_img">
<input type="file" id="upload_properties">
Try this:
var reader = new FileReader();
reader.onload = function(event) {
var src= event.target.result;
console.log(src);
};

Convert input=file to byte array

I try to convert a file that i get through an input file into a byte[].
I tried with a FileReader, but i must miss something :
var bytes = [];
var reader = new FileReader();
reader.onload = function () {
bytes = reader.result;
};
reader.readAsArrayBuffer(myFile);
But in the end, my bytes var doesn't content a byte array.
I saw this post : Getting byte array through input type = file but it doesn't ends with a byte[], and readAsBinaryString() is deprecated
What do i miss?
Faced a similar problem and its true the 'reader.result' doesn't end up as 'byte[]'. So I have cast it to Uint8Array object. This too is not a perfect 'byte[]' ,so I had to create a 'byte[]' from it. Here is my solution to this problem and it worked well for me.
var reader = new FileReader();
var fileByteArray = [];
reader.readAsArrayBuffer(myFile);
reader.onloadend = function (evt) {
if (evt.target.readyState == FileReader.DONE) {
var arrayBuffer = evt.target.result,
array = new Uint8Array(arrayBuffer);
for (var i = 0; i < array.length; i++) {
fileByteArray.push(array[i]);
}
}
}
'fileByteArray' is what you are looking for. Saw the comments and seems you did the same, still wanted to share the approach.
Seems to me you just want to get files into an array? How about these functions - one where you can read it as text, another as a base64 byte string, and if you really want the readAsArrayBuffer array buffer output, I've included that, too:
document.getElementById("myBtn").addEventListener("click", function() {
uploadFile3();
});
var fileByteArray = [];
function uploadFile1(){
var files = myInput.files[0];
var reader = new FileReader();
reader.onload = processFile(files);
reader.readAsText(files);
}
function uploadFile2(){
var files = document.querySelector('input').files[0];
var reader = new FileReader();
reader.onload = processFile(files);
reader.readAsDataURL(files);
}
function uploadFile3(){
var files = myInput.files[0];
var reader = new FileReader();
reader.onload = processFile(files);
reader.readAsArrayBuffer(files);
}
function processFile(theFile){
return function(e) {
var theBytes = e.target.result; //.split('base64,')[1]; // use with uploadFile2
fileByteArray.push(theBytes);
document.getElementById('file').innerText = '';
for (var i=0; i<fileByteArray.length; i++) {
document.getElementById('file').innerText += fileByteArray[i];
}
}
}
<input id="myInput" type="file">
<button id="myBtn">Try it</button>
<span id="file"></span>
this works very well for me in React JS:
const handleUpload = async (e) => {
let image = e.currentTarget.files[0];
const buffer = await image.arrayBuffer();
let byteArray = new Int8Array(buffer);
console.log(byteArray)
formik.setFieldValue(name, byteArray);
}
Here is a modified, and in my opinion easier version of the accepted answer. This function returns a Promise with a value of the byte[].
function fileToByteArray(file) {
return new Promise((resolve, reject) => {
try {
let reader = new FileReader();
let fileByteArray = [];
reader.readAsArrayBuffer(file);
reader.onloadend = (evt) => {
if (evt.target.readyState == FileReader.DONE) {
let arrayBuffer = evt.target.result,
array = new Uint8Array(arrayBuffer);
for (byte of array) {
fileByteArray.push(byte);
}
}
resolve(fileByteArray);
}
}
catch (e) {
reject(e);
}
})
}
This way you can simply call this function in an async function like this
async function getByteArray() {
//Get file from your input element
let myFile = document.getElementById('myFileInput').files[0];
//Wait for the file to be converted to a byteArray
let byteArray = await fileToByteArray(myFile);
//Do something with the byteArray
console.log(byteArray);
}

Parsing Excel sheet with js-xlsx

I am trying to parse all the excel files in a directory specified by the user but the js-xlsx library I am using seems to need manual navigation.
var url = "/test-files/test.xlsx"; <-------- Located in the project directory
var oReq = new XMLHttpRequest();
oReq.open("GET", url, true);
oReq.responseType = "arraybuffer";
oReq.onload = function(e) {
var arraybuffer = oReq.response;
var data = new Uint8Array(arraybuffer);
var arr = new Array();
for (var i = 0; i != data.length; i++) arr[i] = String.fromCharCode(data[i]);
var bstr = arr.join("");
var workbook = XLSX.read(bstr, {
type: "binary"
});
}
oReq.send();
The code needs to be dynamic in that it can open an excel file anywhere.
Is there any way that I can use a fileentry object with js-xlsx library to parse an excel file?
For those who may be curious I figured out a solution. In order to dynamically create a path to a given fileEntry object you must first convert it into a Blob:
fileEntryObject.file(function(file) {});
Then convert it to a window.URL that way your project may have access to a readable path to the desired file:
var newPath = window.URL.createObjectURL(file);
Thus you may use it like a regular path in your functions even if you don't know how to navigate from your project to the file:
fileEntryObject.file(function(file) {
var newPath = window.URL.createObjectURL(file);
var oReq = new XMLHttpRequest();
oReq.open("GET", newPath, true);
oReq.responseType = "arraybuffer";
oReq.onError = function(e) {
console.log('Error in reading excel file');
};
oReq.onload = function(e) {
var arraybuffer = oReq.response;
var data = new Uint8Array(arraybuffer);
var arr = new Array();
for (var i = 0; i != data.length; ++i) arr[i] = String.fromCharCode(data[i]);
var bstr = arr.join("");
var workbook = XLSX.read(bstr, {
type: "binary"
});
var sheet_name = workbook.SheetNames[1];
var worksheet = workbook.Sheets[sheet_name];
self.parseReceive(worksheet, callback);
// self.parseReceive(worksheet);
};
oReq.send();
});

JavaScript readAsBinaryString Function on E11

In this page http://www.html5rocks.com/en/tutorials/file/dndfiles/ if you scroll down to example "Example: Slicing a file. Try it!" you will see uses of readAsBinaryString API to read bytes of local files.
I've seen IE (My case its IE11) doesn't support readAsBinaryString.
Even this code mentioned in post HTML5 File API read as text and binary breaks at readAsBinaryString in IE11.
I have seen some post in stack overflow, it suggests use of ReadAsArrayBuffer(). But it is also not working. It returns undefined.
My question is what are the options if I have to run it on IE11? Is it possible to write another IE compatible JS function which will do the JOB of readAsBinaryString().
I combine #Jack answer with my comment to show a complete working example.
In the <head> section I added this script to add FileReader.readAsBinaryString function in IE11
if (FileReader.prototype.readAsBinaryString === undefined) {
FileReader.prototype.readAsBinaryString = function (fileData) {
var binary = "";
var pt = this;
var reader = new FileReader();
reader.onload = function (e) {
var bytes = new Uint8Array(reader.result);
var length = bytes.byteLength;
for (var i = 0; i < length; i++) {
binary += String.fromCharCode(bytes[i]);
}
//pt.result - readonly so assign content to another property
pt.content = binary;
pt.onload(); // thanks to #Denis comment
}
reader.readAsArrayBuffer(fileData);
}
}
Then I needed to slightly modify my original script code because target.result has no value when using this fallback function.
var reader = new FileReader();
reader.onload = function (e) {
// ADDED CODE
if (!e) {
var data = reader.content;
}
else {
var data = e.target.result;
}
// business code
};
reader.readAsBinaryString(myFile);
This is my solution.
var reader = new FileReader();
reader.readAsBinaryString(fileData);
reader.onload = function(e) {
if (reader.result) reader.content = reader.result;
var base64Data = btoa(reader.content);
//...
}
//extend FileReader
if (!FileReader.prototype.readAsBinaryString) {
FileReader.prototype.readAsBinaryString = function (fileData) {
var binary = "";
var pt = this;
var reader = new FileReader();
reader.onload = function (e) {
var bytes = new Uint8Array(reader.result);
var length = bytes.byteLength;
for (var i = 0; i < length; i++) {
binary += String.fromCharCode(bytes[i]);
}
//pt.result - readonly so assign binary
pt.content = binary;
$(pt).trigger('onload');
}
reader.readAsArrayBuffer(fileData);
}
}
FileReader.readAsBinaryString is a non-standard function and has been deprecated.
FileReader.readAsArrayBuffer should be used instead.
MDN
For IE 11 you can use this XHR trick:
function blobToBinaryStringIE11(blob) {
var blobURL = URL.createObjectURL(blob);
var xhr = new XMLHttpRequest;
xhr.open("get", blobURL);
xhr.overrideMimeType("text/plain; charset=x-user-defined");
xhr.onload = function () {
var binary = xhr.response;
// do stuff
};
xhr.send();
}
It's 20x faster than the Uint8Array + fromCharCode route and as fast as readAsBinaryString.
Replace
reader.readAsBinaryString(blob);
with:
reader.readAsText(blob);
it's works well in cross browser.
I had some problems with the answers here and ended up making a few slight changes.
Instead of assigning to pt.content, my solution is to send a custom object to the prototype's onload, that receiver can specifically look for, I named this property msieContent so it will be very specific.
Also I used other accepted answer for converting Uint8Array to string in more robust way, you can see full details of it here:
https://stackoverflow.com/a/12713326/213050
Polyfill
if (FileReader.prototype.readAsBinaryString === undefined) {
// https://stackoverflow.com/a/12713326/213050
function Uint8ToString(u8a: Uint8Array) {
const CHUNK_SZ = 0x8000;
let c = [];
for (let i = 0; i < u8a.length; i += CHUNK_SZ) {
c.push(String.fromCharCode.apply(null, u8a.subarray(i, i + CHUNK_SZ)));
}
return c.join('');
}
FileReader.prototype.readAsBinaryString = function (fileData) {
const reader = new FileReader();
reader.onload = () => this.onload({
msieContent: Uint8ToString(new Uint8Array(<any>reader.result))
});
reader.readAsArrayBuffer(fileData);
}
}
Usage
private _handleTextFile(file: File) {
const reader = new FileReader();
reader.onload = (e) => {
// support for msie, see polyfills.ts
const readResult: string = (<any>e).msieContent || <string>e.target.result;
};
reader.readAsBinaryString(file);
}

Categories

Resources