Hello everyone I am developing a website using React.js and Node.js where I generate a word document from the user's input and thats worked just fine with docxtemplater!
The problem is I want to let the user download the word document direclty as a PDF document but docxtemplater just permit us to save the file as a docx.
To convert the docx, i have the idea to save my blob document in mongodb with gridfs (already done and worked fine) and then get my blob and convert it to pdf (where i'm blocked)
Here is the part of my code for generate my docx and saving the generated word docx in mongodb (i have intentionnaly delete some things not important for this question)
import React, { useState, useEffect } from "react";
import Docxtemplater from "docxtemplater";
import PizZip from "pizzip";
import PizZipUtils from "pizzip/utils/index.js";
import { saveAs } from "file-saver";
import axios from "axios";
function loadFile(url, callback) {
PizZipUtils.getBinaryContent(url, callback);
}
export const DocxFile = ({ formData }) => {
const [file, setFile] = useState(null);
const [currentlyUploading, setCurrentlyUploading] = useState(false);
const [docxId, setDocxId] = useState(null);
const [progress, setProgress] = useState(null);
const [inputContainsFile, setInputContainsFile] = useState(false);
const [template, setTemplate] = useState();
const generateDocument = () => {
loadFile(template, function (error, content) {
if (error) {
throw error;
}
var zip = new PizZip(content);
var doc = new Docxtemplater(zip, {
paragraphLoop: true,
linebreaks: true,
});
doc.setData({
company: formData.general.companyClient,
version: formData.documentVersion,
quote: formData.general.quoteNumber,
HERE MY DATA //Working :)
});
try {
doc.render();
} catch (error) {
function replaceErrors(key, value) {
if (value instanceof Error) {
return Object.getOwnPropertyNames(value).reduce(function (
error,
key
) {
error[key] = value[key];
return error;
},
{});
}
return value;
}
if (error.properties && error.properties.errors instanceof Array) {
const errorMessages = error.properties.errors
.map(function (error) {
return error.properties.explanation;
})
.join("\n");
}
throw error;
}
var out = doc.getZip().generate({
type: "blob",
mimeType:
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
});
saveAs(
out,
`${name}.${documentVersion}.docx`
);
setFile(out);
setInputContainsFile(true);
});
};
const fileUploadHandler = () => {
const fd = new FormData();
fd.append("docx", file, file.name);
axios
.post(`api/docx/upload`, fd, {
onUploadProgress: (ProgressEvent) => {
setProgress((ProgressEvent.loaded / ProgressEvent.total) * 100);
console.log(
"upload progress",
Math.round((ProgressEvent.loaded / ProgressEvent.total) * 100)
);
},
})
.then(({ data }) => {
setDocxId(data);
setFile(null);
setInputContainsFile(false);
setCurrentlyUploading(false);
})
.catch((err) => {
console.log(err);
if (err.response.status === 400) {
const errMsg = err.response.data;
if (errMsg) {
console.log(errMsg);
}
} else {
console.log("other error", err);
setInputContainsFile(false);
setCurrentlyUploading(false);
}
});
};
const handleClick = () => {
if (inputContainsFile) {
setCurrentlyUploading(true);
fileUploadHandler();
}
};
return (
<>
<button
className="modify__button1 enregistrer generator__button"
onClick={generateDocument}
>
Generate the docx
</button>
<label htmlFor="file" onClick={handleClick}>
{file ? <>SUBMIT</> : <></>}
</label>
</>
);
};
This is what i got on mongodb after sending : MongodbBLOB
After that, i will do a get request to get my blob ( i already know how to get my blob) But then, how can i convert it to pdf (and keep the template used for my docx) ?
Update :
I found a package named docx-pdf which allow me to convert my docx to pdf.
But unfortunately, i've got a lot of webpack problems when i compiled (Can't resolve 'fs', 'child_process', 'path') because "webpack < 5 used to include polyfills for node.js core modules by default."
I will try to resolve this and will update this answer if i succeed to convert my docx :)
Related
I have a custom hook called useBase64 where I convert my image to Base64. This hook accepts an initial value and returns handleCreateBase64 function and logo (piece of state). handleCreateBase64 converts the file to base64 and stores it in logo. I am sending an array of images as File and then mapping through that array and I want to store it in logo and then send it to that component. right now the problem is that in the logo I can only see 1 base64 even if I select multiple files.
useBase64 hook: -
import { useCallback, useState } from "react";
export const useBase64 = (initialValue) => {
const [logo, setLogo] = useState(initialValue)
const handleCreateBase64 = useCallback(async (receivedFile) => {
let file = receivedFile;
if(initialValue instanceof Array) {
file = Object.values(receivedFile)
file.map(async el => {
const base64 = await convertToBase64(el)
setLogo([...logo, base64])
})
}
});
const convertToBase64 = (file) => {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
if (!file) {
console.log("no image");
} else {
fileReader.readAsDataURL(file);
fileReader.onload = () => {
resolve(fileReader.result);
};
}
fileReader.onerror = (error) => {
reject(error);
};
});
};
return { handleCreateBase64, logo }
}
The result: -
As you can see above that I have 3 images selected but I see only 1 base64. I am quiet new to this so it will be great if full explanation is provided.
Wrong use of map and setState
Try:
const items = await Promise.all(file.map(el => convertToBase64(el)))
setLogo([...logo, ...items])
Multer worked fine when I upload multiple images by using Postman but when I try to send files as an array from the front-end, it always return files is undefined and files is not iterable.
I think the problem is in the front-end React. How to fix this problem?
Back-end
router.post('/multer',auth ,upload.array('image'), async (req, res) => { //NOTE image is the field name
try {
const urls = []
const files = req.files;
console.log('Upload cloudinary running '+ files)
.
.
.
Front-end React
import React, {Fragment, useState, useEffect} from 'react';
const Dashboard = ({auth: { user, loading }}) => {
.
.
.
const [files, setFiles] = useState([]);
const handleChange = e => {
const file_reader = new FileReader();
const file = e.target.files[0];
let file = e.target.files[0];
file_reader.onload = () => {
setFiles([...files, { image: file_reader.result }]);
};
file_reader.readAsDataURL(file);
}
const handleSubbmitFile = (e) => {
e.preventDefault();
if(!files) return;
uploadImage(files);
}
const uploadImage = async (base64EncodedImage) => {
try {
const config = {
headers:{
'Content-Type' : 'application/json'
}
}
const body = JSON.stringify({files: base64EncodedImage});
await axios.post('/api/blogs/multer', body, config);
} catch (error) {
console.log(error);
}
}
return(
<form onSubmit={handleSubbmitFile} className="form-outline">
<input name="image" onChange={handleChange} type="file"
class="form-control-file" id="exampleFormControlFile1"
accept="image/*"/>
</form>
)
}
I found the problems and I fixed it by using formData in handleSubbmitFile and set the property filed to image by using append.
const handleSubbmitFile = (e) => {
e.preventDefault(); //NOTE prevent from reload the page
let formData = new FormData();
for(var i = 0 ; i < files.length; i++){
formData.append('image',files[i])
}
if(!files) return;
uploadImage(formData);
}
Another problems is that using Json.stringify() before using axios.
I didn't use Json.stringify() to convert formData before sending it to back-end via axios. As a result, Multer work well without problems
const uploadImage = async (formData) => {
try {
const config = {
headers:{
'Content-Type' : 'application/json'
}
}
await axios.post('/api/blogs/multer', formData, config);
} catch (error) {
console.log(error);
}
}
Basically I want to record audio and I'm using react-mic for this but it gives back blob but I want mp3 or wav file to send to the backend. How to POST blob file to the server?
This is my code in App.js
import React from "react";
import { ReactMic } from "react-mic";
import $ from "jquery";
import { findDOMNode } from "react-dom";
import "./App.css";
class App extends React.Component {
handleDisplayForStart = () => {
const startBtn = findDOMNode(this.refs.startBtn);
$(startBtn).addClass("d-none");
const stopBtn = findDOMNode(this.refs.stopBtn);
$(stopBtn).removeClass("d-none");
};
handleDisplayForStop = () => {
const stopBtn = findDOMNode(this.refs.stopBtn);
$(stopBtn).addClass("d-none");
const processBtn = findDOMNode(this.refs.processBtn);
$(processBtn).removeClass("d-none");
};
constructor(props) {
super(props);
this.state = {
blobURL: null,
recordedBlob: null,
record: false,
};
}
startRecording = () => {
this.setState({ record: true });
this.handleDisplayForStart();
};
stopRecording = () => {
this.setState({ record: false });
this.handleDisplayForStop();
};
onData(recordedBlob) {
console.log("chunk of real-time data is: ", recordedBlob);
}
onStop = (recordedBlob) => {
console.log(recordedBlob.blobURL);
const blobURL = recordedBlob.blobURL;
this.setState({ blobURL: blobURL });
this.onUpload();
return recordedBlob.blobURL;
};
onUpload = (recordedBlob) => {
// var form = new FormData();
// form.append("file", recordedBlob);
// fetch("http://localhost:3000/audio", {
// // content-type header should not be specified!
// method: "POST",
// body: form,
// })
// .then(function (response) {
// return response.text();
// })
// .then(function (text) {
// console.log(text); // The text the endpoint returns
// })
// .catch((error) => console.log(error));
};
render() {
return (
<div className="App">
<ReactMic
visualSetting="frequencyBars"
// mimeType='audio/mp3'
record={this.state.record}
className="d-none"
onStop={this.onStop}
onData={this.onData}
/>
<button
ref="startBtn"
className="start-btn"
onClick={this.startRecording}
type="button"
>
START
</button>
<button
ref="stopBtn"
className="stop-btn concentric-circles d-none"
onClick={this.stopRecording}
type="button"
>
STOP
</button>
<button
ref="processBtn"
className="process-btn d-none"
onClick={this.onUpload}
>
Processing..
</button>
<br />
<audio src={this.state.blobURL} controls />
</div>
);
}
}
export default App;
I tried various things from medium blogs and different StackOverflow answers but anything doesn't work correctly.
This may be a bit late, i also came across this problem and i also couldn't find a solution, sharing the solution so it may help others.
code for sending recorded blob file using react-mic to server,
const onStop = (blob) => {
const formData = new FormData();
// if you print blob in console you may get details of this obj
let blobWithProp = new Blob([blob["blob"]], blob["options"]);
formData.append("file", blobWithProp);
const postRequest = {
method: "POST",
body: formData,
};
fetch("http://127.0.0.1:5000/audio_out/", postRequest)
.then(async (res) => {
const data = await res.json();
console.log(data);
if (!res.ok) {
const err = (data && data.message) || res.status;
return Promise.reject(err);
}
})
.catch((err) => {
console.log(err);
});
};
Here onStop() is the callback function which is executed when recording stopped.
On your commented part of your code you are sending recorded blob file directly. I don't know much about it, but if you log the received file on console you may see it is an object file containing blob, options, etc. So, I think we need to create a new Blob file from that. I am just a beginner in web development, may be i am wrong. But the above code solved the problem.
I am new into this. So i found this example code to upload files to firebase storage using file pond. It works great when uploading the file.
import * as firebase from "firebase/app";
import "firebase/storage";
import shortid from "shortid";
import * as React from "react";
import { FilePond, registerPlugin } from "react-filepond";
import "filepond/dist/filepond.min.css";
import FilePondPluginImageExifOrientation from "filepond-plugin-image-exif-orientation";
import FilePondPluginImagePreview from "filepond-plugin-image-preview";
import "filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css";
import debug from "debug";
const log = debug("app:image");
registerPlugin(FilePondPluginImageExifOrientation, FilePondPluginImagePreview);
const storage = firebase.storage().ref();
export default function ImageUpload({
onRequestSave,
onRequestClear,
defaultFiles = []
}) {
const [files, setFiles] = React.useState(defaultFiles);
const ref = React.useRef(null);
return (
<FilePond
files={files}
ref={ref}
allowMultiple={false}
maxFiles={1}
server={{
process: (
_fieldName,
file,
_metadata,
load,
error,
progress,
_abort
) => {
const id = shortid.generate();
const task = storage.child("images/" + id).put(file, {
contentType: "image/jpeg"
});
task.on(
firebase.storage.TaskEvent.STATE_CHANGED,
snap => {
log("progress: %o", snap);
progress(true, snap.bytesTransferred, snap.totalBytes);
},
err => {
log("error: %o", err);
error(err.message);
},
() => {
log("DONE");
load(id);
// onRequestSave(id);
}
);
},
load: (source, load, error, progress, abort) => {
progress(true, 0, 1024);
storage
.child("images/" + source)
.getDownloadURL()
.then(url => {
let xhr = new XMLHttpRequest();
xhr.responseType = "blob";
xhr.onload = function(event) {
let blob = xhr.response;
log("loaded URL: %s", url);
load(blob);
};
xhr.open("GET", url);
xhr.send();
})
.catch(err => {
error(err.message);
abort();
});
}
}}
/>
);
}
If i add the following prop,:
instantUpload={false}
then FilePond will not automatically upload. But I want to write three functions:
handleUpload, handleRead and handleDelete :
to save, view and delete files. Can someone guide me into achieving that?
I want to upload document files in firestore using redux. i get the file in and passed it as a state to the action file with other datas. following is my code in action file.
const createJob = (project) => {
return (dispatch, getState, {getFirebase, getFirestore}) => {
const firestore = getFirestore();
firestore.collection('Jobs').add({
...project,
postedby:'Employer1',
Documents:project.Documents.name
}).then(()=>{
dispatch({type:'CREATE_JOB', project});
}).catch((err)=>{
dispatch({type:'CREATE_JOB_ERROR', err});
})
}
};
but the data is saved as C:\fakepath\doc1.doc
how to upload the actual file in firestore
Technically you can upload an image to firestore. You need to convert it to base64 text first. Just spent some time figuring it out. I take the selected file from an upload file from browser then I upload it in the callback of the file reader. Hopefully this helps someone out.
function getBase64(file){
var n = document.getElementById('nameU').value;
//name of uploaded file from textbox
var d = document.getElementById('dateU').value;
//date of uploaded file from textbox
var reader = new FileReader();
reader.onerror = function (error) {
console.log('Error: ', error);
};
reader.readAsDataURL(file);
reader.onload = function () {
let encoded = reader.result.split(',');
//you split this to get mimetype out of your base64
addForSale(Date.now().toString(10), {uDesc: n, date: d, uFile: encoded[1]});
// I just used a timestamp as the ID
}
};
function addForSale(id, data) {
var collection = firebase.firestore().collection('forsale');
return collection.doc(id).set(data);}
Hi You cannot directly store the image in firestore.What you have to do is first store the document in firebase storage and get the url as reponse.Once the response is received add the url in documents.
First create an Storage Action in reduce:
import { storage } from '../../../firebase/firebase';
import {
ADD_DOCUMENT_STARTED,
ADD_DOCUMENT_COMPLETED,
ADD_DOCUMENT_ERROR
} from '../../actionTypes/storageActionTypes';
import { toast } from 'react-toastify';
import constants from '../../../config/constants';
export const addDocumentStart = () => ({
type: ADD_DOCUMENT_STARTED
});
export const addDocumentSuccess = () => ({
type: ADD_DOCUMENT_COMPLETED
});
export const addDocumentFailure = () => ({
type: ADD_DOCUMENT_ERROR
});
export const addDocument = (values, pathName) => {
const toastId = toast('Uploading Attachment, Please wait..', {
autoClose: false
});
return (dispatch) =>
new Promise(function(resolve, reject) {
dispatch(addDocumentStart());
const timeStamp = new Date().getTime();
const image = values.document[0];
var name;
if (values && values.documentName) {
name = timeStamp.toString().concat(values.documentName);
} else {
name = timeStamp.toString().concat(image.name);
}
const imageUpload = storage.ref(`${pathName}/${name}`).put(image);
imageUpload.on(
'state_changed',
(snapshot) => {
switch (snapshot.state) {
case 'paused':
reject('Upload is paused');
dispatch(addDocumentFailure('Upload is paused'));
break;
}
},
(error) => {
switch (error.code) {
case 'storage/unauthorized':
reject('Permission Denied');
dispatch(addDocumentFailure('Permission Denied'));
break;
case 'storage/canceled':
reject('Upload Cancelled');
dispatch(addDocumentFailure('Upload Cancelled'));
break;
case 'storage/unknown':
reject('Server Response Error');
dispatch(addDocumentFailure('Server Response Error'));
break;
}
},
() => {
toast.update(toastId, {
render: 'Attachment Uploaded successfully',
type: toast.TYPE.SUCCESS,
autoClose: constants.toastTimer
});
storage
.ref(pathName)
.child(name)
.getDownloadURL()
.then((url) => {
dispatch(addDocumentSuccess());
resolve(url);
});
}
);
});
};
Then in your onsubmit:
this.props.dispatch(addDocument(values, 'Jobs')).then((resp) => {
let documentUrl = resp;
firestore.collection('Jobs').add({
...project,
postedby:'Employer1',
Documents:documentUrl
}).then(()=>{
});