how to turn multiple files into base64 string? - javascript

i have a component like this
<input type="file" multiple #change="toBase64Handler($event)">
<script>
data() {
return {
files: [],
}
},
methods: {
tobase64Handler(event) {
// the code
}
}
</script>
and i would like to turn all of the selected files into base64 string something like this:
files: [
{
selectedFile: 'ajsdgfauywdljasvdajsgvdasdo1u2ydfouayvsdlj2vo8ayasd...'
},
{
selectedFile: 'askdhgoiydvywdljasvdajsgvdasdo1u2ydfoakjgsfdjagswsd...'
},
{
selectedFile: '12edashjvlsljasvdajsgvdasdo1u2ydfouayvsdlj2vo8ayfsd...'
},
]
how do i achieve that?

You can loop though the files call a helper method toBase64, push all Promises to an array and resolve all of them:
toBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
});
};
async tobase64Handler(files) {
const filePathsPromises = [];
files.forEach(file => {
filePathsPromises.push(this.toBase64(file));
});
const filePaths = await Promise.all(filePathsPromises);
const mappedFiles = filePaths.map((base64File) => ({ selectedFile: base64File }));
return mappedFiles;
}

this should do the trick: JSON.stringify(files) you can read more about it here. If you later want to turn it back into the original array, object, or value then you'd do JSON.parse(files). you can read more about it here
UPDATE: turns out I was wrong and JSON.stringify/parse don't work with files(thanks for the info #patrick evans).
found this answer which seems better (the one by #ahmed hamdy)

Related

Blob to Base64 in javascript not returning anything from FileReader

I am using FileReader in typescript to convert a blob to a base64 image that will then be displayed in the template of my application.
adaptResultToBase64(res: Blob): string {
let imageToDisplay : string | ArrayBuffer | null = '';
const reader = new FileReader();
reader.onloadend = function () {
imageToDisplay = reader.result;
return imageToDisplay;
};
reader.readAsDataURL(res);
return imageToDisplay;
}
Whilst the data logged inside the read.onloadend function displays the base64 string I cannot pass it out of the function.
I have tried adding a callback but where it is called elsewhere doesn't return anything but an empty string.
Please check this code
<input type="file" id="file">
<button id="click">click</button>
let data: string | ArrayBuffer;
document.getElementById('file').onchange = function (e: Event) {
let files: FileList | null = (<HTMLInputElement>e.target).files;
let reader: FileReader = new FileReader();
reader.onload = function (e: ProgressEvent<FileReader>) {
console.log(e.target.result);
data = e.target.result;
};
if (files.length > 0) {
reader.readAsDataURL(files?.[0]);
}
};
document.getElementById('click').onclick = function () {
console.log(data); // result if present otherwise null is returned
};
Using a separate method view. The return value is a Promise.
function adaptResultToBase64(res: Blob): Promise<string> {
let reader: FileReader = new FileReader();
return new Promise((resolve, reject) => {
reader.onloadend = () => {
resolve(reader.result as string);
}
reader.onerror = () => {
reject("Error reading file.");
}
reader.readAsDataURL(res);
})
}
To get the result
adaptResultToBase64(/* Blob value */)
.then(resp => console.log(resp))
.catch(error => console.log(error));
See here for specifics on Promise
MDN
learn.javascript.ru
The basic result I needed and did not realise that the reader.onload is actually a callback for read.readAsDataUrl and finishes everything inside it async.
adaptResultToBase64(res:Blob){
const reader = new FileReader();
reader.onload = function () {
// Was missing code here that needed to be called asynchronously.
adapterToNewObject(reader.result.toString())
};
reader.readAsDataURL(res);
}
}
I was performing this in Angular so for anyone else who runs into this problem here it is using Angular syntax:
In your class:
export class Component {
adaptedResult:Result
getBase64() {
this.http.get().subscribe((result: Blob) => {
const reader = new FileReader();
reader.onload = () => {
this.adaptedResult = this.adapter(reader.result) // Assign or use reader.result value, this is an example of using an adapter function.
};
reader.readAsDataURL(result);
});
}
adapter(base64:string){
return {
name:'image',
image:base64'
}
}
}

