So I am using Jupyter 4.x and python 3.5, trying to "upload" a .tsv, but really just trying to capture it as a string and then use
setTimeout(function(){IPython.notebook.kernel.execute("stringData=StringIO(+"fr.result")");},5000);
to allow time for the FileReader() object to complete the binary-string conversion then save the string into a python variable. I tested that the Filereader() was working by printing the entire .tsv file in the console. For some reason, though, stringData remains undefined on the python side.Here is the javascript cell:
%%HTML
<input type="file" id="CSVFileInput" onchange="handleFiles(this.files)" value="upload csv">
<script>
var inputElement=document.getElementById('CSVFileInput');
function handleFiles() {
var file = inputElement.files[0];
var fr = new FileReader();
fr.readAsText(file);
var outputString=fr.result;
var command = "dataString ='"+outputString+"'";
setTimeout(function(){
IPython.notebook.kernel.execute(command);}
,5000);
}
inputElement.addEventListener("change", handleFiles, false);
</script>
And in the next cell I test the output and get NameError because dataString is undefined, here is the next cell:
dataString
Also, I'm kind of new to javascript, so any and all advice is welcome, I only think this is the easy way. Pls? And, of course, thanks a lot!
FileReader methods are asynchronous so there is no data loaded at the time you try to set outputString to the result.
The proper way to handle this is to use the load event handler, so instead of using setTimeout(), which is unreliable for asynchronous handling, you can modify the code to the following:
function handleFiles() {
var file = this.files[0]; // "this" is the calling element
var fr = new FileReader();
fr.onload = function() {
var outputString = this.result; // here the data is ready. Now "this" = fr
var command = "dataString ='" + outputString + "'";
IPython.notebook.kernel.execute(command);
};
fr.readAsText(file); // invoked asynchronously
}
Also remove the inline JavaScript in the HTML:
<input type="file" id="CSVFileInput" onchange="handleFiles(this.files)" value="upload csv">
to
<input type="file" id="CSVFileInput" title="upload csv">
(value has no effect when input is type=file, use title instead). Then use the following code to take care of event handling (after the DOM has loaded):
document.getElementById("CSVFileInput").addEventListener("change", handleFiles);
function handleFiles() {
var file = this.files[0]; // "this" is the calling element
var fr = new FileReader();
fr.onload = function() {
var outputString = this.result; // here the data is ready. Now "this" = fr
var command = "dataString ='" + outputString + "'";
//IPython.notebook.kernel.execute(command);
console.log("Loaded file. Command:", command);
};
fr.readAsText(file); // invoked asynchronously
}
document.getElementById("CSVFileInput").addEventListener("change", handleFiles);
<input type="file" id="CSVFileInput" title="upload csv">
So, #K3N definitely gave me a valuable piece of the puzzle and lesson in async functions. However, the main issue was that python did not recognize the string inputs it was receiving from javascript, so I thought I'd share my journey with all. I ended up converting the string to a 2d javascript array, then I shift() the first row off for the column names, transpose the remaining rows, and clean out all the dumb stuff from english that stops it from working (quotes and apostrophes). Now I can pd.DataFrame(dict(zip(colNames,cols))) and run all the calculations I run on the same .tsv when I read it in from my filesystem. Here is the full script, basically the fixes #K3N showed me plus the pythonify(arr) function:
<input type="file" id="TSVFileInput" title="upload tsv">
function handleFiles() {
//read in file and instantiate filereader
let file = this.files[0];// "this" is the calling element
let fr = new FileReader();
fr.onload = function() {
//split on row delimeter (CRLF)
let outputBuffer = this.result.split("\r\n");
let command;
// split outputBuffer into 2d array
outputBuffer= outputBuffer.map(line => line.split("\t"));
//pop names row from output Buffer
let names=outputBuffer.shift();
//optimized transpose
outputBuffer=outputBuffer.reduce(
(temp,row) =>
row.map((element,i) =>
(temp[i] || []).concat(element))
,[] //initializes temp
);
//build python command
command="colNames ="+pythonify(names);
//send command to notebook kernel
IPython.notebook.kernel.execute(command);
//loop appends columns on python side
for(let i=0 ; i< outputBuffer.length ; i++){
command="cols.append("+pythonify(outputBuffer[i])+")";
IPython.notebook.kernel.execute(command); //send command to kernel
}
}; //end fr.onload()
fr.readAsText(file); // invoked asynchronously, triggers fr.onload
}//end handleFiles()
function pythonify (arr){
//turns javascript array into string representation of python list
let out= '[';
for(let i=0 ; i<arr.length ; i++){
var element=arr[i];
//format double and single quotes
element=element.replace(/\"/g,'\\"').replace(/'/g,"\\'");
//use python raw string
out+='r"'+element+'"';
if(i<arr.length-1){ //skip last comma
out+=',';
}
}
out+=']';
return out;
}// end pythonify(arr)
document.getElementById("CSVFileInput").addEventListener("change", handleFiles);
Related
var profileImage = fileInputInByteArray;
$.ajax({
url: 'abc.com/',
type: 'POST',
dataType: 'json',
data: {
// Other data
ProfileImage: profileimage
// Other data
},
success: {
}
})
// Code in WebAPI
[HttpPost]
public HttpResponseMessage UpdateProfile([FromUri]UpdateProfileModel response) {
//...
return response;
}
public class UpdateProfileModel {
// ...
public byte[] ProfileImage {get ;set; }
// ...
}
<input type="file" id="inputFile" />
I am using ajax call to post byte[] value of a input type = file input to web api which receives in byte[] format. However, I am experiencing difficulty of getting byte array. I am expecting that we can get the byte array through File API.
Note: I need to store the byte array in a variable first before passing through ajax call
[Edit]
As noted in comments above, while still on some UA implementations, readAsBinaryString method didn't made its way to the specs and should not be used in production.
Instead, use readAsArrayBuffer and loop through it's buffer to get back the binary string :
document.querySelector('input').addEventListener('change', function() {
var reader = new FileReader();
reader.onload = function() {
var arrayBuffer = this.result,
array = new Uint8Array(arrayBuffer),
binaryString = String.fromCharCode.apply(null, array);
console.log(binaryString);
}
reader.readAsArrayBuffer(this.files[0]);
}, false);
<input type="file" />
<div id="result"></div>
For a more robust way to convert your arrayBuffer in binary string, you can refer to this answer.
[old answer] (modified)
Yes, the file API does provide a way to convert your File, in the <input type="file"/> to a binary string, thanks to the FileReader Object and its method readAsBinaryString.
[But don't use it in production !]
document.querySelector('input').addEventListener('change', function(){
var reader = new FileReader();
reader.onload = function(){
var binaryString = this.result;
document.querySelector('#result').innerHTML = binaryString;
}
reader.readAsBinaryString(this.files[0]);
}, false);
<input type="file"/>
<div id="result"></div>
If you want an array buffer, then you can use the readAsArrayBuffer() method :
document.querySelector('input').addEventListener('change', function(){
var reader = new FileReader();
reader.onload = function(){
var arrayBuffer = this.result;
console.log(arrayBuffer);
document.querySelector('#result').innerHTML = arrayBuffer + ' '+arrayBuffer.byteLength;
}
reader.readAsArrayBuffer(this.files[0]);
}, false);
<input type="file"/>
<div id="result"></div>
$(document).ready(function(){
(function (document) {
var input = document.getElementById("files"),
output = document.getElementById("result"),
fileData; // We need fileData to be visible to getBuffer.
// Eventhandler for file input.
function openfile(evt) {
var files = input.files;
// Pass the file to the blob, not the input[0].
fileData = new Blob([files[0]]);
// Pass getBuffer to promise.
var promise = new Promise(getBuffer);
// Wait for promise to be resolved, or log error.
promise.then(function(data) {
// Here you can pass the bytes to another function.
output.innerHTML = data.toString();
console.log(data);
}).catch(function(err) {
console.log('Error: ',err);
});
}
/*
Create a function which will be passed to the promise
and resolve it when FileReader has finished loading the file.
*/
function getBuffer(resolve) {
var reader = new FileReader();
reader.readAsArrayBuffer(fileData);
reader.onload = function() {
var arrayBuffer = reader.result
var bytes = new Uint8Array(arrayBuffer);
resolve(bytes);
}
}
// Eventlistener for file input.
input.addEventListener('change', openfile, false);
}(document));
});
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<input type="file" id="files"/>
<div id="result"></div>
</body>
</html>
Modern browsers now have the arrayBuffer method on Blob's:
document.querySelector('input').addEventListener('change', async (event) => {
const buffer = await event.target.files[0].arrayBuffer()
console.log(buffer)
}, false)
🎉 🎉
This is a long post, but I was tired of all these examples that weren't working for me because they used Promise objects or an errant this that has a different meaning when you are using Reactjs. My implementation was using a DropZone with reactjs, and I got the bytes using a framework similar to what is posted at this following site, when nothing else above would work: https://www.mokuji.me/article/drop-upload-tutorial-1 . There were 2 keys, for me:
You have to get the bytes from the event object, using and during a FileReader's onload function.
I tried various combinations, but in the end, what worked was:
const bytes = e.target.result.split('base64,')[1];
Where e is the event. React requires const, you could use var in plain Javascript. But that gave me the base64 encoded byte string.
So I'm just going to include the applicable lines for integrating this as if you were using React, because that's how I was building it, but try to also generalize this, and add comments where necessary, to make it applicable to a vanilla Javascript implementation - caveated that I did not use it like that in such a construct to test it.
These would be your bindings at the top, in your constructor, in a React framework (not relevant to a vanilla Javascript implementation):
this.uploadFile = this.uploadFile.bind(this);
this.processFile = this.processFile.bind(this);
this.errorHandler = this.errorHandler.bind(this);
this.progressHandler = this.progressHandler.bind(this);
And you'd have onDrop={this.uploadFile} in your DropZone element. If you were doing this without React, this is the equivalent of adding the onclick event handler you want to run when you click the "Upload File" button.
<button onclick="uploadFile(event);" value="Upload File" />
Then the function (applicable lines... I'll leave out my resetting my upload progress indicator, etc.):
uploadFile(event){
// This is for React, only
this.setState({
files: event,
});
console.log('File count: ' + this.state.files.length);
// You might check that the "event" has a file & assign it like this
// in vanilla Javascript:
// var files = event.target.files;
// if (!files && files.length > 0)
// files = (event.dataTransfer ? event.dataTransfer.files :
// event.originalEvent.dataTransfer.files);
// You cannot use "files" as a variable in React, however:
const in_files = this.state.files;
// iterate, if files length > 0
if (in_files.length > 0) {
for (let i = 0; i < in_files.length; i++) {
// use this, instead, for vanilla JS:
// for (var i = 0; i < files.length; i++) {
const a = i + 1;
console.log('in loop, pass: ' + a);
const f = in_files[i]; // or just files[i] in vanilla JS
const reader = new FileReader();
reader.onerror = this.errorHandler;
reader.onprogress = this.progressHandler;
reader.onload = this.processFile(f);
reader.readAsDataURL(f);
}
}
}
There was this question on that syntax, for vanilla JS, on how to get that file object:
JavaScript/HTML5/jQuery Drag-And-Drop Upload - "Uncaught TypeError: Cannot read property 'files' of undefined"
Note that React's DropZone will already put the File object into this.state.files for you, as long as you add files: [], to your this.state = { .... } in your constructor. I added syntax from an answer on that post on how to get your File object. It should work, or there are other posts there that can help. But all that Q/A told me was how to get the File object, not the blob data, itself. And even if I did fileData = new Blob([files[0]]); like in sebu's answer, which didn't include var with it for some reason, it didn't tell me how to read that blob's contents, and how to do it without a Promise object. So that's where the FileReader came in, though I actually tried and found I couldn't use their readAsArrayBuffer to any avail.
You will have to have the other functions that go along with this construct - one to handle onerror, one for onprogress (both shown farther below), and then the main one, onload, that actually does the work once a method on reader is invoked in that last line. Basically you are passing your event.dataTransfer.files[0] straight into that onload function, from what I can tell.
So the onload method calls my processFile() function (applicable lines, only):
processFile(theFile) {
return function(e) {
const bytes = e.target.result.split('base64,')[1];
}
}
And bytes should have the base64 bytes.
Additional functions:
errorHandler(e){
switch (e.target.error.code) {
case e.target.error.NOT_FOUND_ERR:
alert('File not found.');
break;
case e.target.error.NOT_READABLE_ERR:
alert('File is not readable.');
break;
case e.target.error.ABORT_ERR:
break; // no operation
default:
alert('An error occurred reading this file.');
break;
}
}
progressHandler(e) {
if (e.lengthComputable){
const loaded = Math.round((e.loaded / e.total) * 100);
let zeros = '';
// Percent loaded in string
if (loaded >= 0 && loaded < 10) {
zeros = '00';
}
else if (loaded < 100) {
zeros = '0';
}
// Display progress in 3-digits and increase bar length
document.getElementById("progress").textContent = zeros + loaded.toString();
document.getElementById("progressBar").style.width = loaded + '%';
}
}
And applicable progress indicator markup:
<table id="tblProgress">
<tbody>
<tr>
<td><b><span id="progress">000</span>%</b> <span className="progressBar"><span id="progressBar" /></span></td>
</tr>
</tbody>
</table>
And CSS:
.progressBar {
background-color: rgba(255, 255, 255, .1);
width: 100%;
height: 26px;
}
#progressBar {
background-color: rgba(87, 184, 208, .5);
content: '';
width: 0;
height: 26px;
}
EPILOGUE:
Inside processFile(), for some reason, I couldn't add bytes to a variable I carved out in this.state. So, instead, I set it directly to the variable, attachments, that was in my JSON object, RequestForm - the same object as my this.state was using. attachments is an array so I could push multiple files. It went like this:
const fileArray = [];
// Collect any existing attachments
if (RequestForm.state.attachments.length > 0) {
for (let i=0; i < RequestForm.state.attachments.length; i++) {
fileArray.push(RequestForm.state.attachments[i]);
}
}
// Add the new one to this.state
fileArray.push(bytes);
// Update the state
RequestForm.setState({
attachments: fileArray,
});
Then, because this.state already contained RequestForm:
this.stores = [
RequestForm,
]
I could reference it as this.state.attachments from there on out. React feature that isn't applicable in vanilla JS. You could build a similar construct in plain JavaScript with a global variable, and push, accordingly, however, much easier:
var fileArray = new Array(); // place at the top, before any functions
// Within your processFile():
var newFileArray = [];
if (fileArray.length > 0) {
for (var i=0; i < fileArray.length; i++) {
newFileArray.push(fileArray[i]);
}
}
// Add the new one
newFileArray.push(bytes);
// Now update the global variable
fileArray = newFileArray;
Then you always just reference fileArray, enumerate it for any file byte strings, e.g. var myBytes = fileArray[0]; for the first file.
This is simple way to convert files to Base64 and avoid "maximum call stack size exceeded at FileReader.reader.onload" with the file has big size.
document.querySelector('#fileInput').addEventListener('change', function () {
var reader = new FileReader();
var selectedFile = this.files[0];
reader.onload = function () {
var comma = this.result.indexOf(',');
var base64 = this.result.substr(comma + 1);
console.log(base64);
}
reader.readAsDataURL(selectedFile);
}, false);
<input id="fileInput" type="file" />
document.querySelector('input').addEventListener('change', function(){
var reader = new FileReader();
reader.onload = function(){
var arrayBuffer = this.result,
array = new Uint8Array(arrayBuffer),
binaryString = String.fromCharCode.apply(null, array);
console.log(binaryString);
console.log(arrayBuffer);
document.querySelector('#result').innerHTML = arrayBuffer + ' '+arrayBuffer.byteLength;
}
reader.readAsArrayBuffer(this.files[0]);
}, false);
<input type="file"/>
<div id="result"></div>
Here is one answer to get the actual final byte array , just using FileReader and ArrayBuffer :
const test_function = async () => {
... ... ...
const get_file_array = (file) => {
return new Promise((acc, err) => {
const reader = new FileReader();
reader.onload = (event) => { acc(event.target.result) };
reader.onerror = (err) => { err(err) };
reader.readAsArrayBuffer(file);
});
}
const temp = await get_file_array(files[0])
console.log('here we finally ve the file as a ArrayBuffer : ',temp);
const fileb = new Uint8Array(fileb)
... ... ...
}
where file is directly the File object u want to read , this has to be done in a async function...
i am facing the issue i always get the last image in my image array due to kind of Filereader library function onloadend.
how can i get base64 for all images in my folder.
<input id="file-input" multiple webkitdirectory type="file" />
var input = document.getElementById('file-input');
var file_names = "";
var entries_length = 0;
var entries_count = 0;
var image = new Array();
var obj = {};
var j = 0;
input.onchange = function(e) {
var files = e.target.files; // FileList
entries_length = files.length;
console.log(files);
for (var i = 0, f; f = files[i]; ++i){
console.log("i:"+i);
entries_count = entries_count + 1;
//console.debug(files[i].webkitRelativePath);
if(files[i].type=="image/jpeg")
{
var string = files[i].webkitRelativePath;
var name = string.split("/")[3]; //this is because my image in 3rd dir in the folder
var reader = new FileReader();
reader.onloadend = function() {
obj.name = string.split("/")[3];
obj.image = reader.result;
image[j] = obj;
j = j+1;
}
reader.readAsDataURL(files[i]);
}
}
console.log(image);
}
The issue is caused by the asynchronous loading of files. You iterate over the array and set the onloadend handler for the reader each time, then start loading by calling readAsDataURL.
One problem is that by the time your first image loads, it is possible the for loop has completed, and i is already at the last index of the array.
At this point, obtaining the path from files[i].webkitRelativePath will give you the last filename, and not the one you are expecting.
Check the example for readAsDataURL on MDN to see one possible solution - each load is performed in a separate function, which preserves its scope, along with file.name. Do not be put off by the construction they are using: [].forEach.call(files, readAndPreview). This is a way to map over the files, which are a FileList and not a regular array (so the list does not have a forEach method of its own).
So, it should be sufficient to wrap the loading logic in a function which takes the file object as a parameter:
var images = [];
function loadFile(f) {
var reader = new FileReader();
reader.onloadend = function () {
images.push({
name : f.name, // use whatever naming magic you prefer here
image : reader.result
});
};
reader.readAsDataURL(f);
}
for (var i=0; i<files.length; i++) {
loadFile(files[i]);
}
Each call of the function 'remembers' the file object it was called with, and prevents the filename from getting messed up. If you are interested, read up on closures.
This also has the nice effect of isolating your reader objects, because I have a sneaking suspicion that, although you create a new 'local' reader each iteration, javascript scoping rules are weird and readers could also be interfering with each other (what happens if one reader is loading, but in the same scope you create a new reader with the same variable name? Not sure).
Now, you do not know how long it would take for all images to be loaded, so if you want to take an action right after that, you would have to perform a check each time an onloadend gets called. This is the essence of asynchronous behavior.
As an aside, I should note that it is pointless to manually keep track of the last index of images, which is j. You should just use images.push({ name: "bla", image: "base64..." }). Keeping indices manually opens up possibilities for bugs.
I am very new to coding and javascript; just a few days in. I was wondering if there was a way to import objects from a text file(separated by lines) to use in my array: replyText. Here is what I'm working with:
// Variables
var theButton = document.getElementById("theButton");
var mainText = document.getElementById("mainText");
var replyText = [...,...,...,...,];
var i = 0;
// Functions
function nextText() {
mainText.innerHTML = replyText[i++ % replyText.length];
}
// MAIN SCRIPT
theButton.onclick = function() {
nextText();
};
You can use XMLHttpRequest to get the .txt file just pass the path of it.
var file = new XMLHttpRequest();
file.open("GET", "file:/../file.txt", false);
file.onreadystatechange = function () {
if (file.readyState === 4) {
if (file.status === 200 || file.status == 0) {
var text = file.responseText;
alert(text);
}
}
}
EDIT: you must pass the absolute path file:///C:/your/path/to/file.txt
For client/browser-side file reading:
You cannot easily read a file on the client-side as you are not allowed direct access to the client's file system. However, you can place a input element of file type in your HTML markup via which the client can load a file for your program to process. For example:
<input type="file" id="file" onchange="readFile()" />
Now when the client selects a file for use, the readFile() function will be called which will read and process the file. Here's an example:
function readFile() {
var file = document.getElementById('file').files[0]; // select the input element from the DOM
var fileReader = new FileReader(); // initialize a new File Reader object
fileReader.onload(function() { // call this function when file is loaded
console.log(this.result); // <--- You can access the file data from this variable
// Do necessary processing on the file
});
fileReader.readAsText(file); // Read the file as text
}
For more information on File Reader, check out the docs.
To add on to Paulo's solution, read below for splitting string by line breaks (new line character)
var replyText = text.split("\n"); // "\n" is new line character
var profileImage = fileInputInByteArray;
$.ajax({
url: 'abc.com/',
type: 'POST',
dataType: 'json',
data: {
// Other data
ProfileImage: profileimage
// Other data
},
success: {
}
})
// Code in WebAPI
[HttpPost]
public HttpResponseMessage UpdateProfile([FromUri]UpdateProfileModel response) {
//...
return response;
}
public class UpdateProfileModel {
// ...
public byte[] ProfileImage {get ;set; }
// ...
}
<input type="file" id="inputFile" />
I am using ajax call to post byte[] value of a input type = file input to web api which receives in byte[] format. However, I am experiencing difficulty of getting byte array. I am expecting that we can get the byte array through File API.
Note: I need to store the byte array in a variable first before passing through ajax call
[Edit]
As noted in comments above, while still on some UA implementations, readAsBinaryString method didn't made its way to the specs and should not be used in production.
Instead, use readAsArrayBuffer and loop through it's buffer to get back the binary string :
document.querySelector('input').addEventListener('change', function() {
var reader = new FileReader();
reader.onload = function() {
var arrayBuffer = this.result,
array = new Uint8Array(arrayBuffer),
binaryString = String.fromCharCode.apply(null, array);
console.log(binaryString);
}
reader.readAsArrayBuffer(this.files[0]);
}, false);
<input type="file" />
<div id="result"></div>
For a more robust way to convert your arrayBuffer in binary string, you can refer to this answer.
[old answer] (modified)
Yes, the file API does provide a way to convert your File, in the <input type="file"/> to a binary string, thanks to the FileReader Object and its method readAsBinaryString.
[But don't use it in production !]
document.querySelector('input').addEventListener('change', function(){
var reader = new FileReader();
reader.onload = function(){
var binaryString = this.result;
document.querySelector('#result').innerHTML = binaryString;
}
reader.readAsBinaryString(this.files[0]);
}, false);
<input type="file"/>
<div id="result"></div>
If you want an array buffer, then you can use the readAsArrayBuffer() method :
document.querySelector('input').addEventListener('change', function(){
var reader = new FileReader();
reader.onload = function(){
var arrayBuffer = this.result;
console.log(arrayBuffer);
document.querySelector('#result').innerHTML = arrayBuffer + ' '+arrayBuffer.byteLength;
}
reader.readAsArrayBuffer(this.files[0]);
}, false);
<input type="file"/>
<div id="result"></div>
$(document).ready(function(){
(function (document) {
var input = document.getElementById("files"),
output = document.getElementById("result"),
fileData; // We need fileData to be visible to getBuffer.
// Eventhandler for file input.
function openfile(evt) {
var files = input.files;
// Pass the file to the blob, not the input[0].
fileData = new Blob([files[0]]);
// Pass getBuffer to promise.
var promise = new Promise(getBuffer);
// Wait for promise to be resolved, or log error.
promise.then(function(data) {
// Here you can pass the bytes to another function.
output.innerHTML = data.toString();
console.log(data);
}).catch(function(err) {
console.log('Error: ',err);
});
}
/*
Create a function which will be passed to the promise
and resolve it when FileReader has finished loading the file.
*/
function getBuffer(resolve) {
var reader = new FileReader();
reader.readAsArrayBuffer(fileData);
reader.onload = function() {
var arrayBuffer = reader.result
var bytes = new Uint8Array(arrayBuffer);
resolve(bytes);
}
}
// Eventlistener for file input.
input.addEventListener('change', openfile, false);
}(document));
});
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<input type="file" id="files"/>
<div id="result"></div>
</body>
</html>
Modern browsers now have the arrayBuffer method on Blob's:
document.querySelector('input').addEventListener('change', async (event) => {
const buffer = await event.target.files[0].arrayBuffer()
console.log(buffer)
}, false)
🎉 🎉
This is a long post, but I was tired of all these examples that weren't working for me because they used Promise objects or an errant this that has a different meaning when you are using Reactjs. My implementation was using a DropZone with reactjs, and I got the bytes using a framework similar to what is posted at this following site, when nothing else above would work: https://www.mokuji.me/article/drop-upload-tutorial-1 . There were 2 keys, for me:
You have to get the bytes from the event object, using and during a FileReader's onload function.
I tried various combinations, but in the end, what worked was:
const bytes = e.target.result.split('base64,')[1];
Where e is the event. React requires const, you could use var in plain Javascript. But that gave me the base64 encoded byte string.
So I'm just going to include the applicable lines for integrating this as if you were using React, because that's how I was building it, but try to also generalize this, and add comments where necessary, to make it applicable to a vanilla Javascript implementation - caveated that I did not use it like that in such a construct to test it.
These would be your bindings at the top, in your constructor, in a React framework (not relevant to a vanilla Javascript implementation):
this.uploadFile = this.uploadFile.bind(this);
this.processFile = this.processFile.bind(this);
this.errorHandler = this.errorHandler.bind(this);
this.progressHandler = this.progressHandler.bind(this);
And you'd have onDrop={this.uploadFile} in your DropZone element. If you were doing this without React, this is the equivalent of adding the onclick event handler you want to run when you click the "Upload File" button.
<button onclick="uploadFile(event);" value="Upload File" />
Then the function (applicable lines... I'll leave out my resetting my upload progress indicator, etc.):
uploadFile(event){
// This is for React, only
this.setState({
files: event,
});
console.log('File count: ' + this.state.files.length);
// You might check that the "event" has a file & assign it like this
// in vanilla Javascript:
// var files = event.target.files;
// if (!files && files.length > 0)
// files = (event.dataTransfer ? event.dataTransfer.files :
// event.originalEvent.dataTransfer.files);
// You cannot use "files" as a variable in React, however:
const in_files = this.state.files;
// iterate, if files length > 0
if (in_files.length > 0) {
for (let i = 0; i < in_files.length; i++) {
// use this, instead, for vanilla JS:
// for (var i = 0; i < files.length; i++) {
const a = i + 1;
console.log('in loop, pass: ' + a);
const f = in_files[i]; // or just files[i] in vanilla JS
const reader = new FileReader();
reader.onerror = this.errorHandler;
reader.onprogress = this.progressHandler;
reader.onload = this.processFile(f);
reader.readAsDataURL(f);
}
}
}
There was this question on that syntax, for vanilla JS, on how to get that file object:
JavaScript/HTML5/jQuery Drag-And-Drop Upload - "Uncaught TypeError: Cannot read property 'files' of undefined"
Note that React's DropZone will already put the File object into this.state.files for you, as long as you add files: [], to your this.state = { .... } in your constructor. I added syntax from an answer on that post on how to get your File object. It should work, or there are other posts there that can help. But all that Q/A told me was how to get the File object, not the blob data, itself. And even if I did fileData = new Blob([files[0]]); like in sebu's answer, which didn't include var with it for some reason, it didn't tell me how to read that blob's contents, and how to do it without a Promise object. So that's where the FileReader came in, though I actually tried and found I couldn't use their readAsArrayBuffer to any avail.
You will have to have the other functions that go along with this construct - one to handle onerror, one for onprogress (both shown farther below), and then the main one, onload, that actually does the work once a method on reader is invoked in that last line. Basically you are passing your event.dataTransfer.files[0] straight into that onload function, from what I can tell.
So the onload method calls my processFile() function (applicable lines, only):
processFile(theFile) {
return function(e) {
const bytes = e.target.result.split('base64,')[1];
}
}
And bytes should have the base64 bytes.
Additional functions:
errorHandler(e){
switch (e.target.error.code) {
case e.target.error.NOT_FOUND_ERR:
alert('File not found.');
break;
case e.target.error.NOT_READABLE_ERR:
alert('File is not readable.');
break;
case e.target.error.ABORT_ERR:
break; // no operation
default:
alert('An error occurred reading this file.');
break;
}
}
progressHandler(e) {
if (e.lengthComputable){
const loaded = Math.round((e.loaded / e.total) * 100);
let zeros = '';
// Percent loaded in string
if (loaded >= 0 && loaded < 10) {
zeros = '00';
}
else if (loaded < 100) {
zeros = '0';
}
// Display progress in 3-digits and increase bar length
document.getElementById("progress").textContent = zeros + loaded.toString();
document.getElementById("progressBar").style.width = loaded + '%';
}
}
And applicable progress indicator markup:
<table id="tblProgress">
<tbody>
<tr>
<td><b><span id="progress">000</span>%</b> <span className="progressBar"><span id="progressBar" /></span></td>
</tr>
</tbody>
</table>
And CSS:
.progressBar {
background-color: rgba(255, 255, 255, .1);
width: 100%;
height: 26px;
}
#progressBar {
background-color: rgba(87, 184, 208, .5);
content: '';
width: 0;
height: 26px;
}
EPILOGUE:
Inside processFile(), for some reason, I couldn't add bytes to a variable I carved out in this.state. So, instead, I set it directly to the variable, attachments, that was in my JSON object, RequestForm - the same object as my this.state was using. attachments is an array so I could push multiple files. It went like this:
const fileArray = [];
// Collect any existing attachments
if (RequestForm.state.attachments.length > 0) {
for (let i=0; i < RequestForm.state.attachments.length; i++) {
fileArray.push(RequestForm.state.attachments[i]);
}
}
// Add the new one to this.state
fileArray.push(bytes);
// Update the state
RequestForm.setState({
attachments: fileArray,
});
Then, because this.state already contained RequestForm:
this.stores = [
RequestForm,
]
I could reference it as this.state.attachments from there on out. React feature that isn't applicable in vanilla JS. You could build a similar construct in plain JavaScript with a global variable, and push, accordingly, however, much easier:
var fileArray = new Array(); // place at the top, before any functions
// Within your processFile():
var newFileArray = [];
if (fileArray.length > 0) {
for (var i=0; i < fileArray.length; i++) {
newFileArray.push(fileArray[i]);
}
}
// Add the new one
newFileArray.push(bytes);
// Now update the global variable
fileArray = newFileArray;
Then you always just reference fileArray, enumerate it for any file byte strings, e.g. var myBytes = fileArray[0]; for the first file.
This is simple way to convert files to Base64 and avoid "maximum call stack size exceeded at FileReader.reader.onload" with the file has big size.
document.querySelector('#fileInput').addEventListener('change', function () {
var reader = new FileReader();
var selectedFile = this.files[0];
reader.onload = function () {
var comma = this.result.indexOf(',');
var base64 = this.result.substr(comma + 1);
console.log(base64);
}
reader.readAsDataURL(selectedFile);
}, false);
<input id="fileInput" type="file" />
document.querySelector('input').addEventListener('change', function(){
var reader = new FileReader();
reader.onload = function(){
var arrayBuffer = this.result,
array = new Uint8Array(arrayBuffer),
binaryString = String.fromCharCode.apply(null, array);
console.log(binaryString);
console.log(arrayBuffer);
document.querySelector('#result').innerHTML = arrayBuffer + ' '+arrayBuffer.byteLength;
}
reader.readAsArrayBuffer(this.files[0]);
}, false);
<input type="file"/>
<div id="result"></div>
Here is one answer to get the actual final byte array , just using FileReader and ArrayBuffer :
const test_function = async () => {
... ... ...
const get_file_array = (file) => {
return new Promise((acc, err) => {
const reader = new FileReader();
reader.onload = (event) => { acc(event.target.result) };
reader.onerror = (err) => { err(err) };
reader.readAsArrayBuffer(file);
});
}
const temp = await get_file_array(files[0])
console.log('here we finally ve the file as a ArrayBuffer : ',temp);
const fileb = new Uint8Array(fileb)
... ... ...
}
where file is directly the File object u want to read , this has to be done in a async function...
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?