javascriptdatatablesjquery-eventsexport-to-pdf

How to export multiple datatables into PDF, into a single zipped folder


I have a solution in a system for group reporting where I have a selected number of datatables rendered out of view and I provide download links to each table, i.e.:

let singlePDFdownload = function (table_id) {
    $(`.toolbar${table_id} .custom-pdf`).trigger('click')
}

This works fine for single downloads, but I also want to enable a quick button so all downloads can be generated at once. I currently do this with the .custom_pdf class so I can group the buttons that execute the datatable export of said table. This is a problem with the way I currently have this, because it initiates multiple downloads in the browser with multiple pop-ups (occasionally showing a warning).

How do I go about combining those 'clicks' into a single download?

Can I capture the download that occurs because of the trigger('click;) and then combine the PDFs somehow in a zipped folder, or how can this be accomplished?

Here is the code that triggers the multiple downloads:

const $massPDFExport = $('#massPDFExport')
$massPDFExport.on('click', () => {
    // remember all tables shown have this class
    $(`.custom-pdf`).trigger('click')
})

Here is a fully reproducible example:

<link rel="stylesheet" href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css">
<link rel="stylesheet" href="https://cdn.datatables.net/buttons/2.3.6/css/buttons.dataTables.min.css">

<script src="https://code.jquery.com/jquery-3.5.1.js"></script>
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/buttons/2.3.6/js/dataTables.buttons.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.3/jszip.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.53/pdfmake.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.53/vfs_fonts.js"></script>
<script src="https://cdn.datatables.net/buttons/2.3.6/js/buttons.html5.min.js"></script>
<script src="https://cdn.datatables.net/buttons/2.3.6/js/buttons.print.min.js"></script>


<script>
    $(document).ready(function () {
        $('#example_1').DataTable({
            dom: 'Bfrtip',
            buttons: [
                'copy', 'csv', 'excel', 'pdf', 'print'
            ]
        });
        $('#example_2').DataTable({
            dom: 'Bfrtip',
            buttons: [
                'copy', 'csv', 'excel', 'pdf', 'print'
            ]
        });
        $('#export_all').on('click', () => {
            $('.buttons-pdf').each(function (a) {
                console.log(9888)
                $(this).trigger("click");
            });
        });
    });
</script>

<a id='export_all' class=''> Export All As PDF</a>

<table id="example_1" class="display nowrap" style="width:100%">
    <thead>
    <tr>
        <th>Name</th>
        <th>Position</th>
        <th>Office</th>
        <th>Age</th>
        <th>Start date</th>
        <th>Salary</th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td>Tiger Nixon</td>
        <td>System Architect</td>
        <td>Edinburgh</td>
        <td>61</td>
        <td>2011-04-25</td>
        <td>$320,800</td>
    </tr>
    <tr>
        <td>Garrett Winters</td>
        <td>Accountant</td>
        <td>Tokyo</td>
        <td>63</td>
        <td>2011-07-25</td>
        <td>$170,750</td>
    </tr>
    </tbody>
</table>

<table id="example_2" class="display nowrap" style="width:100%">
    <thead>
    <tr>
        <th>Name</th>
        <th>Position</th>
        <th>Office</th>
        <th>Age</th>
        <th>Start date</th>
        <th>Salary</th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td>Tiger Nixon</td>
        <td>System Architect</td>
        <td>Edinburgh</td>
        <td>61</td>
        <td>2011-04-25</td>
        <td>$320,800</td>
    </tr>
    <tr>
        <td>Garrett Winters</td>
        <td>Accountant</td>
        <td>Tokyo</td>
        <td>63</td>
        <td>2011-07-25</td>
        <td>$170,750</td>
    </tr>
    </tbody>
</table>


Solution

  • I figured out a solution and figured I would post an answer showing an easy example of how to combine and zip multiple PDF files using datatables, JSZip, and PDfMake since I could not find many real examples of it.

    Overall it came down to the customize option for button definitions.

    buttons.pdf.js

    function customPdfButtonAction (e, dt, button, config) {
      this.processing(true);
    
      var that = this;
      var data = dt.buttons.exportData(config.exportOptions);
      var info = dt.buttons.exportInfo(config);
      var rows = [];
    
      if (config.header) {
        rows.push(
          $.map(data.header, function (d) {
            return {
              text: typeof d === "string" ? d : d + "",
              style: "tableHeader"
            };
          })
        );
      }
    
      for (var i = 0, ien = data.body.length; i < ien; i++) {
        rows.push(
          $.map(data.body[i], function (d) {
            if (d === null || d === undefined) {
              d = "";
            }
            return {
              text: typeof d === "string" ? d : d + "",
              style: i % 2 ? "tableBodyEven" : "tableBodyOdd"
            };
          })
        );
      }
    
      if (config.footer && data.footer) {
        rows.push(
          $.map(data.footer, function (d) {
            return {
              text: typeof d === "string" ? d : d + "",
              style: "tableFooter"
            };
          })
        );
      }
    
      var doc = {
        pageSize: config.pageSize,
        pageOrientation: config.orientation,
        content: [
          {
            table: {
              headerRows: 1,
              body: rows
            },
            layout: "noBorders"
          }
        ],
        styles: {
          tableHeader: {
            bold: true,
            fontSize: 11,
            color: "white",
            fillColor: "#2d4154",
            alignment: "center"
          },
          tableBodyEven: {},
          tableBodyOdd: {
            fillColor: "#f3f3f3"
          },
          tableFooter: {
            bold: true,
            fontSize: 11,
            color: "white",
            fillColor: "#2d4154"
          },
          title: {
            alignment: "center",
            fontSize: 15
          },
          message: {}
        },
        defaultStyle: {
          fontSize: 10
        }
      };
    
      if (info.messageTop) {
        doc.content.unshift({
          text: info.messageTop,
          style: "message",
          margin: [0, 0, 0, 12]
        });
      }
    
      if (info.messageBottom) {
        doc.content.push({
          text: info.messageBottom,
          style: "message",
          margin: [0, 0, 0, 12]
        });
      }
    
      if (info.title) {
        doc.content.unshift({
          text: info.title,
          style: "title",
          margin: [0, 0, 0, 12]
        });
      }
    
      if (config.customize) {
        config.customize(doc, config, dt);
      }
    
      var pdf = window.pdfMake.createPdf(doc);
    
      if (config.download === "open" && !_isDuffSafari()) {
        pdf.open();
      } else {
        // In case of silent mode — exit without downloading
        if (e.detail.silent) return this.processing(false);
    
        pdf.download(info.filename);
      }
    
      this.processing(false);
    }
    
    window.customPdfButtonAction = customPdfButtonAction
    

    main_functionality.js

    (function ($) {
      let PDF_BLOB_PROMISES = [];
      const buttons = [
        "copy",
        "csv",
        "excel",
        "print",
        {
          extend: "pdf",
          text: "PDF",
          customize: customPDFButtonCustomize,    // here is the key
          action: customPdfButtonAction          // here is the key
        }
      ]
    
      // Push Blob to array of Promises
        
      async function customPDFButtonCustomize(doc) {
        try {
          PDF_BLOB_PROMISES.push(
            new Promise((resolve, reject) => {
              window.pdfMake.createPdf(doc).getBlob(resolve);
            })
          );
        } catch (e) {
          console.error("Push PDF to Blob failed", e);
        }
      }
    
      // Init Data tables
    
      function initDT() {
        $('[data-type="dt"]').each(function (i) {
          const table = $(this).DataTable({
            buttons
          });
    
          table
            .buttons()
            .container()
            .appendTo(`#DataTables_Table_${i}_wrapper .col-md-6:eq(0)`);
        });
      }
    
      // Export All button handler
    
      async function exportAllPDF() {
        try {
          const zip = new window.JSZip();
          const folder = zip.folder("pdf");
    
          PDF_BLOB_PROMISES = [];
    
          $(".buttons-pdf").each(function (a) {
            const event = new CustomEvent("click", {
              detail: {
                silent: true
              }
            });
            this.dispatchEvent(event);
          });
    
          const blobs = await Promise.all(PDF_BLOB_PROMISES);
    
          if (!blobs.length) return;
    
          for (let i = 0; i < blobs.length; i++) {
            folder.file(`pdf-${i + 1}.pdf`, blobs[i]);
          }
    
          const zipped = await zip.generateAsync({ type: "blob" });
    
          window.saveAs(zipped, "pdfs.zip");
        } catch (e) {
          console.error("Eport All PDF failed", e);
        }
      }
    
      $(document).ready(function () {
        // Init Data Tables
        initDT();
    
        // Export All button event listener
        $("#export_all").on("click", exportAllPDF);
      })
    })(window.jQuery)
    

    HTML Page

    <!DOCTYPE html>
    <html>
      <head>
        <title>Show Me Some Magic</title>
        <meta charset="UTF-8" />
        <!-- CSS -->
        <link
          rel="stylesheet"
          href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.2/css/bootstrap.css"
        />
        <link
          rel="stylesheet"
          href="https://cdn.datatables.net/1.13.4/css/dataTables.bootstrap4.min.css"
        />
        <link
          rel="stylesheet"
          href="https://cdn.datatables.net/buttons/2.3.6/css/buttons.bootstrap4.min.css"
        />
        <style>
            body {
              padding: 2rem;
            }
    
            .dataTables_wrapper {
              margin-bottom: 3rem;
            }
    
            .btn-export-all {
              background: #afddff;
              border-radius: 10px;
              border: none;
              padding: 10px 20px;
              cursor: pointer;
            }
    
        </style>
    
        <!-- VENDOR JS -->
        <script src="https://code.jquery.com/jquery-3.5.1.js"></script>
        <script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
        <script src="https://cdn.datatables.net/1.13.4/js/dataTables.bootstrap4.min.js"></script>
        <script src="https://cdn.datatables.net/buttons/2.3.6/js/dataTables.buttons.min.js"></script>
        <script src="https://cdn.datatables.net/buttons/2.3.6/js/buttons.bootstrap4.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.3/jszip.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.53/pdfmake.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.53/vfs_fonts.js"></script>
        <script src="https://cdn.datatables.net/buttons/2.3.6/js/buttons.html5.min.js"></script>
        <script src="https://cdn.datatables.net/buttons/2.3.6/js/buttons.print.min.js"></script>
        <script src="https://cdn.datatables.net/buttons/2.3.6/js/buttons.colVis.min.js"></script>
    
        <!-- Local JS -->
        <script src="./js/buttons.pdf.js"></script>
        <script src="./js/main_functionality.js"></script>
      </head>
    
      <body>
        <div style="padding: 20px 0;">
          <button id="export_all" class="btn-export-all">Export All As PDF</button>
        </div>
    
        <table data-type="dt" class="table table-striped table-bordered compact">
          <thead>
            <tr>
              <th>Name</th>
              <th>Position</th>
              <th>Office</th>
              <th>Age</th>
              <th>Start date</th>
              <th>Salary</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>Tiger Nixon 1</td>
              <td>System Architect</td>
              <td>Edinburgh</td>
              <td>61</td>
              <td>2011-04-25</td>
              <td>$320,800</td>
            </tr>
            <tr>
              <td>Garrett Winters 1</td>
              <td>Accountant</td>
              <td>Tokyo</td>
              <td>63</td>
              <td>2011-07-25</td>
              <td>$170,750</td>
            </tr>
          </tbody>
        </table>
    
        <table data-type="dt" class="table table-striped table-bordered compact">
          <thead>
            <tr>
              <th>Name</th>
              <th>Position</th>
              <th>Office</th>
              <th>Age</th>
              <th>Start date</th>
              <th>Salary</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>Tiger Nixon 2</td>
              <td>System Architect</td>
              <td>Edinburgh</td>
              <td>61</td>
              <td>2011-04-25</td>
              <td>$280,800</td>
            </tr>
            <tr>
              <td>Garrett Winters 2</td>
              <td>Accountant</td>
              <td>Tokyo</td>
              <td>63</td>
              <td>2011-07-25</td>
              <td>$590,750</td>
            </tr>
          </tbody>
        </table>
      </body>
    </html>