Reputation: 144
I am making a react application where I am in the need to generate a pdf from specific div.
Current scenario:
-> Made an app with a printable div and upon click button, the pdf is generated and downloaded.
Code:
<div id="printable-div">
<h1>Generate PDF</h1>
<p>Create a screenshot from this div, and make it as a PDF file.</p>
<p style={{ color: "red" }}>
*Then do not download instead attach to contact us form as attachment.
</p>
</div>
<button id="print" onClick={printPDF}>
Contact Us
</button>
printPDF Function:
const printPDF = () => {
setIsShowContact(true);
const domElement = document.getElementById("printable-div");
html2canvas(domElement).then((canvas) => {
const doc = new jsPdf();
doc.save(`attachment.pdf`);
});
};
On click of the Contact Us button, two actions happen.
-> Pdf file is downloaded.
-> A form with inputs Name
, Email
and Attachment
will be shown.
Working Codesandbox:
Requirement:
Here the requirement is onclick of the Contact Us button, the pdf should be generated but not downloadable instead the generate pdf needs to be attached to the input type="file"
in the contact form
.
We need to send information of the data user have right now in the specific div id="printable-div"
to backend api as pdf attachment on click of the contact button.
In real application, this is like an enquiry of a product, where user selects a product with some config's and finally that product info will be shown to user based on the config they choosen. Then the user will click Contact Us button, so the
printable-div
will have that product information which user searched, so we need to capture it as pdf and attach to input and send to backend on form submission.
Kindly help me with the inputs on achieving this scenario of making the generated pdf to attach as attachment to the input field.
Upvotes: 0
Views: 2817
Reputation: 981
1. You need to convert the PDF correctly, although the intention is to attach the PDF in the input field, you are downloading a blank PDF.
The first step would be to download the PDF correctly, referring to the DIV element whose id is printable-div, and after that, instead of downloading, attach it to the input field.
The invalid code is here:
const printPDF = () => {
setIsShowContact(true);
const domElement = document.getElementById("printable-div");
html2canvas(domElement).then((canvas) => {
const doc = new jsPdf(); <<- YOU`RE CREATING AN EMPTY PDF AND
doc.save('attachment.pdf'); <<- DOWNLOADING THIS EMPTY PDF
});
};
The solution is very simple, just use the argument
canvas
passed to the callback
function instead of generating a new PDF
2. You need to append the .files property and not add
instead the generate pdf needs to be
attachedto theinput type="file"
in the contactform
.
It is impossible to add new items to the .files
of input[type="file"]
field that belongs to the FileList class, on the other hand, it is possible to change it, that is, remove the old FileList and attach a new one with the necessary file(s).
Which in this example would just be a single file.
1. You need to convert the canvas that was passed as a callback from the html2canvas
function to a file.
You can do this in the following way:
const canvas2file = (canvas, fileName = 't.jpg') =>
new Promise(resolve => {
canvas.toBlob((blob) => {
resolve(new File([blob], fileName, { type: "image/jpeg" }))
}, 'image/jpeg');
})
2. You need to use this function in the promise that is expected by the html2canvas
function, that is:
html2canvas(domElement)
.then(canvas2file)
3. You will need to get a reference (or document.querySelector
/ document.getElementXXX
) to the input field whose type is file, and also a state variable for the file itself that was converted previously (by the canvas2file function), that is:
function App() {
...
const fileRef = useRef(); //Reference to input[type="file"]
const [file, setFile] = useState(); //State variable that contains the File
...
}
4. Modify the printPDF
function to save the File
to the state variable
const printPDF = () => {
setIsShowContact(true);
const domElement = document.getElementById("printable-div");
html2canvas(domElement)
.then(canvas2file)
.then(setFile);
};
5. Use the useEffect
hook to detect the change of the File
in the state variable, that is, every time the user clicks on "Contact Us", a new File
will be generated through the canvas2file
function, and this file will be stored in the file state variable.
After detecting this change, we remove the .files (of type FileList) from the input[type="file"] and we will re-attach a new FileList to the input, example:
useEffect(() => {
if(!fileRef.current) return;
let list = new DataTransfer();
list.items.add(file);
fileRef.current.files = list.files;
console.log(fileRef.current)
}, [file])
const { useEffect, useRef, useState } = React;
const canvas2file = (canvas, fileName = 't.jpg') =>
new Promise(resolve => {
canvas.toBlob((blob) => {
resolve(new File([blob], fileName, { type: "image/jpeg" }))
}, 'image/jpeg');
})
function App() {
const [isShowContact, setIsShowContact] = useState(false);
const fileRef = useRef();
const [file, setFile] = useState();
useEffect(() => {
if(!fileRef.current) return;
let list = new DataTransfer();
list.items.add(file);
fileRef.current.files = list.files;
console.log(fileRef.current)
}, [file])
const printPDF = () => {
setIsShowContact(true);
const domElement = document.getElementById("printable-div");
html2canvas(domElement)
.then(canvas2file)
.then(setFile);
};
return (
<div className="App">
<div id="printable-div">
<h1>Generate PDF</h1>
<p>Create a screenshot from this div, and make it as a PDF file.</p>
<p style={{ color: "red" }}>
*Then do not download instead attach to contact us form as attachment.
</p>
</div>
<br />
<button id="print" onClick={printPDF}>
Contact Us
</button>
<br />
<br />
<br />
{isShowContact && (
<form>
<div id="contact">
<div className="block">
<label>Name:</label>
<input type="text" defaultValue="John Doe" />
</div>
<br />
<div className="block">
<label>Email:</label>
<input type="email" defaultValue="[email protected]" />
</div>
<br />
<div className="block">
<label>Table pdf as attachment:</label>
<input ref={fileRef} type="file" />
</div>
</div>
</form>
)}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
label {
display: inline-block;
width: 75x;
text-align: right;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<title>React App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
Demo URL: https://codesandbox.io/s/html2canvas-jspdf-forked-5ennvt?file=/src/index.js
Upvotes: 3
Reputation: 57
From what I understand, you're essentially trying to take a screenshot of an area and send that in the form data.
You can use a library called use-react-screenshot
. It's really easy to implement and you'll need to set the reference area using the createRef
hook from react.
Once you've got the screenshot, you can silently upload that somewhere to a database, alongside that form data, so that you can reference the screenshot when looking at the form data.
With the current method, I don't think there is a way to set the file input programmatically.
I hope this helps.
Upvotes: -1
Reputation: 91
You cannot set the value of a file input unfortunately, even if you give it a "file" yourself. You will have to intercept the form submission and construct your own request while adding the attachment. FormData API is useful for that.
Here's the link to the updated sandbox: https://codesandbox.io/s/html2canvas-jspdf-forked-eun59q?file=/src/index.js
And here's the used jsPDF function docs: https://artskydj.github.io/jsPDF/docs/jsPDF.html#output
Upvotes: 1