Spread operator not working with module function

I've this function in a module for to load a files as base64 and its attributes:
function getFiles(obj, type) {
let destinazione = type;
let files = obj.files;
let imagesDocs = [];
files.forEach(file => {
let reader = new FileReader();
reader.onload = function(ev) {
let result = this.result;
let f = {
name: file.name,
size: file.size,
type: file.type,
data: result
};
imagesDocs.push(f);
};
reader.readAsDataURL(file);
});
return imagesDocs;
}
export default getFiles;
In another module, I import fileArray module and use it as:
const resultArray = getFiles(e.target, type);
let newArray = [...resultArray];
console.log(newArray);
console.log show me an empty array, while resultArray contain more objects.
I tried too with:
let numbers = [1, 2, 3, 4];
let newnumbers = [...numbers];
and work fine.
Why?
Update:
Using the Anson Miu's code I resolved.
The "files" variable, being filelist type must be converted to array type. I did it with [...files].
Thanks to all.
The problem is that reader.onload is an asynchronous process.
What happens is that you loop through all the files, then you pass the empty result (imagesDocs) back, and then sometime later populate imagesDocs.
you need to handle the process asynchronously, using callback, async/await and/or promises.
reader.onload is an asynchronous callback, so it is possible that the imageDocs array is not populated when you return from your getFiles function.
I suggest to extract the logic for reading a file into a separate function that returns a Promise:
function readFile(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = function(ev) {
const result = this.result;
resolve({
name: file.name,
size: file.size,
type: file.type,
data: result,
});
};
reader.readAsDataURL(file);
});
}
Then update getFiles to be an asynchronous function that maps the files to Promises and returns a Promise that resolves to the imageDocs:
async function getFiles(obj, type) {
const destinazione = type;
const files = obj.files;
const imageDocs = await Promise.all(files.map(readFile));
return imageDocs;
}
I think you need to
export default getFiles;

Check when all images are done uploading?

I have an array of objects. In each object there are an array of files..
Im looping through all files and uploading them one by one, which works as expected.. However, i want to show a "success" modal when all files from each object are done uploading..
Im struggling a bit here... The code I have so far:
Im thinking im doing something wrong when I do the check on the:
if (allFiles.length === filesToQuestions.length) {
triggerUploadFiles() {
let allFiles = [];
let filesToQuestions = this.filesToQuestions;
filesToQuestions.forEach((item) => {
let files = item.images;
let payload = {
instanceId: item.instanceId,
answerId: item.answerId,
path: item.path,
fileType: item.fileType,
optionId: item.optionId
};
if (files.length > 0) {
files.map(async(file) => {
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = async(e) => {
// Make a fileInfo object for storing later
let fileInfo = {
questionId: this.questionId,
name: file.name,
type: file.type,
data: e.target.result,
file: file
};
fileInfo.type.includes('video') ? fileInfo.type = 'video' : fileInfo.type = 'image';
if (payload.instanceId && payload.path) {
const {response} = await uploadFileToCloudinary(fileInfo.data, payload);
try {
this.$store.dispatch('surveys/submitFilesToSurvey', {
instanceId: payload.instanceId,
answerId: payload.answerId,
fileName: response.public_id,
type: fileInfo.type,
optionId: payload.optionId
}).then((response) => {
console.log('file submitted', response);
allFiles.push(fileInfo);
});
} catch (e) {
console.log('could not upload file');
}
}
// If all files have been proceed
if (allFiles.length === filesToQuestions.length) {
const delayForCompletedStatus = 2000;
deletePendingSurveyByID(this.tempSurveyId);
setTimeout(() => {
this.isUploadingFiles = false;
this.$store.dispatch('modals/toggleModal', 'showModalSurveyCreated');
}, delayForCompletedStatus);
}
};
});
}
});
}
You can use Promise.all() to check if all asynchronous events are finished.
The Promise.all() method returns a single Promise that fulfills when all of the promises passed as an iterable have been fulfilled or when the iterable contains no promises. It rejects with the reason of the first promise that rejects.
Promise.all(files.map((file) => {
return new Promise((resolve, reject) => {
...
reader.onload = e => {
// upload file
...
resolve();
}
...
});
})
.then() {
// all asynchronous events are finished!
}
FYI, I added a simple example of using Promise.all.
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => resolve('first'), 1000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => resolve('second'), 2000);
});
const promiseList = [promise1, promise2];
Promise.all(promiseList).then(function(values) {
console.log(values);
});

