Search code examples
datatablespdfmake

Datatables - uncaught malformed table row a cell is undefined when trying to export to PDF


I have the below table,

<table id="dtBasicExample" class="table table-bordered" style="border : none;">
    <thead class="text-info" style="border : none;">
        <tr>
            <th colspan="16" style="border : none;"></th>
        </tr>
        <tr>
            <th colspan="2" class="text-right" style="border : none;">Date</th>
            <th colspan="14" style="border : none;">: <%=dstart%> - <%=dend%></th>
        </tr>
        <tr>
            <th colspan="2" class="text-right" style="border:none;">Personel</th>
            <th colspan="14" style="border : none;">: <%=pers%></th>
        </tr>
        <tr>
            <th colspan="2" class="text-right" style="border:none;">Grade</th>
            <th colspan="14" style="border : none;">: <%=grade%></th>
        </tr>
        <tr>
            <th colspan="2" class="text-right" style="border:none;">Date Generated</th>
            <th colspan="14" style="border : none;">: <%=nowdate %></th>
        </tr>
        <tr>
            <th colspan="16" style="border-left : none; border-right: none;"/>
        </tr>
        <tr class="text-center">
            <th rowspan="3">Date</th>
            <th colspan="14">Number of Students</th>
            <th rowspan="3">Grand Total</th>
        </tr>
        <tr class="text-center">
            <th colspan="7">Class A</th>
            <th colspan="7">Class B</th>
        </tr>
        <tr class="text-center">
            <th style="width:6.2%;">Eng</th>
            <th style="width:6.2%;">BM</th>
            <th style="width:6.2%;">Man</th>
            <th style="width:6.2%;">SC</th>
            <th style="width:6.2%;">PHY</th>
            <th style="width:6.2%;">CHE</th>
            <th style="width:6.2%;">Sum</th>
            <th style="width:6.2%;">Eng</th>
            <th style="width:6.2%;">BM</th>
            <th style="width:6.2%;">Man</th>
            <th style="width:6.2%;">SC</th>
            <th style="width:6.2%;">PHY</th>
            <th style="width:6.2%;">CHE</th>
            <th style="width:6.2%;">Sum</th>
        </tr>
    </thead>
...

Below is my js for my PDF exporting.

