javascripthtmlcsscss-paged-media

How can I make HTML table footer take available space on each page when printing on multiple pages?


I like to print a very long table on multiple pages. On each page I have a fixed header, multiple entries in body section with random heights, and a footer. Random entries should not break across pages and the footer should use the available remaining space. I created a sample code with a print button. When I click on the print button, I want the orange box to start right after its preceding red box and take all the space to the bottom of the page. Any ideas how to fix the issue?

const main = document.querySelectorAll(".table-body")[0];
const newDiv = document.createElement("div");
for (let i = 0; i < 30; i++) {
  newDiv.innerHTML += `<div class="box" style="height: ${getRandomIntInclusive(120,250)}px">Line ${i+1} </div>`
}
main.appendChild(newDiv);

document.getElementById("print").addEventListener("click", () => {
  window.print();
});

function getRandomIntInclusive(min, max) {
  min = Math.ceil(min);
  max = Math.floor(max);
  return Math.floor(Math.random() * (max - min + 1)) + min;
}
table {
  width: 100%;
  border: 1px solid blue;
  padding: 10px;
}

.table-body {
  border: 1px dashed green;
  padding: 10px;
}

.box {
  border: 1px solid red;
  page-break-inside: avoid;
}

table > tfoot > tr > td {
  border: 2px solid orange;
}

.table-footer {
  border: 1px solid black;
  min-height: 100px;
  vertical-align: top;
}

@media print {
  @page {
      size: letter;
  }
  
  #print {
    display: none;
  }
}
<button id='print'>Print</button>
<table>
  <thead>
    <tr>
      <th>
        Header
      </th>
    <tr>
  </thead>
  <tbody>
    <tr>
      <td>
          <div class='table-body'></div>
      </td>
    <tr>
  </tbody>
  <tfoot>
    <tr>
      <td>
        <div class='table-footer'>Footer</div>
      </td>
    <tr>
  </tfoot>
</table>


Solution

  • I was not able to solve the issue using a table so I ended up solving my problem using divs. I kept track of the random height of divs on each page and assigned the remaining space to the footer. My footers have some min-height limitation to ensure that I have a footer on each page. The content of each page is placed in another div to give me more control on styling each page; e.g: page-break-after and overflow. The solution is not ideal and hoping that someone comes with a better approach but it satisfies my need for the moment. Below you can find the updated code:

    const main = document.getElementById("main");
    const newDiv = document.createElement("div");
    const paperHeight = 11 // paper height in inch
    const paperMargin = 1; // sum of top and bottom paper margins in inch
    const overflow = 0.11; // used to protect page overflow to the next page
    const dpi = 96; // Display dots per inch (DPI)
    const maxHeight = (paperHeight - paperMargin) * dpi; // max page height
    const footerMinHeight = 40; // Min height of footer
    const headerHeight = 100; // Height of header in px
    const numOfItems = 20; // Number of items
    
    let j = 0;
    let items = '';
    let pageCount = 1;
    do {
      items += `<div class="page" style="max-height: ${paperHeight - paperMargin - overflow}in"><div class="box header" style="height: ${headerHeight}px">Page ${pageCount} - Header</div>`;
      let pageHeight = headerHeight;
      do {
        const elementHeight = getRandomIntInclusive(60, 250);
        if (elementHeight + pageHeight > maxHeight - footerMinHeight || j === numOfItems) {
          items += `<div class="box footer" style="height: ${maxHeight - pageHeight}px">Footer</div></div>`;
          break;
        } else {
          pageHeight += elementHeight;
          j++;
        }
        items += `<div class="box" style="height: ${elementHeight}px">Item ${j} </div>`;
      } while (j <= numOfItems)
      pageCount++;
    } while (j < numOfItems)
    newDiv.innerHTML = items;
    main.appendChild(newDiv);
    
    
    
    document.getElementById("print").addEventListener("click", () => {
      window.print();
    });
    
    function getRandomIntInclusive(min, max) {
      min = Math.ceil(min);
      max = Math.floor(max);
      return Math.floor(Math.random() * (max - min + 1)) + min;
    }
    * {
      box-sizing: border-box;
    }
    
    .page {
      border: 1px dashed green;
      page-break-after: always;
      overflow: hidden;
    }
    
    .box {
      border-bottom: 1px solid red;
    }
    
    .header {
      background: #e6ffe6
    }
    
    .footer {
      background: #fdfed6;
    }
    
    @media print {
      @page {
        size: letter;
        margin: 0.5in;
      }
      #print {
        display: none;
      }
    }
    <button id='print'>Print</button>
    <div id='main'></div>