Lag by taking an instance of an array using es6

I got an empty array while I'm logging for an instance of an array!
onSelectMultipleImage = (event): Promise<any> => {
return new Promise(resolve => {
const files = <File[]>event.target.files;
let file: File;
let counter = -1;
const response = [];
while (file = files[++counter]) {
const reader: FileReader = new FileReader();
reader.onload = ((f) => {
return () => {
response.push({
file: f,
base64: reader.result
})
}
})(file);
reader.readAsDataURL(file);
console.log(counter);
if(counter == files.length -1) {
console.log('inside the while');
resolve(response);
}
}
});
};
onImagesSelect = async (event: Event) => {
this.newImages = await this.helper.onSelectMultipleImage(event) || [];
console.log(this.newImages, "Original"); // [file: File, base64: "base 64 long string..."]
console.log([...this.newImages],"instance"); // [] empty array
setTimeout(() => {console.log([...this.newImages, 'instance'])}, 2000); // [file: File, base64: "base 64 long string..."]
}
So why I'm getting the presented result? It's something causing by the base64 presented inside the array? if yes what is the solution?
It doesn't wait reader.onload to be completed. So resolve(response) is called before response.push.
You can create a promise to read one file and return them with Promise.all like following code.
readFile = (file: File): Promise<any> => {
return new Promise(resolve => {
const reader: FileReader = new FileReader();
reader.onload = (f) => {
resolve({
file: f,
base64: reader.result
});
};
reader.readAsDataURL(file);
})
}
onSelectMultipleImage = (event): Promise<any> => {
const files = <File[]>event.target.files;
return Promise.all(files.map(file => readFile(file)));
};

Mapping objects, and using state, and passing value through a function in setState

I have a react application, where i want to set the state equal to an array of objects, that has been passed through a function.
I have two variables, and is the file extension, and one is the raw data base64 encoded.
this.setState({
result: fileItems.map(fileItem => ({
"file": this.findFileTypeHandler(fileItem.file.name),
"data": this.getBase64(fileItem.file)
}))
});
}}>
as of right now i just return the value within the two functions, but the issue occurs, when the functions are not asynchronous.
findFileTypeHandler = file =>{
return file.split('.').pop();
}
getBase64 = file => {
//find en måde at gøre den asynkron
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function () {
console.log(reader.result)
this.setState({base64: reader.result})
return reader.result;
};
reader.onerror = function (error) {
console.log('error is ', error)
};
}
the best case scenario, would be to setState, and then assign the value for each map iteration, but how can i pass the data through the functions, and set the state respectivly?
How about use Promise.all to wait all files to be read, then setState the results.
Promise.all(
fileItems.map(fileItem => new Promise((resolve, reject) => {
//find en måde at gøre den asynkron
var reader = new FileReader();
reader.readAsDataURL(fileItem.file);
reader.onload = () => {
resolve({
file: this.findFileTypeHandler(fileItem.file.name),
data: {
base64: reader.result
}
});
};
reader.onerror = (error) => reject(error);
}))
)
.then((results) => {
this.setState({
result: results
});
})
.catch((error) => console.log(error));

Categories