PDF with jsPDF and AutoTable
PDF generation is a common requirement in web applications. In our recent Angular-18 TypeScript project, We Xor Geek implemented it using jsPDF and AutoTable. While working on generate PDF with jsPDF, we faced several challenges, which we successfully resolved. Here’s a breakdown of the solutions for generate PDF with jsPDF.
1.Formatting Complex Table Header
Challenge:
- The default AutoTable headers did not match the required design.
Solution:
- Multi-Column Merging: The FFFF section spans 3 columns.
- Multi-Row Headers: The 区分 (Category) and ラベル枚数 (Label Count) headers stretch across 2 rows for better clarity.
- Line Breaks in Headers: The ラベル枚数 column includes \n for text wrapping.
Here is code:(Angular-18)
const headerRows = [[
{ content: '区 分', rowSpan: 2,colSpan: 3 },
{ content: '区画数', rowSpan: 2 },
{ content: 'FFFF', colSpan: 3 }, // Merges into 3 columns below
{ content: 'ラベル'+'\n'+'枚数',rowSpan: 2 },],
[{ content: '基材(下地)材料名/品番' },{ content: '認定番号' },{ content: '使用量' }]];
2.Data Overflow in Columns
Challenge:
- Some table content exceeded cell limits, causing text truncation.
Solution:
- I enabled cellWidth and adjusted column width statically.
Here is code:(Angular-18)
columnStyles: {
0: { cellWidth: 47,fillColor: [255, 255, 255] },
1: { cellWidth: 87 },2: { cellWidth: 21 },3: { cellWidth: 43 },},
3.Ensuring a Minimum of 10 Rows
- The API sometimes returned fewer than 10 rows, making the table inconsistent.
- Blank rows were dynamically added to maintain a uniform table structure.
- The last row contained totals for 区画 (sections), 使用量 (usage), and ラベル (labels).
- These totals were calculated dynamically and inserted into the last row.
Here is code:(Angular-18)
const rows = pdfdetails.map(item => [
// Dynamically setting the values for each row
item.kubun_area === "1" || item.kubun_area === "3" ? "居 室" : "",
item.kubun_kabe === "1" ? "天井" : item.kubun_kabe !== "1" ? "壁" : "",
' '+' '+' '+' '+"階"+'\n'+item.kubun_kaisu,
' '+' '+' '+' '+"区画"+'\n'+item.kukaku_num,
item.sitaji_name + '\n' + item.merchandise_id,
item.nintei_no,
' '+' '+' '+' '+"㎡"+'\n'+item.shiyoryo,
' '+' '+' '+' '+"枚"+'\n'+item.label_num
]);
const minRows = 11;
const rowsToAdd = minRows - rows.length;
for (let i = 0; i < rowsToAdd; i++) {
rows.push([
"","","","","","","","","","",]);}
let kukaku_total=0
let siyoryo_total=0
let label_num_total=0
pdfdetails.map(item => [
kukaku_total += parseInt(String(item.kukaku_num), 10) || 0,
siyoryo_total += parseInt(String(item.shiyoryo), 10) || 0,
label_num_total += parseInt(String(item.label_num), 10) || 0
]);
rows[rows.length - 1] = [
"", "","",' '+' '+' '+' '+"区画"+'\n'+`${kukaku_total}`,
"","",' '+' '+' '+' '+"㎡"+'\n'+`${siyoryo_total}`,
' '+' '+' '+' '+"枚"+'\n'+`${label_num_total}`,];
4.Alignment and Styling Issues
Challenge:
- The alignment of table content was inconsistent.
Solution:
- Used halign and valign properties to ensure proper text positioning.
Here is code:(Angular-18)
bodyStyles: { fontSize: 11, textColor: 50, font: 'NotoSans',halign:'left',minCellHeight: rowHeight,valign: 'middle',cellPadding:.8},
headStyles: { fillColor: [255, 255, 255],valign: 'middle',halign:'center',font: 'NotoSans'}
5.Generate PDF:
Issue Faced:
- Initially, the PDF was only displayed in an iframe and did not download automatically.
- Users had to manually save the file, which was inconvenient.
Fix Implemented:
- Created a temporary element with href pointing to the Blob URL.
- Set the download attribute to specify the filename.
- Programmatically triggered a click event to initiate the download.
- Cleaned up by removing the element and revoking the Blob URL to free memory.
Here is Code:(Angular-18)
const pdfBlob = doc.output("blob");
const pdfUrl = window.URL.createObjectURL(pdfBlob);
const downloadLink = document.createElement("a");
downloadLink.href = pdfUrl;
downloadLink.download = `shinsei-${("00000000" +shinseiId).slice(-8
)}-${this.formatDatePdf(new Date())}.pdf`; // Specify the file name
downloadLink.style.display = "none"; // Hide the link
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
setTimeout(() => {
window.URL.revokeObjectURL(pdfUrl);
}, 100);
Now, the PDF automatically downloads without requiring user interaction!
Final Thoughts
Using jsPDF and AutoTable in Angular-18 is a powerful solution for PDF generation. By addressing these challenges, I achieved a smooth, well-formatted PDF output. If you’re implementing a similar feature, consider optimizing styles, handling large tables efficiently, and leveraging customization options.
Share