{
    action: function (e, dt, button, config) {
        showNotification('top', 'right', 'PDF exported sucessfully!');
        $.fn.dataTable.ext.buttons.pdfHtml5.action.call(this, e, dt, button, config);
    },
    extend: 'pdf',
    className: 'btn-info btnspaces',
    text: 'Export to PDF',
    filename: 'Students',
    orientation: 'landscape',
    pageSize: 'LEGAL',
    customize: function (pdf) {
        var firstHeaderRow = [];
        var secondHeaderRow = [];
        //11
        $('#dtBasicExample').find("thead>tr:nth-child(7)>th").each(
            function (index, element) {
            var colSpan = element.getAttribute("colspan");
            var rowSpan = element.getAttribute("rowspan");
            firstHeaderRow.push({
                text: element.innerHTML,
                style: "tableHeader",
                colSpan: colSpan,
                rowSpan: rowSpan
            });
            for (var i = 0; i < colSpan - 1; i++) {
                firstHeaderRow.push({});
            }
        });
        //10
        $('#dtBasicExample').find("thead>tr:nth-child(8)>th").each(
            function (index, element) {
            var colSpan = element.getAttribute("colspan");
            var rowSpan = element.getAttribute("rowspan");
            secondHeaderRow.push({
                text: element.innerHTML,
                style: "tableHeader",
                colSpan: colSpan,
                rowSpan: rowSpan
            });
            for (var i = 0; i < colSpan - 1; i++) {
                secondHeaderRow.push({});
            }
        });

        pdf.content[1].table.headerRows = 3;

        var objLayout = {};
        objLayout['hLineWidth'] = function (i) {
            return .5;
        };
        objLayout['vLineWidth'] = function (i) {
            return .5;
        };
        objLayout['hLineColor'] = function (i) {
            return 'black';
        };
        objLayout['vLineColor'] = function (i) {
            return 'black';
        };
        objLayout['paddingTop'] = function (i) {
            return 5;
        };
        objLayout['paddingBottom'] = function (i) {
            return 5;
        };
        objLayout['paddingLeft'] = function (i) {
            return 9;
        };
        objLayout['paddingRight'] = function (i) {
            return 9;
        };
        pdf.content[1].table.body.unshift(secondHeaderRow);
        pdf.content[1].table.body.unshift(firstHeaderRow);
        pdf.content[1].layout = objLayout;
    }

I have no issue exporting this to excel, but when I try to export it to PDF, it is giving the error

uncaught malformed table row a cell is undefined

I tried to remove the colspan and manually add in <td></td> into the tables up to 16 cols, it works fine, but the date and grand total are in the last column instead of spanning throughout the 3 columns.


Solution

  • To export the pdf header with colSpan, and rowSpan, empty cell should be padded for cell covered by span. The following code snippet will pad empty cell base on the colSpan and rowSpan of the head row(<th>).


    Due to Download in Sandboxed Iframes (removed), the button in the code snippet will not work, you may copy the following code to an html file, and open the file with a browser to see the effect.

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/v/dt/jszip-2.5.0/dt-1.10.21/b-1.6.2/b-flash-1.6.2/b-html5-1.6.2/b-print-1.6.2/datatables.min.css" />
    
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.36/pdfmake.min.js"></script>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.36/vfs_fonts.js"></script>
    <script type="text/javascript" src="https://cdn.datatables.net/v/dt/jszip-2.5.0/dt-1.10.21/b-1.6.2/b-flash-1.6.2/b-html5-1.6.2/b-print-1.6.2/datatables.min.js"></script>
    
    <script type="text/javascript">
        $(document).ready(function() {
            $('#dataTable').DataTable({
                dom: 'Bfrtip',
                buttons: [{
                    extend: 'pdf',
                    download: 'download',
                    text: 'Export to PDF',
                    filename: 'Students',
                    orientation: 'landscape',
                    pageSize: 'LEGAL',
                    customize: function(pdfDocument) {
                        let headerRows = [];
                        let noOfColumn = 16;
                        let rowSpansOfColumns = [];
                        for (let i = 0; i < noOfColumn; i++) {
                            rowSpansOfColumns.push([]);
                        }
                        // only last header row is included, hence need to add back first 3 rows.
                        let noOfExtraHeaderRow = 3;
                        pdfDocument.content[1].table.headerRows = noOfExtraHeaderRow + 1;
                        // below loop convert html header to json, pad all covered by col/row span with {}
                        // i.e. there are 16 object in each row.
                        for (let i = 1; i <= noOfExtraHeaderRow; i++) {
                            let headerRow = [];
                            let colIdx = 0;
                            while (colIdx < rowSpansOfColumns.length && rowSpansOfColumns[colIdx].includes(i)) {
                                headerRow.push({});
                                colIdx++
                            }
    
                            $('#dataTable').find("thead>tr:nth-child(" + i + ")>th").each(
                                function(index, element) {
                                    let colSpan = parseInt(element.getAttribute("colSpan"));
                                    let rowSpan = parseInt(element.getAttribute("rowSpan"));
                                    if (rowSpan > 1) {
                                        for (let col = colIdx; col < colIdx + colSpan; col++) {
                                            for (let row = i + 1; row < i + rowSpan; row++) {
                                                rowSpansOfColumns[col].push(row);
                                            }
                                        }
                                    }
                                    headerRow.push({
                                        text: element.innerHTML,
                                        style: "tableHeader",
                                        colSpan: colSpan,
                                        rowSpan: rowSpan
                                    });
                                    colIdx++
                                    for (let j = 0; j < colSpan - 1; j++) {
                                        headerRow.push({});
                                        colIdx++
                                    }
                                    while (colIdx < rowSpansOfColumns.length && rowSpansOfColumns[colIdx].includes(i)) {
                                        headerRow.push({});
                                        colIdx++
                                    }
                                });
                            headerRows.push(headerRow);
                        }
                        pdfDocument.content[1].table.body = headerRows.concat(pdfDocument.content[1].table.body)
                    }
                }]
            });
        });
    </script>
    
    <table id="dataTable" cellspacing="0" width="auto">
      <thead>
        <tr>
          <th colspan="16" style="border-left : none; border-right: none;"></th>
        </tr>
        <tr class="text-center">
          <th rowspan="3">Date</th>
          <th colspan="14">Number of Students</th>
          <th rowspan="3">Grand Total</th>
        </tr>
        <tr class="text-center">
          <th colspan="7">Class A</th>
          <th colspan="7">Class B</th>
        </tr>
    
        <tr class="text-center">
          <th style="width:6.2%;">Eng</th>
          <th style="width:6.2%;">BM</th>
          <th style="width:6.2%;">Man</th>
          <th style="width:6.2%;">SC</th>
          <th style="width:6.2%;">PHY</th>
          <th style="width:6.2%;">CHE</th>
          <th style="width:6.2%;">Sum</th>
          <th style="width:6.2%;">Eng</th>
          <th style="width:6.2%;">BM</th>
          <th style="width:6.2%;">Man</th>
          <th style="width:6.2%;">SC</th>
          <th style="width:6.2%;">PHY</th>
          <th style="width:6.2%;">CHE</th>
          <th style="width:6.2%;">Sum</th>
        </tr>
      </thead>
    
      <tbody>
        <tr>
          <td>2020/10/11</td>
          <td>50</td>
          <td>60</td>
          <td>70</td>
          <td>80</td>
          <td>90</td>
          <td>85</td>
          <td>25</td>
          <td>50</td>
          <td>60</td>
          <td>70</td>
          <td>80</td>
          <td>90</td>
          <td>85</td>
          <td>25</td>
          <td>100</td>
        </tr>
        <tr>
          <td>2020/11/11</td>
          <td>53</td>
          <td>63</td>
          <td>73</td>
          <td>83</td>
          <td>93</td>
          <td>83</td>
          <td>23</td>
          <td>53</td>
          <td>63</td>
          <td>73</td>
          <td>83</td>
          <td>93</td>
          <td>83</td>
          <td>26</td>
          <td>100</td>
        </tr>
      </tbody>
    </table>

    Image of exported report

    pdf_withRowSpanHeader

    References:
    pdfmake documentation
    pdfmake playground