Print PDF with jsPDF

Hi all,
Following Use PDF Exporter to print content of table - #13 by PatrickMast
I'm using jsPDF to print tables for FREE with out API and it's work great, attached example of the PDF document and the code

I have an issue with tables that are more than 3 pages, I don't understand where is the limitation !!!


this is the JS code:

async function generatePDF() {
console.log("Starting PDF generation process...");
const { jsPDF } = window.jspdf;
if (!jsPDF) {
console.error("jsPDF not loaded");
return;
}

const doc = new jsPDF({
orientation: "landscape",
unit: "mm",
format: "a4",
putOnlyUsedFonts: true,
floatPrecision: 16
});

const pageHeight = doc.internal.pageSize.getHeight();
const pageWidth = doc.internal.pageSize.getWidth();

const formatDate = (dateString) => {
const d = new Date(dateString);
return ${String(d.getDate()).padStart(2, '0')}.${String(d.getMonth() + 1).padStart(2, '0')}.${String(d.getFullYear()).slice(-2)} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')};
};

// Logo, Header, and Title
const addPageHeader = (pageNum) => {
doc.addImage(QSA_Logo.value, 'PNG', 8, 8, 20, 9);
const currentDate = formatDate(new Date());
doc.setFontSize(8);
doc.text(currentDate, pageWidth - 15, 15, null, null, "right");
doc.setFontSize(11);
doc.text('Document List', pageWidth / 2, 18, null, null, "center");
};

const addPageFooter = (pageNum) => {
doc.setFontSize(8);
doc.text(Page ${pageNum}, pageWidth - 10, pageHeight - 10, null, null, "right");
doc.text("Generated by QSA-Soft https://qsa-soft.online", pageWidth / 2, pageHeight - 10, null, null, "center");
};

let currentPage = 1;

// Table Content
const visibleFields = [
{ field: "doc_category", display: "Category" },
{ field: "doc_no", display: "Number" },
{ field: "doc_ver", display: "Version" },
{ field: "doc_status", display: "Status" },
{ field: "doc_title", display: "Title" },
{ field: "open_date", display: "Open Date" },
{ field: "last_update", display: "Last update" },
{ field: "update_by", display: "Update By" },
{ field: "doc_link", display: "Word Link" },
{ field: "pd_flink", display: "PDF Link" },
{ field: "doc_rem", display: "Remarks" }
];

const tableHeaders = visibleFields.map(field => field.display);
const fieldKeys = visibleFields.map(field => field.field);

const sortedData = DOC_table.data.sort((a, b) => a.doc_category - b.doc_category);
const tableData = sortedData.map(row => {
return fieldKeys.map((field) => {
if ((field === "doc_link" && row.doc_link) || (field === "pd_flink" && row.pd_flink)) {
return "link"; // Handle links later
}
if (field === "open_date" || field === "last_update") {
return formatDate(row[field]);
}
return row[field] || "";
});
});

const columnStyles = {
doc_category: { cellWidth: 10 },
doc_no: { cellWidth: 10 },
doc_ver: { cellWidth: 25 },
doc_status: { cellWidth: 25 },
doc_title: { cellWidth: 10 },
open_date: { cellWidth: 10 },
last_update: { cellWidth: 10 },
update_by: { cellWidth: 10 },
doc_link: { cellWidth: 10 },
pd_flink: { cellWidth: 10 },
doc_rem: { cellWidth: 10 }
};

const rowStyles = {
fillColor: (rowIndex) => (rowIndex % 2 === 0 ? [220, 220, 220] : [255, 255, 255])
};

// Increase page break handling for large datasets
const maxHeight = pageHeight - 40; // Adjust this to fit content more efficiently

// Wrap the table generation inside a setTimeout to allow async handling
setTimeout(() => {
doc.autoTable({
startY: 30,
head: [tableHeaders],
body: tableData,
styles: { overflow: 'linebreak', cellPadding: 2 },
headStyles: { fontSize: 9, fillColor: [0, 102, 204] },
bodyStyles: { fontSize: 8, textColor: [10, 10, 10] },
columnStyles: columnStyles,
rowStyles: {
fillColor: (rowIndex) => rowStyles.fillColor(rowIndex)
},
margin: { top: 30, bottom: 10, left: 10, right: 10 },
maxHeight: maxHeight, // Limit the height of the table content per page
didDrawCell: function(data) {
const rowIndex = data.row.index;
const columnIndex = data.column.index;

    if (fieldKeys[columnIndex] === "doc_link") {
      const linkUrl = sortedData[rowIndex].doc_link;
      if (linkUrl) {
        doc.link(data.cell.x, data.cell.y, data.cell.width, data.cell.height, { url: linkUrl });
      }
    }
    if (fieldKeys[columnIndex] === "pd_flink") {
      const linkUrl = sortedData[rowIndex].pd_flink;
      if (linkUrl) {
        doc.link(data.cell.x, data.cell.y, data.cell.width, data.cell.height, { url: linkUrl });
      }
    }
  },
  didDrawPage: function(data) {
    addPageHeader(currentPage);
    addPageFooter(currentPage);
    currentPage++;
  }
});

const mainImage = SignatureBackground.value;
if (mainImage) {
  const imgX = 20;
  const imgY = pageHeight - 40;
  const imgWidth = 40;
  const imgHeight = 15;
  doc.addImage(mainImage, 'PNG', imgX, imgY, imgWidth, imgHeight);

  const name = G_GetUser_info.data.full_name[0];
  doc.setFontSize(9);
  doc.setFont("times", "italic");
  doc.setTextColor(50, 50, 50);
  doc.text(name, imgX + 8, imgY + 8);

  doc.setFontSize(9);
  doc.setTextColor(69, 69, 69);
  doc.text(formatDate(new Date()), imgX + 18, imgY + 14, null, null, "center");
}

doc.save('Document List.pdf');
console.log("PDF generated successfully!");

}, 500); // Delay of 100ms, adjust as needed
}

