var arrayTest;
function handleFiles(files) {
arrayTest = new Array();
for(var i = 0; files[i]; i++) {
var reader = new FileReader();
reader.onload = function(e) {
var binary = e.target.result;
var parentSelector = '#output_' + i;
$(parentSelector).html(binary);
arraytest.push({ 'bin' : binary, 'parentSelector' : parentSelector });
};
reader.readAsBinaryString(files[i]);
}
}
function buttonClick() {
var files = [file1, file2];
handleFiles(files);
console.log(arrayTest); // I got the objects inserted at async function here, length != 0
console.log(arrayTest.length); // 0
console.log(arrayTest[0]); //undefined
// accesing any member of arrayTest returns undefined
}
FireFox Console output.
The code above shows a Js that converts files into binary string that is ran after a button event and I am having issues with being unable to access the global variable arrayTest that is updated with newly pushed value from the filereader onload event.Is there anyway to fix this problem?
Ok. I realized when I woke up that the point where console.log is executed, the async task might still be running which means that the arrayTest is incomplete, so what I this is what I did to fix the issue.
var arrayTest;
var asyncCount;
function handleFiles(files) {
arrayTest = new Array();
asyncCount = 0;
for(var i = 0; files[i]; i++) {
var reader = new FileReader();
asyncCount += 1;
reader.onload = function(e) {
var binary = e.target.result;
var parentSelector = '#output_' + i;
$(parentSelector).html(binary);
arrayTest.push({ 'bin' : binary, 'parentSelector' : parentSelector });
asyncCount -= 1;
if(asyncCount === 0) {
console.log(arrayTest);
console.log(arrayTest.length);
console.log(arrayTest[0]);
// at this point I am able to access all the array members.
}
};
reader.readAsBinaryString(files[i]);
}
}
function buttonClick() {
var files = [file1, file2];
handleFiles(files);
}
Related
I have a question and also in need for help with my code.
I'm trying to create a script that can update metadata on picture. user can upload multiple images and the script automatically update the metadata like latitude and longitude from a list.
As i am a newbie in java script i can not understand why when i pass all my coordinates inside reader.onload it only take the last one and assign it to all my pictures.
My idea is: I loop through the images and assign to each image one coordinate chronologically, then i convert the coordinate to exifByte. Till here is all working fine but when i try to attach the exifByte to each image inside reader.onload it takes the last coordinate and assign it to all my images.
I hope the code below is clear enough for you guys
Any explanation or help would be much appreciated
Thanks
<script>
function Initialize() {
var fileCatcher = document.getElementById('file-catcher');
var fileInput = document.getElementById('file-input');
var fileListDisplay = document.getElementById('file-list-display');
var fileList = [];
var renderFileList, dispimg;
var x = [];
var jpeg;
fileInput.addEventListener('change', function (evnt) {
var files = evnt.target.files;
fileList = [];
for (var i = 0; i < fileInput.files.length; i++) {
fileList.push(fileInput.files[i]);
var file = fileInput.files[i];
x = (i);
var gpsIfd = {};
var rutedata = '<?php echo "$str" ?>';
points = rutedata.split(";").reverse();
for (var j=0; j<(points.length-x); j++) {
var mData = points[j].split(',');}
console.log("current point:");
console.log(x)
var lat = (mData[0]);
var lng = (mData[1]);
gpsIfd[piexif.GPSIFD.GPSLatitudeRef] = lat < 0 ? 'S' : 'N';
gpsIfd[piexif.GPSIFD.GPSLatitude] = piexif.GPSHelper.degToDmsRational(lat);
gpsIfd[piexif.GPSIFD.GPSLongitudeRef] = lng < 0 ? 'W' : 'E';
gpsIfd[piexif.GPSIFD.GPSLongitude] = piexif.GPSHelper.degToDmsRational(lng);
var exifObj = { "GPS":gpsIfd};
var exifBytes = piexif.dump(exifObj);
var c = document.createDocumentFragment();
var reader = new FileReader();
reader.onload = (e) => { // this is the problem loop only the last point'
var jpeg = piexif.insert(exifBytes, e.target.result);
console.log(lat); // here is logging the last point only
var image = new Image();
image.src = jpeg;
image.width = 500;
var el = $("<div></div>").append(image);
$("#resized").prepend(el);
};
reader.readAsDataURL(fileInput.files[i]);
}
renderFileList();
});
}
</script>
Since reader is async in nature, you get the last lat-lang. You can use closure to keep data. Else create a function to pass data.
More: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
(function (byte, lats) {
var reader = new FileReader();
reader.onload = (e) => {
var jpeg = piexif.insert(byte, e.target.result);
console.log(lats); // here is logging the last point only
var image = new Image();
image.src = jpeg;
image.width = 500;
var el = $("<div></div>").append(image);
$("#resized").prepend(el);
};
})(exifBytes, lat);
I'm trying to script a converter (image) that pickup the checksum inside a image (form my Server) and add it to the selected image file.
But there I got 2 problems:
First problem:
It just accept jpg files, I tried everythink, but it dont work (png, gif etc.)
The second problem is, that the valid image, need to be from my server (convert.jpg)
But if I do that, I get this error:
TypeError: Argument 1 of FileReader.readAsDataURL is not an
object.
The line:
readerValid.readAsDataURL(valid);
What did I do wrong? (And sry for my bad english)
My Code:
function hexToBytes(hex) {
for (var bytes = [], c = 0; c < hex.length; c += 2)
bytes.push(parseInt(hex.substr(c, 2), 16));
return bytes;
}
function bytesToHex(bytes) {
for (var hex = [], i = 0; i < bytes.length; i++) {
hex.push((bytes.charCodeAt(i) >>> 4).toString(16));
hex.push((bytes.charCodeAt(i) & 0xF).toString(16));
}
return hex.join("");
}
function UpdateChecksum(pic, data) {
var string = ""
for (var i = 0; i < data.length; ++i) {
string += String.fromCharCode(data[i])
}
pic = atob(pic.replace(/^data:image\/jpeg;base64,/, ""))
for (var i = 0; i + 1 < pic.length; ++i) {
if (pic.charCodeAt(i) == 0xFF && pic.charCodeAt(i + 1) == 0xDB) {
pic = pic.slice(i, pic.length)
break
}
}
var regexp = new RegExp("(<checksum2>[0-9a-f]{32}</checksum2>)")
var match = regexp.exec(string)
string = string.replace(match[1], "<checksum2>" + bytesToHex(rc4("bns_gamepic", hexToBytes(md5(pic)))) + "</checksum2>")
for (var i = 0; i < data.length; ++i) {
data[i] = string.charCodeAt(i)
}
return data
}
function process() {
if(document.getElementById("custom").value == ""){
document.getElementById('error').innerHTML="Chose Image!";
}
else{
var valid = "convert.jpg";
var custom = document.getElementById('custom').files[0]
var readerValid = new FileReader()
readerValid.onloadend = function(e) {
var exif = piexif.load(e.target.result)
var readerCustom = new FileReader()
readerCustom.onloadend = function(e) {
var image = new Image()
image.onload = function() {
var result
try {
result = piexif.remove(e.target.result)
} catch (err) {
result = e.target.result
}
exif["Exif"][700] = UpdateChecksum(result, exif["Exif"][700])
result = piexif.insert(piexif.dump(exif), result)
download(result, "Converted_Image.jpg", "image/jpeg");
}
image.src = e.target.result
}
readerCustom.readAsDataURL(custom);
};
readerValid.readAsDataURL(valid);
}
}
I hope you can help me
Someone said:
The code var valid = "convert.jpg"; defines valid as a string, but the function readAsDataURL expect an object of type blob as a parameter, not a string.
But:
var valid = new Blob(["convert.jpg"], { type: 'image/jpeg'});
dont work :/
In this W3schools example, console.log on the input element reveals a FileInput object:
FileList {0: File, 1: File, length: 2}
How can I work with this? The example demonstrates accessing the file, but every time a user selects new files, the old files disappear. How can I create a new empty FileList and copy it over, so that a user can add more files to the FileList?
I tried this, but it results in two FileList objects, rather than one FileList with all the files:
var fileStore = x.files;
function myFunction(){
var txt = "";
if ('files' in x) {
if (x.files.length == 0) {
txt = "Select one or more files.";
} else {
fileStore += x.files;
console.log(x.files);
console.log(fileStore);
Untested, but this should work
var fileStore = [];
function myFunction(){
var txt = "";
if ('files' in x) {
if (x.files.length == 0) {
txt = "Select one or more files.";
} else {
fileStore.push.apply(fileStore,x.files);
console.log(x.files);
console.log(fileStore);
More on Function::apply
More on Array::push
It is not possible to add File objects to FileList. You can use FormData to append Files to a single object.
var data = new FormData();
document.querySelector("input[type=file]")
.addEventListener("change", function(event) {
for (var i = 0, files = event.target.files; i < files.length; i++) {
data.append("file-" + [...data.keys()].length, files[i], files[i].name)
}
})
An array is fine for holding onto the File instances, but FormData is better if you want to upload them somewhere. If you want to log out or view the FormData, turning it into a Map is an option. Keep in mind that FormData is iterable.
var formData = new FormData();
var index = 0;
function onDrop(event)
{
var dt = event.dataTransfer;
var files = dt.files;
var count = files.length;
output("File Count: " + count + "\n");
for (var i = 0; i < files.length; i++) {
formData.append(files[i].name, files[i]);
}
}
function output(text)
{
document.getElementById("output").textContent += text;
console.dir(new Map(formData));
}
See this JSBin.
it is possible to add files using the datatransfer class
export const makeFileList = files => {
const reducer = (dataTransfer, file) => {
dataTransfer.items.add(file);
return dataTransfer;
}
return files.reduce(reducer, new DataTransfer()).files;
}
I would like to ask you, if it is possible to iterate through list of file inputs and save the results in one array. Here is bit of my code.
Also I use AngularJS if it helps...
HTML
<input type="file" id="file1">
<input type="file" id="file2">
<button id="saveBtn">Save</button>
JAVASCRIPT
results = []; // array for storing results
/*file inputs*/
inputs = [document.getElementById('file1'), document.getElementById('file2')];
function readFile() {
for (var i = 0; i < inputs.length(); i++) {
if (inputs[i].files && inputs[i].files[0]) {
var FR = new FileReader();
FR.onload = function (event) {
results[i] = event.target.result; //array where I would like to store results
};
FR.readAsDataURL(inputs[0].files[0]);
}
}
}
//here I would like to write down all results
var btn = document.getElementById('saveBtn');
btn.onclick = function() {
readFile();
for(var i=0; i < inputs.length(); i++) {
console.log(results[i]);
}
}
When I run this code, as result I get 'undefined'.
Do you guys have any ideas how to achieve this? Thank you
Besides the point that Niek Vandael makes in his answer; the fact that you are trying to show information before it has completed loading, there are a few other issues with your code.
input.lengths()
should be
input.lengths
lengths it's not a method. That causes an error when I test the code (in Chrome).
You also read the same data over and over;
FR.readAsDataURL(inputs[0].files[0]);
should probably be
FR.readAsDataURL(inputs[i].files[0]);
So, here's another take on it. I added a couple of vars to keep track of items loaded and how many there are to load, then call the function to display the data once it has been loaded.
results = []; // array for storing results
/*file inputs*/
inputs = [document.getElementById('file1'), document.getElementById('file2')];
//here I would like to write down all results
var btn = document.getElementById('saveBtn');
var inputCount;
var filesLoaded = 0;
function readFile() {
inputCount = Number(inputs.length);
for (var i = 0; i < inputCount ; i++) {
if (inputs[i].files && inputs[i].files[0]) {
var FR = new FileReader();
FR.onload = function (event) {
results.push(event.target.result); //array where I would like to store results
filesLoaded++;
if (filesLoaded == inputCount) {
showResult();
}
};
FR.readAsDataURL(inputs[i].files[0]);
}
}
}
btn.onclick = function () {
readFile();
}
function showResult() {
for (var i = 0; i < inputs.length ; i++) {
console.log(results[i]);
}
}
There are a few more things to think about, such as validating that files have been selected etc, but I guess it's kind of out of the scope here.
onLoad gets executed when the FileReader has loaded, so after you write your log statements.
Try this:
https://jsfiddle.net/s17Lmc21/1/
results = []; // array for storing results
/*file inputs*/
inputs = [document.getElementById('file1'), document.getElementById('file2')];
function readFile() {
for (var i = 0; i < inputs.length; i++) {
if (inputs[i].files && inputs[i].files[0]) {
var FR = new FileReader();
FR.onload = function (event) {
results[i] = event.target.result; //array where I would like to store results
console.log(results[i]);
};
FR.readAsDataURL(inputs[0].files[0]);
}
}
}
//here I would like to write down all results
var btn = document.getElementById('saveBtn');
btn.onclick = function() {
readFile();
}
I new to JavaScript an jQuery and Google doesn't lead to an answer. I am writing a online Ebook reader. This is the code for a library where the user can input multiple epub files and relevant information(like Author) should be displayed in a table. To do this I need to extract the ePub file. The jsZip library works perfect. The contents must be displayed in a table which is dynamically created(since I don't know the amount of files).
The problem is that the for loops is to fast and creates all the cells with only the name and filesize within them and after the for loop completes the onload of the FileReader executes and adds all the contents into the very last cell. In this code the alert("A") happens as many times as files that were inputted before alert("B") happens. Is there some way that I can make the loops wait until the onload of the FileReader is done?
function handleFileSelect(evt) {
var files = evt.target.files;
var rows = Math.ceil(files.length/3);
var a = 0;
var root = document.getElementById('mainTable');
var tab=document.createElement('table');
tab.style.textAlign = "center";
var row, cell;
var tbo=document.createElement('tbody');
for(var i = 0; i != rows; i++)
{
row=document.createElement('tr');
for(var j = 0; (j < 3);j++)
{
cell = document.createElement('td');
cell.height = "300px";
cell.width = "300px"
if(a <indexes.length)
{
var f = files[a];
var str = f.name;
str = str.substring(0, str.length - 5);
str = "File Name: " + str;
cell.appendChild(document.createTextNode(str));
cell.appendChild(document.createElement('br'));
str = "File Size: " + Math.round(f.size/1024) + " KB";
cell.appendChild(document.createTextNode(str));
cell.appendChild(document.createElement('br'));
var reader = new FileReader();
reader.onload = (function(theFile)
{
return function(e)
{
alert("B");
var zip = new JSZip(e.target.result);
$.each(zip.files, function (index, zipEntry)
{
cell.appendChild(document.createTextNode(zipEntry.name));
cell.appendChild(document.createElement('br'));
//row.appendChild(cell);
});
}
})(f);
reader.readAsArrayBuffer(f);
alert("A");
a++;
}
row.appendChild(cell);
}
tbo.appendChild(row);
}
tab.appendChild(tbo);
root.appendChild(tab);
}
document.getElementById('files').addEventListener('change', handleFileSelect, false);
Your problem is that the variable cell used within your onload handler will refer to the last value assigned to cell in the outer loop. You can get around this by putting the file processing in a separate function which will create it's own scope.
function read_zip_file(f, cell) {
var reader = new FileReader();
reader.onload = (function(theFile)
{
return function(e)
{
alert("B");
var zip = new JSZip(e.target.result);
$.each(zip.files, function (index, zipEntry)
{
cell.appendChild(document.createTextNode(zipEntry.name));
cell.appendChild(document.createElement('br'));
//row.appendChild(cell);
});
}
})(f);
reader.readAsArrayBuffer(f);
}
Then within the outer loop you can call:
read_zip_file(f, cell);