The frontend of the application having a file download option (which can be in the following format: xlsx, csv, dat).
For that, I use fileSaver.js
Everything works fine for the format .dat/.csv but for the .xlsx it does not work the files are corrupted.
I tested the conversion with the following formats :
utf8
base64
binary
Here's how I do :
// /* BACK */ //
// data is
fs.readFile(filePath, (err, data) {...})
// the api give this answer the important part is "filename" & "data"
{"status":"ok","context":"writing the intermediate file","target":"/temp/","fileName":"name.xlsx","data":{"type":"Buffer","data":[72,82,65,67,67,69,83,83,32,10]}}
// /* FRONT */ //
let json = JSON.stringify(data)
let buffer = Buffer.from(JSON.parse(json).data)
let read = buffer.toString('utf8')
let blob = new Blob([read])
FileSaver.saveAs(blob, fileName)
Ok for anybody who pass in this topic, my solution :
(keep in mind the real better solution for dl a file : send file in api response with header 'Content-disposition' or use express for that like this)
The back (Node) work like this :
fs.readFile(filePath, (err, data) => {
if (err) {
console.log(`-------- oups error - read --------`)
console.log(err)
res.send({ status: `erreur`, context: `read the source file`, id: err.errno, code: err.code, message: err.message.replace(/\\/g, '/') })
} else {
res.send({ status: `ok`, context: `send data file`, target: target, fileName: fileName, data: data })
}
})
Here :
target is the path for the front with the name of the file and his
extension (/path/name.ext)
fileName is juste the name and the extension (name.ext)
data is the data send by the readFile ({"type":"Buffer","data":[72,82,65,67,67,69,83,83,32,10]})
The front (React) work like this :
fetch(targetUrl)
.then(res => res.json())
.then(res => {
if (res.status !== `ok`) {
this.setState({
errorDlFile: true,
errorDlFile_context: res.context,
errorDlFile_id: res.id,
errorDlFile_code: res.code,
errorDlFile_message: res.message
})
} else {
const target = res.target
const fileName = res.fileName
const data = res.data
const splitName = res.fileName.split('.')
const format = splitName[splitName.length-1]
// file saver blob solution
let json = JSON.stringify(data)
let buffer = Buffer.from(JSON.parse(json).data)
let readUTF8 = buffer.toString('utf8')
let blob = ''
if (format === 'xlsx') {
blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
} else if (format === 'csv') {
blob = new Blob([readUTF8], { type: 'application/vnd.ms-excel' })
} else {
blob = new Blob([readUTF8])
}
FileSaver.saveAs(blob, fileName)
}
})
#Hadock
if you want to download file with .csv extension then you need to pass the type of file like
let blob = new Blob([csv], { type: 'application/vnd.ms-excel' });
instead of
let blob = new Blob([read])
and don't forgot to send filename with extension (test.csv).
For excel file I used different plugin exceljs demo.
you can't save json data directly to .xlsx file, you can convert json data to excel format using library like 'sheetjs' (https://sheetjs.com/)
var ws_name = filename;//"SheetJS";
var wb = new Workbook(), ws = sheet_from_array_of_arrays(data);
/* add worksheet to workbook */
wb.SheetNames.push(ws_name);
wb.Sheets[ws_name] = ws;
var wbout = XLSX.write(wb, { bookType: 'xlsx', bookSST: true, type: 'binary' });
saveAs(new Blob([s2ab(wbout)], { type: "application/octet-stream" }), filename + ".xlsx")
Related
This is an issue that occurred while trying to implement a profile image change using React.
Function that receives image values cropped from canvas, converts to blob, and requests it to be saved to the server
//Function that receives image values cropped from canvas, converts to blob, and requests it to be saved to the server
const onClickUploadImage = (canvas, crop) => {
if (!crop || !canvas) {
return;
}
const canvasUrl = canvas.toDataURL("image/png");
const base64Incoding = atob(canvasUrl.split(",")[1]);
const array = [];
for (let i = 0; i < base64Incoding.length; i++) {
array.push(base64Incoding.charCodeAt(i));
}
const file = new Blob([new Uint8Array(array)], { type: "image/png" });
const fd = new FormData();
fd.append("file", file);
//Verify that the blob is created as an url image
console.log("convert file >> ", window.URL.createObjectURL(file));
//conver file >> blob:http://localhost:3000/afdc0910-76bb-4e53-801c-61dc890c651f
const config = {
header: {
processData: false,
"content-type": false,
},
};
axios
.post("/api/users/update/image", fd, config)
.then(({ data }) => {
console.log("data >> ", data);
//data >> { success:true, message:"File saved successfully" }
alert("Profile upload successful.");
})
.catch((err) => {
alert("Profile upload failed.");
});
};
// On the server side, we use the filter module to store very well.
// Type is saved as file.
// The problem is that I want to read this file and display it as an image.
// To do that, I checked to see if the stored blob reads well.
// I tried to read the saved blob file using input and convert it as below code, but failed.
// input ex) <input type="file" onChange={onClickBlobToURL} />
function onClickBlobToURL(e) {
const file = e.target.files[0];
console.log("file > ", file);
//File Attach Output Result Image
const reader = new FileReader();
if (file) {
console.log("blobToURL > ", reader.readAsDataURL(file));
//blobToURL > undifined
}
}
Did you save it the wrong way?
Is it wrong to read blob?
console.log("file > ", file)
I have an array of the object, each object contains some properties of type string and a file.
This is how my array looks like:
const args = [
{
name: 'presentation',
price: 9.99,
tags: 'invest, finance, business',
file: File,
thumbnail: File
},
{
name: 'presentation2',
price: 20.99,
tags: 'invest, finance, business',
file: File,
thumbnail: File
}
]
const headers = {
headers: {
Authorization: `Bearer ${token}`
}
};
My goal is to send this whole array of the object to the Express Server. there I will save information to the Mongo and upload the file to S3.
But Currently, I am unable to get the file stream on the server when I send this in a POST request,
even the whole req.body is an empty object when I console.log it
axios.post(`${slidesRoute}/createSlides`, args, headers);
Alternatively, I tried to put everything into FormData.
for example:
let formData = new FormData();
selectedFiles.forEach((selectedFile, i) => {
formData.append(`name_${i}`, selectedFile.name);
formData.append(`price_${i}`, selectedFile.price);
formData.append(`tags_${i}`, selectedFile.tags);
formData.append(`file_${i}`, selectedFile.file);
formData.append(`thumbnail_${i}`, selectedFile.thumbnail);
});
axios.post(`${slidesRoute}/createSlides`, formData, headers);
Then In the backend. I am unable to catch these files using multer. Because the filenames in form data are being generated dynamically like
file_1, file_2 file_3,...
But multer expects the exact filename to be passed
for example multer().array('file')
so in my second approach I am unable to catch files using Multer
You can't include file inside JSON. File is not JSON-serializable. Ideally you should have a separate endpoint to accept your files, send them with content-type: multipart/form-data, receive unique id for that file stored on the server, then send your JSON with ids instead of files.
As a chaper alternative you can Base64 encode your files into a string, insert this string in your request, and then decode it back on the receiving side
FormData key values are array by default
in your code Just change
formData.append(`file_${i}`, selectedFile.file);
to
formData.append(`file`, selectedFile.file);
You should be able to receive the file array in server side using multer().array('file')
Sample below demonstrating FormData key values array behavior
const formData = new FormData();
formData.append('key1', 'value1');
formData.append('key1', 'value2');
formData.append('key2', 'value1');
formData.forEach(function(value, key){
console.log(key, value);
});
I use FormData to send a number of files - and then on the node.js server I use Formidable.
I've pasted some partial code below that may help - sorry it's not a smaller sample.
Hope it helps
On the browser side I have:
let form_data = new FormData()
form_data.append('upload_data', file)
$.ajax({
url: '/file/upload' + remote_path + '/' + filename,
type: 'POST',
data: form_data,
processData: false,
contentType: false,
success: (res) => {
...
On the node.js side (apologies for the length..)I have:
const fs = require('fs')
const formidable = require('formidable')
const path = require('path')
...
app.post('/file/upload/*', (req, res) => {
let filename = req.params[0]
let media = path.basename(filename)
let folder = filename.substring(0, filename.length - media.length)
let form = new formidable.IncomingForm()
// form.encoding = 'utf-8'
form.multiples = true
form.uploadDir = path.join(media_folder, folder)
form.keepExtensions = true
form.parse(req, (err, fields, files) => {
if (err) {
fail(res, 'failed to upload')
} else {
success(res)
}
})
form.on('fileBegin', (name, file) => {
const [fileName, fileExt] = file.name.split('.')
file.path = path.join(form.uploadDir, `${fileName}_${new Date().getTime()}.${fileExt}`)
})
form.on('file', (field, file) => {
fs.rename(file.path, path.join(form.uploadDir, file.name), (err) => {
if (!res.headersSent) { // Fix since first response is received
fail(res, 'an error has occured with form upload' + err)
}
})
})
form.on('error', (err) => {
fail(res, 'an error has occured with form upload' + err)
})
form.on('aborted', (err) => {
fail(res, 'Upload cancelled by browser')
})
})
I'm using Multer to upload an image file into my node server, and it always comes back to giving me undefined for using ajax to send the image.
Ajax :
image = $("#input-file-img").val()
const data = new FormData();
data.append("image", image);
$.ajax({
url: '/uploadfile/' + userName,
method: 'POST',
async: false,
processData: false ,
contentType: false,
data: data
})
Upload.js
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads')
},
filename: function (req, file, cb) {
cb(null, file.originalname)
}
})
var upload = multer({ storage: storage })
router.post('/uploadfile/:userName', upload.single('image'), async (req, res, next) => {
let user = await User.findOne({ userName: req.params.userName })
let file = req.file
let fileName = file.filename
let path = variables.kepler + 'uploads/' + fileName
user.image = path
await user.save()
if (!path) {
const error = new Error('Please upload a file ...')
error.httpStatusCode = 400
return next(error)
}
if (path) {
return res.status(200).send({
status: '200',
message: 'Operation completed successfully ...',
data: user,
path
})
}
})
I checked the image value with console and it shows C:\fakepath\Capture d’écran de 2019-09-19 11-33-59.png'
Would appreciate any help.
I think your server side code is fine, if I modify the client side code as below, everything works nicely, we end up with images in the /uploads folder:
function base64toBlob(base64, mimeType) {
const bytes = atob(base64.split(',')[1]);
const arrayBuffer = new ArrayBuffer(bytes.length);
const uintArray = new Uint8Array(arrayBuffer);
for (let i = 0; i < bytes.length; i++) {
uintArray[i] = bytes.charCodeAt(i);
}
return new Blob([ arrayBuffer ], { type: mimeType });
}
function submitForm() {
const imgRegEx = /^data:(image\/(gif|png|jpg|jpeg))/;
const imageData = $('#input-file-img').attr('src');
const mimeType = imgRegEx.exec(imageData)[1];
const blob = base64toBlob(imageData, mimeType);
const fileExt = mimeType.replace("image/", "");
const fileName = "test-image." + fileExt; // Change as appropriate..
const data = new FormData();
data.append("image", blob, fileName);
$.ajax({
url: '/uploadfile/' + userName,
method: 'POST',
async: false,
processData: false ,
contentType: false,
data: data
})
}
Solved!!!
getting value by
image = $("#input-file-img").val()
that means I was sending a type String as a file
so I had to change it to
image = $('#input-file-img')[0].files[0]
and everything works really well
I am currently working in angular 6. i have to download pdf file from bytearray.
when i tried to download an pdf file. it downloads but when i open it. it shows error. please help me with it.
In the download.component.ts i am getting response as two values. first pdfname, second pdfbytearray. i am getting those values from component response.
sample response:
{
pdfName: "test.pdf",
pdfByteArray: "JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PC9Qcm9kdWNlcij"
}
download.service.ts:
generatePdf(id) {
const httpOptions = {
'responseType': 'arraybuffer' as 'json'
};
return this.http.post<any>(URL.BASE_URL + 'application/generate?id='+ id,
{ httpOptions },
);
}
download.component.ts:
import { saveAs } from 'file-saver/FileSaver';
generatePdf() {
this.downloadApiService.generatePdf('4')
.subscribe( pdf => {
console.log('pdf response: ', pdf);
var mediaType = 'application/pdf';
var blob = new Blob([pdf.pdfByteArray], {type: mediaType});
let fileName = pdf.pdfName;
saveAs(blob, fileName);
}, err => {
console.log('Pdf generated err: ', JSON.stringify(err));
});
}
I'm building a library to "nodeize" the HTML5 File Api (currently in alpha)[0], to make it work with binary contents and don't have problems with charsets, I'm using Buffer[1] utility.
But the HTML5 File API uses Blob native object. Actualy I'm using the type 'application/octet-stream', and 'binary' from Buffer encoding. But, I want to use base64 in order to prevent any problem:
CoFS.prototype.writeFile = function (fileName, data, encoding, callback) {
var self = this;
if (Buffer.isBuffer(data)) {
callback = encoding;
encoding = undefined;
} else {
data = new Buffer(data, encoding);
}
this._ifready(function () {
self.getFileEntry(fileName, {create: true, exclusive: true}, function (err, fileEntry) {
if (err) return callback(new Error("Error getting file access " + err.message));
fileEntry.createWriter(function(fileWriter) {
fileWriter.onwriteend = function () {
callback(null);
};
fileWriter.onerror = function(e) {
callback(new Error(e.toString()));
};
var blob = new Blob([data.toString('binary')], {type: 'application/octet-stream'});
fileWriter.write(blob);
}, function () {
callback(new Error('Error writing ' + fileName));
});
});
});
};
Exacts on:
var blob = new Blob([data.toString('binary')], {type: 'application/octet-stream'});
I red the MDN page[2] but I didn't see anything about the encoding.
Is there any way to accomplish something like that?:
var blob = new Blob([data.toString('base64')], {type: 'application/octet-stream', encoding: 'base64'});
Thankyou.
0: https://github.com/exos/cofs
1: https://github.com/anodynos/node2web_buffer
2: https://developer.mozilla.org/en-US/docs/Web/API/Blob
I realized the Buffer object is extended from Int8Array, and both are compatible.
My code now is:
var blob = new Blob(
[data], // Data is a buffer!
{
type: 'application/octet-stream'
}
);
fileWriter.write(blob);
And for read, I use readAsArrayBuffer method, you can do:
var d = new Int8Array(arr); // When arr is arrayBuffer
var buf = new Buffer(d); // And works!
This solve my problem. For convert base64 encoded content and use in a blob, with Buffer, can be:
var blob = new Blob(
[new Buffer(cleanData, 'base64')],
{
type: 'application/octet-stream'
}
);