// Call the function to generate the PDF
generatePDF();

Hello @Avner1!

I am not familiar with jsPDF :sweat_smile:

What error are you getting with tables that are more than three pages?

Are the other pages from the table just not showing up? Are you able to increase the table size to reduce the number of pages?

Hi Jay, already solved it. Thanks.

Avner Avrahami
+972 54 228 1158

1 Like

@Avner1 Great to hear!

Feel free to share your solution in case there are other users that run into similar issues :sweat_smile:

Handling PDFs has been somewhat tricky at times with Retool so this is great you have a solution working with jsPDF :tada:

SURE
async function generatePDF() {
console.log("Starting PDF generation process...");

const { jsPDF } = window.jspdf;
if (!jsPDF) {
    console.error("jsPDF not loaded");
    return;
}

const doc = new jsPDF({
    orientation: "landscape",
    unit: "mm",
    format: "a4",
    putOnlyUsedFonts: true,
    floatPrecision: 16
});

const notoSansVariableFontBase64 = ArielFont.value; // Replace this with the actual Base64 encoded string

// Add the font to VFS (Virtual File System)
doc.addFileToVFS("Helvetica World.ttf", notoSansVariableFontBase64);

// Register the font
doc.addFont("Helvetica World.ttf", "NotoSansVariable", "normal");

// Set the font to the new one
doc.setFont("NotoSansVariable");

console.log(doc.getFontList()); // Check if "NotoSans" appears in the console output

function capitalizeFirstLetter(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}

const allTableColumns = Object.keys(DOC_tablePDF.data[0]).filter(key => !['id', 'updater', 'company'].includes(key));
console.log("allTableColumns :", allTableColumns);

const columnsToFilter = DOC_table_column_multiSelect.value;
console.log("columnsToFilter :", columnsToFilter);

const tableHeader = allTableColumns.filter(key => !columnsToFilter.includes(key))
  //  .map(key => capitalizeFirstLetter(key));
console.log("Table Header:", tableHeader);

const tableHeaderCapital = allTableColumns.filter(key => !columnsToFilter.includes(key)).map(key => capitalizeFirstLetter(key));
console.log("Table Header:", tableHeader);

// Function to format date to "DD.MM.YY HH:MM"
const formatDate = (dateString) => {
if (!dateString) return ""; // Return an empty string if the date is null or undefined

   try {
       const d = new Date(dateString);
       // Check if the date is valid
       if (isNaN(d.getTime())) return ""; // Return empty if date is invalid

       return `${String(d.getDate()).padStart(2, '0')}.${String(d.getMonth() + 1).padStart(2, '0')}.${String(d.getFullYear()).slice(-2)} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
   } catch (error) {
       console.error("Error formatting date:", error);
       return "";
   }

};

// Create an object to store the link cells with their real URLs
const linkCells = [];

// Generate table data and apply date formatting to columns that contain "date"
const tableData = DOC_tablePDF.data.map((row, rowIndex) => 
    tableHeader.map((col, colIndex) => {
        let value = row[col];
        // If the value is a URL, store the real URL and replace the cell content with 'link'
        if (typeof value === 'string' && value.startsWith('http')) {
            
            // Store the real URL and the cell position in the linkCells array
            linkCells.push({
                row: rowIndex,
                col: colIndex,
                url: row[col] // This is the real URL
            });

          value = 'link'; // Replace with 'link' text
            row[col] = value; // Update the row data with 'link' text
          }

     if (col.toLowerCase().includes("updated") || col.toLowerCase().includes("initiated")) {
      return formatDate(value);  // If condition is met, format the date
      } else {
      return value;  // Otherwise, return the value as it is
  }}));



console.log("Final Filtered Table Data:", tableData);
console.log("Link Cells Array:", linkCells);

const pageHeight = doc.internal.pageSize.getHeight();
const pageWidth = doc.internal.pageSize.getWidth();

const addPageHeader = () => {
    doc.addImage(QSA_Logo.value, 'PNG', 8, 8, 20, 9);
    const currentDate = formatDate(new Date());
    doc.setFontSize(8);
    doc.text(currentDate, pageWidth - 15, 15, null, null, "right");
    doc.setFontSize(11);
    doc.text("Documents List", pageWidth / 2, 18, null, null, "center");
};

const addPageFooter = (doc, pageWidth, pageHeight) => {
    const pageNo = doc.internal.getCurrentPageInfo().pageNumber;
    doc.setFontSize(8);
    doc.text(`Page ${pageNo}`, pageWidth - 10, pageHeight - 10, null, null, "right");
    doc.text("Generated by QSA-Soft https://qsa-soft.online", pageWidth / 2, pageHeight - 10, null, null, "center");
};

doc.autoTable({
    startY: 25,
    head: [tableHeaderCapital],
    body: tableData,
    styles: { overflow: 'linebreak', cellPadding: 1 },
    headStyles: { fontSize: 8, fillColor: [0, 102, 204] },
    bodyStyles: { fontSize: 8, textColor: [10, 10, 10] },
    theme: 'striped',
    margin: { top: 20, bottom: 20, left: 10, right: 10 },
    
    didDrawCell: function(data) {
        const rowIndex = data.row.index;
        const columnIndex = data.column.index;
      

        // Check if this cell is a link and if we have a URL stored in linkCells
        linkCells.forEach(linkCell => {
            if (linkCell.row === rowIndex && linkCell.col === columnIndex) {
                const url = linkCell.url;
                const x = data.cell.x;
                const y = data.cell.y;
                const width = data.cell.width;
                const height = data.cell.height;
                doc.link(x, y, width, height, { url: url }); // Add the actual link

            }
        });
    },

    didDrawPage: function (data) {
        addPageHeader(doc, pageWidth, pageHeight);  
        addPageFooter(doc, pageWidth, pageHeight);
    }
});

// Add a signature image to the document, if available
const mainImage = SignatureBackground.value;
if (mainImage) {
    const imgX = 20;
    const imgY = pageHeight - 40;
    const imgWidth = 40;
    const imgHeight = 15;
    doc.addImage(mainImage, 'PNG', imgX, imgY, imgWidth, imgHeight); // Signature image

    const name = G_GetUser_info.data.full_name[0]; // User name from data
    doc.setFontSize(9);
    //  doc.setFont("times", "italic");
    doc.setTextColor(50, 50, 50);
    doc.text(name, imgX + 8, imgY + 8); // User name next to signature

    doc.setFontSize(9);
    doc.setTextColor(69, 69, 69);
    doc.text(formatDate(new Date()), imgX + 18, imgY + 14, null, null, "center"); // Date next to signature
}

doc.save('Document List.pdf');

}
generatePDF();

1 Like