Rectangle 27 41

UPDATE 2017-03-22: Repeating table headers have finally been implemented in Chrome! (Actually, I think they were implemented some time ago.) That means you probably don't need this solution anymore; just put your column headers in a <thead> tag and you should be all set. Use the solution below only if: a) you encounter show-stopping bugs in Chrome's implementation, b) you need the "bonus features", or c) you need to support some oddball browser that still doesn't support repeating headers.

The code below demonstrates the best method I've found for multi-page table printing. It has the following features:

  • No need to worry about paper size or how many rows will fit-- the browser handles everything automatically
  • If a page break occurs near the top of the table, it won't leave behind an orphaned caption or column headers with no data attached (a problem which isn't limited to just Chrome)
  • Works in Chrome! (and other Webkit-based browsers like Safari and Opera)

... and the following known limitations:

<thead>
<tfoot>
<caption>
  • Table can't have top or bottom margin; to add white space above or below the table, insert an empty div and set a bottom margin on it
  • Any CSS size values that affect height (including border-width and line-height) must be in px

Column widths can't be set by applying width values to individual table cells; you should either let cell content automatically determine column width, or use <col>s to set specific widths if needed

Table can't (easily) be changed dynamically after the JS has run

<!DOCTYPE html>
<html>
  <body>
    <table class="print t1"> <!-- Delete "t1" class to remove row numbers. -->
      <caption>Print-Friendly Table</caption>
      <thead>
        <tr>
          <th></th>
          <th>Column Header</th>
          <th>Column Header</th>
          <th>Multi-Line<br/>Column<br/>Header</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td></td>
          <td>data</td>
          <td>Multiple<br/>lines of<br/>data</td>
          <td>data</td>
        </tr>
      </tbody>
    </table>
  </body>
</html>

<style>
  /* THE FOLLOWING CSS IS REQUIRED AND SHOULD NOT BE MODIFIED. */
    div.fauxRow {
      display: inline-block;
      vertical-align: top;
      width: 100%;
      page-break-inside: avoid;
    }
    table.fauxRow {border-spacing: 0;}
    table.fauxRow > tbody > tr > td {
      padding: 0;
      overflow: hidden;
    }
    table.fauxRow > tbody > tr > td > table.print {
      display: inline-table;
      vertical-align: top;
    }
    table.fauxRow > tbody > tr > td > table.print > caption {caption-side: top;}
    .noBreak {
      float: right;
      width: 100%;
      visibility: hidden;
    }
    .noBreak:before, .noBreak:after {
      display: block;
      content: "";
    }
    .noBreak:after {margin-top: -594mm;}
    .noBreak > div {
      display: inline-block;
      vertical-align: top;
      width:100%;
      page-break-inside: avoid;
    }
    table.print > tbody > tr {page-break-inside: avoid;}
    table.print > tbody > .metricsRow > td {border-top: none !important;}

  /* THE FOLLOWING CSS IS REQUIRED, but the values may be adjusted. */
    /* NOTE: All size values that can affect an element's height should use the px unit! */
    table.fauxRow, table.print {
      font-size: 16px;
      line-height: 20px;
    }

  /* THE FOLLOWING CSS IS OPTIONAL. */
    body {counter-reset: t1;} /* Delete to remove row numbers. */
    .noBreak .t1 > tbody > tr > :first-child:before {counter-increment: none;} /* Delete to remove row numbers. */
    .t1 > tbody > tr > :first-child:before { /* Delete to remove row numbers. */
      display: block;
      text-align: right;
      counter-increment: t1 1;
      content: counter(t1);
    }
    table.fauxRow, table.print {
      font-family: Tahoma, Verdana, Georgia; /* Try to use fonts that don't get bigger when printed. */
      margin: 0 auto 0 auto; /* Delete if you don't want table to be centered. */
    }
    table.print {border-spacing: 0;}
    table.print > * > tr > * {
      border-right: 2px solid black;
      border-bottom: 2px solid black;
      padding: 0 5px 0 5px;
    }
    table.print > * > :first-child > * {border-top: 2px solid black;}
    table.print > thead ~ * > :first-child > *, table.print > tbody ~ * > :first-child > * {border-top: none;}
    table.print > * > tr > :first-child {border-left: 2px solid black;}
    table.print > thead {vertical-align: bottom;}
    table.print > thead > .borderRow > th {border-bottom: none;}
    table.print > tbody {vertical-align: top;}
    table.print > caption {font-weight: bold;}
</style>

<script>
  (function() { // THIS FUNCTION IS NOT REQUIRED. It just adds table rows for testing purposes.
    var rowCount = 100
      , tbod = document.querySelector("table.print > tbody")
      , row = tbod.rows[0];
    for(; --rowCount; tbod.appendChild(row.cloneNode(true)));
  })();

  (function() { // THIS FUNCTION IS REQUIRED.
    if(/Firefox|MSIE |Trident/i.test(navigator.userAgent))
      var formatForPrint = function(table) {
        var noBreak = document.createElement("div")
          , noBreakTable = noBreak.appendChild(document.createElement("div")).appendChild(table.cloneNode())
          , tableParent = table.parentNode
          , tableParts = table.children
          , partCount = tableParts.length
          , partNum = 0
          , cell = table.querySelector("tbody > tr > td");
        noBreak.className = "noBreak";
        for(; partNum < partCount; partNum++) {
          if(!/tbody/i.test(tableParts[partNum].tagName))
            noBreakTable.appendChild(tableParts[partNum].cloneNode(true));
        }
        if(cell) {
          noBreakTable.appendChild(cell.parentNode.parentNode.cloneNode()).appendChild(cell.parentNode.cloneNode(true));
          if(!table.tHead) {
            var borderRow = document.createElement("tr");
            borderRow.appendChild(document.createElement("th")).colSpan="1000";
            borderRow.className = "borderRow";
            table.insertBefore(document.createElement("thead"), table.tBodies[0]).appendChild(borderRow);
          }
        }
        tableParent.insertBefore(document.createElement("div"), table).style.paddingTop = ".009px";
        tableParent.insertBefore(noBreak, table);
      };
    else
      var formatForPrint = function(table) {
        var tableParent = table.parentNode
          , cell = table.querySelector("tbody > tr > td");
        if(cell) {
          var topFauxRow = document.createElement("table")
            , fauxRowTable = topFauxRow.insertRow(0).insertCell(0).appendChild(table.cloneNode())
            , colgroup = fauxRowTable.appendChild(document.createElement("colgroup"))
            , headerHider = document.createElement("div")
            , metricsRow = document.createElement("tr")
            , cells = cell.parentNode.cells
            , cellNum = cells.length
            , colCount = 0
            , tbods = table.tBodies
            , tbodCount = tbods.length
            , tbodNum = 0
            , tbod = tbods[0];
          for(; cellNum--; colCount += cells[cellNum].colSpan);
          for(cellNum = colCount; cellNum--; metricsRow.appendChild(document.createElement("td")).style.padding = 0);
          cells = metricsRow.cells;
          tbod.insertBefore(metricsRow, tbod.firstChild);
          for(; ++cellNum < colCount; colgroup.appendChild(document.createElement("col")).style.width = cells[cellNum].offsetWidth + "px");
          var borderWidth = metricsRow.offsetHeight;
          metricsRow.className = "metricsRow";
          borderWidth -= metricsRow.offsetHeight;
          tbod.removeChild(metricsRow);
          tableParent.insertBefore(topFauxRow, table).className = "fauxRow";
          if(table.tHead)
            fauxRowTable.appendChild(table.tHead);
          var fauxRow = topFauxRow.cloneNode(true)
            , fauxRowCell = fauxRow.rows[0].cells[0];
          fauxRowCell.insertBefore(headerHider, fauxRowCell.firstChild).style.marginBottom = -fauxRowTable.offsetHeight - borderWidth + "px";
          if(table.caption)
            fauxRowTable.insertBefore(table.caption, fauxRowTable.firstChild);
          if(tbod.rows[0])
            fauxRowTable.appendChild(tbod.cloneNode()).appendChild(tbod.rows[0]);
          for(; tbodNum < tbodCount; tbodNum++) {
            tbod = tbods[tbodNum];
            rows = tbod.rows;
            for(; rows[0]; tableParent.insertBefore(fauxRow.cloneNode(true), table).rows[0].cells[0].children[1].appendChild(tbod.cloneNode()).appendChild(rows[0]));
          }
          tableParent.removeChild(table);
        }
        else
          tableParent.insertBefore(document.createElement("div"), table).appendChild(table).parentNode.className="fauxRow";
      };
    var tables = document.body.querySelectorAll("table.print")
      , tableNum = tables.length;
    for(; tableNum--; formatForPrint(tables[tableNum]));
  })();
</script>

HOW IT WORKS (If you don't care, read no further; everything you need is above.)

Per @Kingsolmn's request, below is an explanation of how this solution works. It doesn't cover the JavaScript, which isn't strictly required (though it does make this technique much easier to use). Instead, it focuses on the generated HTML structures and associated CSS, which is where the real magic happens.

<table>
  <tr><th>ColumnA</th><th>ColumnB</th></tr>
  <tr><td>row1</td><td>row1</td></tr>
  <tr><td>row2</td><td>row2</td></tr>
  <tr><td>row3</td><td>row3</td></tr>
</table>

(To save space, I gave it only 3 data rows; obviously, a multipage table would usually have more)

The first thing we need to do is split the table into a series of smaller tables, each with its own copy of the column headers. I call these smaller tables fauxRows.

<table> <!-- fauxRow -->
  <tr><th>ColumnA</th><th>ColumnB</th></tr>
  <tr><td>row1</td><td>row1</td></tr>
</table>

<table> <!-- fauxRow -->
  <tr><th>ColumnA</th><th>ColumnB</th></tr>
  <tr><td>row2</td><td>row2</td></tr>
</table>

<table> <!-- fauxRow -->
  <tr><th>ColumnA</th><th>ColumnB</th></tr>
  <tr><td>row3</td><td>row3</td></tr>
</table>

The fauxRows are essentially clones of the original table, but with only one data row apiece. (If your table has a caption, though, only the top fauxRow should include it.)

Next we need to make the fauxRows unbreakable. What does that mean? (Pay attention-- this is probably the most important concept in page break handling.) "Unbreakable" is the term I use to describe a block of content that can't be split between two pages*. When a page break occurs within the space occupied by such a block, the whole block moves to the next page. (Note that I'm using the word "block" informally here; I'm not referring specifically to block level elements.) This behavior has an interesting side-effect that we'll make use of later on: it can expose content that was initially hidden due to layering or overflow.

We can make the fauxRows unbreakable by applying either of the following CSS declarations:

page-break-inside: avoid;
display: inline-table;

I usually use both, because the first is made for this purpose and the second works in older/noncompliant browsers. In this case, though, for simplicity's sake, I'll stick to the page-break property. Note that you will not see any change in the table's appearance after adding this property.

<table style="page-break-inside: avoid;"> <!-- fauxRow -->
      <tr><th>ColumnA</th><th>ColumnB</th></tr>
      <tr><td>row1</td><td>row1</td></tr>
    </table>
    
    <table style="page-break-inside: avoid;"> <!-- fauxRow -->
      <tr><th>ColumnA</th><th>ColumnB</th></tr>
      <tr><td>row2</td><td>row2</td></tr>
    </table>
    
    <table style="page-break-inside: avoid;"> <!-- fauxRow -->
      <tr><th>ColumnA</th><th>ColumnB</th></tr>
      <tr><td>row3</td><td>row3</td></tr>
    </table>

Now that the fauxRows are unbreakable, if a page break occurs within a data row, it will shift to the next page along with its attached header row. So the next page will always have column headers at the top, which is our goal. But the table looks very strange now with all the extra header rows. To make it look like a normal table again, we need to hide the extra headers in such a way that they'll appear only when needed.

What we're going to do is put each fauxRow in a container element with overflow: hidden; and then shift it upward so that the headers get clipped by the top of the container. This will also move the data rows back together so that they appear contiguous.

Your first instinct might be to use divs for the containers, but we're going to use the cells of a parent table instead. I'll explain why later, but for now, let's just add the code. (Once again, this will not affect the table's appearance.)

Notice the CSS above the table markup. I added it for two reasons: first, it prevents the parent table from adding white space between the fauxRows; second, it makes the header height predictable, which is necessary since we're not using JavaScript to calculate it dynamically.

Now we just need to shift the fauxRows upward, which we'll do with negative margins. But it's not as simple as you might think. If we add a negative margin directly to a fauxRow, it will remain in effect when the fauxRow gets bumped to the next page, causing the headers to get clipped by the top of the page. We need a way to leave the negative margin behind.

To accomplish this, we'll insert an empty div above each fauxRow after the first and add the negative margin to it. (The first fauxRow is skipped because its headers should always be visible.) Since the margin is on a separate element, it won't follow the fauxRow to the next page, and the headers won't be clipped. I call these empty divs headerHiders.

table {
  border-spacing: 0;
  line-height: 20px;
}
th, td {
  padding-top: 0;
  padding-bottom: 0;
}
<table> <!-- parent table -->
  <tr>
    <td style="overflow: hidden;">

      <table style="page-break-inside: avoid;"> <!-- fauxRow -->
        <tr><th>ColumnA</th><th>ColumnB</th></tr>
        <tr><td>row1</td><td>row1</td></tr>
      </table>

    </td>
  </tr>
  <tr>
    <td style="overflow: hidden;">

      <div style="margin-bottom: -20px;"></div> <!-- headerHider -->

      <table style="page-break-inside: avoid;"> <!-- fauxRow -->
        <tr><th>ColumnA</th><th>ColumnB</th></tr>
        <tr><td>row2</td><td>row2</td></tr>
      </table>

    </td>
  </tr>
  <tr>
    <td style="overflow: hidden;">

      <div style="margin-bottom: -20px;"></div> <!-- headerHider -->

      <table style="page-break-inside: avoid;"> <!-- fauxRow -->
        <tr><th>ColumnA</th><th>ColumnB</th></tr>
        <tr><td>row3</td><td>row3</td></tr>
      </table>

    </td>
  </tr>
</table>

That's it, we're done! On screen, the table should now look normal, with only one set of column headers at the top. In print, it should now have running headers.

If you were wondering why we used a parent table instead of a bunch of container divs, it's because Chrome/webkit has a bug that causes a div-enclosed unbreakable block to carry its container to the next page with it. Since the headerHider is also in the container, it won't get left behind like it's supposed to, which leads to clipped headers. This bug only happens if the unbreakable block is the topmost element in the div with a non-zero height.

I did discover a workaround while writing this tutorial: you just have to explicitly set height: 0; on the headerHider and give it an empty child div with a non-zero height. Then you can use a div container. I still prefer to use a parent table, though, because it has been more thoroughly tested, and it salvages the semantics to some extent by tying the fauxRows back together into a single table.

EDIT: I just realized that the JavaScript-generated markup is slightly different in that it puts each fauxRow in a separate container table, and assigns the "fauxRow" className to it (the container). This would be required for footer support, which I intended to add someday but never did. If I were to update the JS, I might consider switching to div containers since my semantic justification for using a table doesn't apply.

* There is one situation in which an unbreakable block can be split between two pages: when it exceeds the height of the printable area. You should try to avoid this scenario; you're essentially asking the browser to do the impossible, and it can have some very strange effects on the output.

This should be the accepted answer, it just works!

@DoctorDestructo i tried to implement this solution, it really avoided the page breaking inside a row, but i ended up with the columns all messed up, like this image : s11.postimg.org/uyhoktapv/error.jpg Can you help me to fix that ? Thanks in advance !

@delphirules Make sure no width values are being applied to any <td> or <th> elements in the table. There are only two valid ways to set column width: 1) let the cell content set it automatically, or 2) use <col> elements. If that's not the problem, then I'd probably have to see your table html (prior to running the required js function) to diagnose it.

@delphirules If your table has top padding applied to it, that could cause the behavior you describe. The best workaround would be to get rid of the top padding and instead add a bottom margin to the element above the table. (In fact, when it comes to printing, it's usually best to use bottom margins for vertical white space).

@Kingsolmn yw! Here's a simple explanation of how it works: The js splits your table into a series of one-row subtables, each with its own set of column headers. A negative margin partially retracts each subtable into its container, hiding the headers. The margin is implemented in such a way that it doesn't carry over if a subtable gets bumped to the next page by a page break. So, the first subtable on each page will have visible headers.

html - Having Google Chrome repeat table headers on printed pages - St...

html google-chrome html-table
Rectangle 27 41

UPDATE 2017-03-22: Repeating table headers have finally been implemented in Chrome! (Actually, I think they were implemented some time ago.) That means you probably don't need this solution anymore; just put your column headers in a <thead> tag and you should be all set. Use the solution below only if: a) you encounter show-stopping bugs in Chrome's implementation, b) you need the "bonus features", or c) you need to support some oddball browser that still doesn't support repeating headers.

The code below demonstrates the best method I've found for multi-page table printing. It has the following features:

  • No need to worry about paper size or how many rows will fit-- the browser handles everything automatically
  • If a page break occurs near the top of the table, it won't leave behind an orphaned caption or column headers with no data attached (a problem which isn't limited to just Chrome)
  • Works in Chrome! (and other Webkit-based browsers like Safari and Opera)

... and the following known limitations:

<thead>
<tfoot>
<caption>
  • Table can't have top or bottom margin; to add white space above or below the table, insert an empty div and set a bottom margin on it
  • Any CSS size values that affect height (including border-width and line-height) must be in px

Column widths can't be set by applying width values to individual table cells; you should either let cell content automatically determine column width, or use <col>s to set specific widths if needed

Table can't (easily) be changed dynamically after the JS has run

<!DOCTYPE html>
<html>
  <body>
    <table class="print t1"> <!-- Delete "t1" class to remove row numbers. -->
      <caption>Print-Friendly Table</caption>
      <thead>
        <tr>
          <th></th>
          <th>Column Header</th>
          <th>Column Header</th>
          <th>Multi-Line<br/>Column<br/>Header</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td></td>
          <td>data</td>
          <td>Multiple<br/>lines of<br/>data</td>
          <td>data</td>
        </tr>
      </tbody>
    </table>
  </body>
</html>

<style>
  /* THE FOLLOWING CSS IS REQUIRED AND SHOULD NOT BE MODIFIED. */
    div.fauxRow {
      display: inline-block;
      vertical-align: top;
      width: 100%;
      page-break-inside: avoid;
    }
    table.fauxRow {border-spacing: 0;}
    table.fauxRow > tbody > tr > td {
      padding: 0;
      overflow: hidden;
    }
    table.fauxRow > tbody > tr > td > table.print {
      display: inline-table;
      vertical-align: top;
    }
    table.fauxRow > tbody > tr > td > table.print > caption {caption-side: top;}
    .noBreak {
      float: right;
      width: 100%;
      visibility: hidden;
    }
    .noBreak:before, .noBreak:after {
      display: block;
      content: "";
    }
    .noBreak:after {margin-top: -594mm;}
    .noBreak > div {
      display: inline-block;
      vertical-align: top;
      width:100%;
      page-break-inside: avoid;
    }
    table.print > tbody > tr {page-break-inside: avoid;}
    table.print > tbody > .metricsRow > td {border-top: none !important;}

  /* THE FOLLOWING CSS IS REQUIRED, but the values may be adjusted. */
    /* NOTE: All size values that can affect an element's height should use the px unit! */
    table.fauxRow, table.print {
      font-size: 16px;
      line-height: 20px;
    }

  /* THE FOLLOWING CSS IS OPTIONAL. */
    body {counter-reset: t1;} /* Delete to remove row numbers. */
    .noBreak .t1 > tbody > tr > :first-child:before {counter-increment: none;} /* Delete to remove row numbers. */
    .t1 > tbody > tr > :first-child:before { /* Delete to remove row numbers. */
      display: block;
      text-align: right;
      counter-increment: t1 1;
      content: counter(t1);
    }
    table.fauxRow, table.print {
      font-family: Tahoma, Verdana, Georgia; /* Try to use fonts that don't get bigger when printed. */
      margin: 0 auto 0 auto; /* Delete if you don't want table to be centered. */
    }
    table.print {border-spacing: 0;}
    table.print > * > tr > * {
      border-right: 2px solid black;
      border-bottom: 2px solid black;
      padding: 0 5px 0 5px;
    }
    table.print > * > :first-child > * {border-top: 2px solid black;}
    table.print > thead ~ * > :first-child > *, table.print > tbody ~ * > :first-child > * {border-top: none;}
    table.print > * > tr > :first-child {border-left: 2px solid black;}
    table.print > thead {vertical-align: bottom;}
    table.print > thead > .borderRow > th {border-bottom: none;}
    table.print > tbody {vertical-align: top;}
    table.print > caption {font-weight: bold;}
</style>

<script>
  (function() { // THIS FUNCTION IS NOT REQUIRED. It just adds table rows for testing purposes.
    var rowCount = 100
      , tbod = document.querySelector("table.print > tbody")
      , row = tbod.rows[0];
    for(; --rowCount; tbod.appendChild(row.cloneNode(true)));
  })();

  (function() { // THIS FUNCTION IS REQUIRED.
    if(/Firefox|MSIE |Trident/i.test(navigator.userAgent))
      var formatForPrint = function(table) {
        var noBreak = document.createElement("div")
          , noBreakTable = noBreak.appendChild(document.createElement("div")).appendChild(table.cloneNode())
          , tableParent = table.parentNode
          , tableParts = table.children
          , partCount = tableParts.length
          , partNum = 0
          , cell = table.querySelector("tbody > tr > td");
        noBreak.className = "noBreak";
        for(; partNum < partCount; partNum++) {
          if(!/tbody/i.test(tableParts[partNum].tagName))
            noBreakTable.appendChild(tableParts[partNum].cloneNode(true));
        }
        if(cell) {
          noBreakTable.appendChild(cell.parentNode.parentNode.cloneNode()).appendChild(cell.parentNode.cloneNode(true));
          if(!table.tHead) {
            var borderRow = document.createElement("tr");
            borderRow.appendChild(document.createElement("th")).colSpan="1000";
            borderRow.className = "borderRow";
            table.insertBefore(document.createElement("thead"), table.tBodies[0]).appendChild(borderRow);
          }
        }
        tableParent.insertBefore(document.createElement("div"), table).style.paddingTop = ".009px";
        tableParent.insertBefore(noBreak, table);
      };
    else
      var formatForPrint = function(table) {
        var tableParent = table.parentNode
          , cell = table.querySelector("tbody > tr > td");
        if(cell) {
          var topFauxRow = document.createElement("table")
            , fauxRowTable = topFauxRow.insertRow(0).insertCell(0).appendChild(table.cloneNode())
            , colgroup = fauxRowTable.appendChild(document.createElement("colgroup"))
            , headerHider = document.createElement("div")
            , metricsRow = document.createElement("tr")
            , cells = cell.parentNode.cells
            , cellNum = cells.length
            , colCount = 0
            , tbods = table.tBodies
            , tbodCount = tbods.length
            , tbodNum = 0
            , tbod = tbods[0];
          for(; cellNum--; colCount += cells[cellNum].colSpan);
          for(cellNum = colCount; cellNum--; metricsRow.appendChild(document.createElement("td")).style.padding = 0);
          cells = metricsRow.cells;
          tbod.insertBefore(metricsRow, tbod.firstChild);
          for(; ++cellNum < colCount; colgroup.appendChild(document.createElement("col")).style.width = cells[cellNum].offsetWidth + "px");
          var borderWidth = metricsRow.offsetHeight;
          metricsRow.className = "metricsRow";
          borderWidth -= metricsRow.offsetHeight;
          tbod.removeChild(metricsRow);
          tableParent.insertBefore(topFauxRow, table).className = "fauxRow";
          if(table.tHead)
            fauxRowTable.appendChild(table.tHead);
          var fauxRow = topFauxRow.cloneNode(true)
            , fauxRowCell = fauxRow.rows[0].cells[0];
          fauxRowCell.insertBefore(headerHider, fauxRowCell.firstChild).style.marginBottom = -fauxRowTable.offsetHeight - borderWidth + "px";
          if(table.caption)
            fauxRowTable.insertBefore(table.caption, fauxRowTable.firstChild);
          if(tbod.rows[0])
            fauxRowTable.appendChild(tbod.cloneNode()).appendChild(tbod.rows[0]);
          for(; tbodNum < tbodCount; tbodNum++) {
            tbod = tbods[tbodNum];
            rows = tbod.rows;
            for(; rows[0]; tableParent.insertBefore(fauxRow.cloneNode(true), table).rows[0].cells[0].children[1].appendChild(tbod.cloneNode()).appendChild(rows[0]));
          }
          tableParent.removeChild(table);
        }
        else
          tableParent.insertBefore(document.createElement("div"), table).appendChild(table).parentNode.className="fauxRow";
      };
    var tables = document.body.querySelectorAll("table.print")
      , tableNum = tables.length;
    for(; tableNum--; formatForPrint(tables[tableNum]));
  })();
</script>

HOW IT WORKS (If you don't care, read no further; everything you need is above.)

Per @Kingsolmn's request, below is an explanation of how this solution works. It doesn't cover the JavaScript, which isn't strictly required (though it does make this technique much easier to use). Instead, it focuses on the generated HTML structures and associated CSS, which is where the real magic happens.

<table>
  <tr><th>ColumnA</th><th>ColumnB</th></tr>
  <tr><td>row1</td><td>row1</td></tr>
  <tr><td>row2</td><td>row2</td></tr>
  <tr><td>row3</td><td>row3</td></tr>
</table>

(To save space, I gave it only 3 data rows; obviously, a multipage table would usually have more)

The first thing we need to do is split the table into a series of smaller tables, each with its own copy of the column headers. I call these smaller tables fauxRows.

<table> <!-- fauxRow -->
  <tr><th>ColumnA</th><th>ColumnB</th></tr>
  <tr><td>row1</td><td>row1</td></tr>
</table>

<table> <!-- fauxRow -->
  <tr><th>ColumnA</th><th>ColumnB</th></tr>
  <tr><td>row2</td><td>row2</td></tr>
</table>

<table> <!-- fauxRow -->
  <tr><th>ColumnA</th><th>ColumnB</th></tr>
  <tr><td>row3</td><td>row3</td></tr>
</table>

The fauxRows are essentially clones of the original table, but with only one data row apiece. (If your table has a caption, though, only the top fauxRow should include it.)

Next we need to make the fauxRows unbreakable. What does that mean? (Pay attention-- this is probably the most important concept in page break handling.) "Unbreakable" is the term I use to describe a block of content that can't be split between two pages*. When a page break occurs within the space occupied by such a block, the whole block moves to the next page. (Note that I'm using the word "block" informally here; I'm not referring specifically to block level elements.) This behavior has an interesting side-effect that we'll make use of later on: it can expose content that was initially hidden due to layering or overflow.

We can make the fauxRows unbreakable by applying either of the following CSS declarations:

page-break-inside: avoid;
display: inline-table;

I usually use both, because the first is made for this purpose and the second works in older/noncompliant browsers. In this case, though, for simplicity's sake, I'll stick to the page-break property. Note that you will not see any change in the table's appearance after adding this property.

<table style="page-break-inside: avoid;"> <!-- fauxRow -->
      <tr><th>ColumnA</th><th>ColumnB</th></tr>
      <tr><td>row1</td><td>row1</td></tr>
    </table>
    
    <table style="page-break-inside: avoid;"> <!-- fauxRow -->
      <tr><th>ColumnA</th><th>ColumnB</th></tr>
      <tr><td>row2</td><td>row2</td></tr>
    </table>
    
    <table style="page-break-inside: avoid;"> <!-- fauxRow -->
      <tr><th>ColumnA</th><th>ColumnB</th></tr>
      <tr><td>row3</td><td>row3</td></tr>
    </table>

Now that the fauxRows are unbreakable, if a page break occurs within a data row, it will shift to the next page along with its attached header row. So the next page will always have column headers at the top, which is our goal. But the table looks very strange now with all the extra header rows. To make it look like a normal table again, we need to hide the extra headers in such a way that they'll appear only when needed.

What we're going to do is put each fauxRow in a container element with overflow: hidden; and then shift it upward so that the headers get clipped by the top of the container. This will also move the data rows back together so that they appear contiguous.

Your first instinct might be to use divs for the containers, but we're going to use the cells of a parent table instead. I'll explain why later, but for now, let's just add the code. (Once again, this will not affect the table's appearance.)

Notice the CSS above the table markup. I added it for two reasons: first, it prevents the parent table from adding white space between the fauxRows; second, it makes the header height predictable, which is necessary since we're not using JavaScript to calculate it dynamically.

Now we just need to shift the fauxRows upward, which we'll do with negative margins. But it's not as simple as you might think. If we add a negative margin directly to a fauxRow, it will remain in effect when the fauxRow gets bumped to the next page, causing the headers to get clipped by the top of the page. We need a way to leave the negative margin behind.

To accomplish this, we'll insert an empty div above each fauxRow after the first and add the negative margin to it. (The first fauxRow is skipped because its headers should always be visible.) Since the margin is on a separate element, it won't follow the fauxRow to the next page, and the headers won't be clipped. I call these empty divs headerHiders.

table {
  border-spacing: 0;
  line-height: 20px;
}
th, td {
  padding-top: 0;
  padding-bottom: 0;
}
<table> <!-- parent table -->
  <tr>
    <td style="overflow: hidden;">

      <table style="page-break-inside: avoid;"> <!-- fauxRow -->
        <tr><th>ColumnA</th><th>ColumnB</th></tr>
        <tr><td>row1</td><td>row1</td></tr>
      </table>

    </td>
  </tr>
  <tr>
    <td style="overflow: hidden;">

      <div style="margin-bottom: -20px;"></div> <!-- headerHider -->

      <table style="page-break-inside: avoid;"> <!-- fauxRow -->
        <tr><th>ColumnA</th><th>ColumnB</th></tr>
        <tr><td>row2</td><td>row2</td></tr>
      </table>

    </td>
  </tr>
  <tr>
    <td style="overflow: hidden;">

      <div style="margin-bottom: -20px;"></div> <!-- headerHider -->

      <table style="page-break-inside: avoid;"> <!-- fauxRow -->
        <tr><th>ColumnA</th><th>ColumnB</th></tr>
        <tr><td>row3</td><td>row3</td></tr>
      </table>

    </td>
  </tr>
</table>

That's it, we're done! On screen, the table should now look normal, with only one set of column headers at the top. In print, it should now have running headers.

If you were wondering why we used a parent table instead of a bunch of container divs, it's because Chrome/webkit has a bug that causes a div-enclosed unbreakable block to carry its container to the next page with it. Since the headerHider is also in the container, it won't get left behind like it's supposed to, which leads to clipped headers. This bug only happens if the unbreakable block is the topmost element in the div with a non-zero height.

I did discover a workaround while writing this tutorial: you just have to explicitly set height: 0; on the headerHider and give it an empty child div with a non-zero height. Then you can use a div container. I still prefer to use a parent table, though, because it has been more thoroughly tested, and it salvages the semantics to some extent by tying the fauxRows back together into a single table.

EDIT: I just realized that the JavaScript-generated markup is slightly different in that it puts each fauxRow in a separate container table, and assigns the "fauxRow" className to it (the container). This would be required for footer support, which I intended to add someday but never did. If I were to update the JS, I might consider switching to div containers since my semantic justification for using a table doesn't apply.

* There is one situation in which an unbreakable block can be split between two pages: when it exceeds the height of the printable area. You should try to avoid this scenario; you're essentially asking the browser to do the impossible, and it can have some very strange effects on the output.

This should be the accepted answer, it just works!

@DoctorDestructo i tried to implement this solution, it really avoided the page breaking inside a row, but i ended up with the columns all messed up, like this image : s11.postimg.org/uyhoktapv/error.jpg Can you help me to fix that ? Thanks in advance !

@delphirules Make sure no width values are being applied to any <td> or <th> elements in the table. There are only two valid ways to set column width: 1) let the cell content set it automatically, or 2) use <col> elements. If that's not the problem, then I'd probably have to see your table html (prior to running the required js function) to diagnose it.

@delphirules If your table has top padding applied to it, that could cause the behavior you describe. The best workaround would be to get rid of the top padding and instead add a bottom margin to the element above the table. (In fact, when it comes to printing, it's usually best to use bottom margins for vertical white space).

@Kingsolmn yw! Here's a simple explanation of how it works: The js splits your table into a series of one-row subtables, each with its own set of column headers. A negative margin partially retracts each subtable into its container, hiding the headers. The margin is implemented in such a way that it doesn't carry over if a subtable gets bumped to the next page by a page break. So, the first subtable on each page will have visible headers.

html - Having Google Chrome repeat table headers on printed pages - St...

html google-chrome html-table
Rectangle 27 0

Check your browser settings. JavaScript does not control this.

any idea in the settings what i change?

I'm not going to guess the version for you.

javascript - how to get rid of the header and footer when printing fro...

javascript printing printf
Rectangle 27 0

it looks you mix two things together. Your example is a javascript. It is not a PDF, it is just printing your document. It is equal as browser menu File -> Print, but the event is invoked from a javascript that handles button action. You can use the same button as in that example and add @print CSS to your web page to make your document nicely printable.

Also there is another way. If you want to print a PDF document from your application and you generate the PDF from Java code, look the following example for Wicket 1.6:

add(new Link<Void>("myPdfLink") {

    private static final long serialVersionUID = 1L;

    @Override
    public void onClick() {
        byte[] data = ... // TODO your data
        final ByteArrayInputStream stream = new ByteArrayInputStream(data);
        IResourceStream resourceStream = new AbstractResourceStream() {                    
            private static final long serialVersionUID = 1L;

            @Override
            public InputStream getInputStream() throws ResourceStreamNotFoundException {
                return stream;
            }

            @Override
            public void close() throws IOException {
                stream.close();
            }

            @Override
            public String getContentType() {
                return "application/pdf";
            }

        };   

        getRequestCycle().scheduleRequestHandlerAfterCurrent(
            new ResourceStreamRequestHandler(resourceStream)
                .setFileName("my-pdf-to-download.pdf")
                .setContentDisposition(ContentDisposition.ATTACHMENT)
                .setCacheDuration(Duration.ONE_SECOND)
        );  

    }

});
setCacheDuration()

java - How to print pdf file with wicket and javascript - Stack Overfl...

java printing wicket
Rectangle 27 0

This has been asked several times already, and the result is always that you can't do it via normal web technologies (HTML + Javascript). The best you can do is open the print dialog, and that is by design. What you can do since you control the kiosks is create some kind of browser extension (like a flash object, .Net hosted control, java applet, etc) and make that available to your page, so that it handles the printing part for you.

c# - Printing to a client printer from a web app - Stack Overflow

c# .net asp.net javascript printing
Rectangle 27 0

<html>
  <head>
    <title>Test</title>
    <g:javascript src="jquery.js"/>
    <script type="text/javascript">
           $(function(){
            var fontSize = 16;

            // cannot store img.position().top or .left here because it is
            // before the browser has re-flowed the columns, therefore the
            // positions will be incorrect
            var imageTops = new Array;
            var imageLefts = new Array;

            $('#fontUp').click(function() {
                reflow(1);
            });

            $('#fontDown').click(function() {
                reflow(-1);
            });

            function reflow(fontSizeStep) {
                storeImagePositions();
                var fontLimitReached = changeFont(fontSizeStep);
                if (!fontLimitReached) {
         //           moveImages();
                }
                return false;
            }

            function changeFont(step) {
                fontSize += step;

                var fontSizeLimitReached = true;

                if (fontSize > 30) {
                    fontSize = 30;
                } else if (fontSize < 16) {
                    fontSize = 16;
                } else {
                    fontSizeLimitReached = false;
                }

                if (!fontSizeLimitReached) {
            //      alert(fontSize)
                    $('p').css({fontSize: fontSize + 'px'});
                }

                return fontSizeLimitReached;
            }

            // initialize store of img top and left positions
            function storeImagePositions() {
                if (imageTops.length == 0) { // only do it once
                    $('img').each(function() {
                        var imgPosition = $(this).position();

                        imageTops.push(imgPosition.top);
                        imageLefts.push(imgPosition.left);
                    });
                }
            }

            function moveImages() {
                // bye bye images
                var images = $('img').detach();

                // clear any existing marginTop added on the images
                images.each(function() {
                    $(this).css('marginTop', 0);
                });


                // spanify paragraphs
                $('#column > p').each(function() {
                    $(this).html('<span>' + $(this).html().replace(/\s\s+/g).replace(/(\s)/g,'</span>$1<span>') + '</span>');
                });

                var imageIndex = 0;

                // iterate words, working out where we can move the img to in the flow and if
                // we find a match, increment the index so that as we continue the each()
                // the next image is evaluated for replacement
                $('#column > p span').each(function() {
                    var wordPosition = $(this).position();
                    var wordLeft = wordPosition.left;

                    if (wordLeft >= imageLefts[imageIndex]) {
                        var wordBottom = wordPosition.top + $(this).height();

                        if (wordBottom > imageTops[imageIndex]) {
                            $(this).before(images[imageIndex]); // move img before this word
                            var newImgTop = $(images[imageIndex]).position().top;
                            $(images[imageIndex]).css({marginTop: imageTops[imageIndex] - newImgTop + 'px'});
                            imageIndex++; // increment index so remainder spans are being evaluated against the next image
                        }
                    }
                });

                // reverse the "spanification"
                $('#column > p').each(function() {
                    $(this).html($(this).html().replace(/<\\?span>/g, '').trim());
                });
            }
        });
    //]]>
    </script>
    <style type="text/css">
      div#column {
        margin-left:20px;
        -moz-column-width: 250px;
        -moz-column-gap: 20px;
        -webkit-column-width: 250px;
        -webkit-column-gap: 20px;
        height: 850px;
      }

      p {
        margin:0;
        clear:left;
        font-size:16px;
        text-align:justify;
      }

      img {
        float:left;
        margin-top: 2px;
        margin-right: 10px;
        position:fixed;
      }
    </style>
  </head>
  <body>
    <div><a href="#" id="fontUp" style="margin-right:10px">Font +</a><a href="#" id="fontDown">Font -</a></div>
    <div><img src="${createLinkTo(dir:'images',file:'Winter.jpg')}" height="250" width="660" id="image" title="yoda" alt="yoda"/></div>
          <div id="column" style="margin-top: 255px;">
            <p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>
            <p>The change of name from  LiveScript to JavaScript roughly coincided with Netscape adding support for Java technology in its Netscape Navigator web browser. The final choice of name caused confusion,  giving the impression that the language was a spin-off of the Java programming language, and the choice has been characterized by many as a marketing ploy by Netscape to give JavaScript the cachet of what was then the hot new web-programming language. It has also been claimed that the language's name is the result of a co-marketing deal between Netscape and Sun, in exchange for Netscape bundling Sun's Java runtime with their then-dominant browser. Vivamus scelerisque ipsum ut justo. Pellentesque et ligula eu massa sagittis rutrum. In urna nibh, eleifend vel, suscipit ut, sagittis id, nunc.</p>
            <p>Nam ut sapien sed pede pulvinar rutrum. Nunc eu elit sed augue aliquet tincidunt. Morbi rutrum. Fusce at nisl eu tortor bibendum eleifend. Sed ac metus. Phasellus nec elit. Morbi tortor nulla, tristique a, adipiscing at, consectetuer et, nisi. Nunc vel sapien sed risus hendrerit egestas. Vivamus turpis arcu, placerat eu, congue vel, commodo ut, nisl.</p>
            <p>Java EE includes several API specifications, such as JDBC, RMI, e-mail, JMS, web services, XML, etc., and defines how to coordinate them. Java EE also features some specifications unique to Java EE for components. These include Enterprise JavaBeans, Connectors, servlets, portlets (following the Java Portlet specification), JavaServer Pages and several web service technologies. This allows developers to create enterprise applications that are portable and scalable, and that integrate with legacy technologies.  A Java EE application server can handle transactions, security, scalability, concurrency and management of the components that are deployed to it, in order to enable developers to concentrate more on the business logic of the components rather than on infrastructure and integration tasks.</p>
            <p>Java (Indonesian: Jawa) is an island of Indonesia. With a population of 136 million, it is the world's most populous island, and one of the most densely populated regions in the world. It is home to 60% of Indonesia's population. The Indonesian capital city, Jakarta, is in west Java.  Much of Indonesian history took place on Java; it was the centre of powerful Hindu-Buddhist empires, Islamic sultanates, the core of the colonial Dutch East Indies, and was at the centre of Indonesia's campaign for independence. The island dominates Indonesian social, political and economic life.</p>
            <p>Groovy is an object-oriented programming language for the Java platform. It is a dynamic language with features similar to those of Python, Ruby, Perl, and Smalltalk. It can be used as a scripting language for the Java Platform.
              Groovy uses a Java-like bracket syntax. It is dynamically compiled to Java Virtual Machine (JVM) bytecode and interoperates with other Java code and libraries. Most Java code is also syntactically valid Groovy.</p>
            <p>Scala runs on the Java platform (Java Virtual Machine) and is compatible with existing Java programs. It also runs on Android smartphones. An alternative implementation exists for the .NET platform, but it has not been kept up to date.
              Scala has the same compilation model as Java and C# (separate compilation, dynamic class loading), so Scala code can call Java libraries (or .NET libraries in the .NET implementation).Scala's operational characteristics are the same as Java's. The Scala compiler generates byte code that is nearly identical to that generated by the Java compiler. In fact, Scala code can be decompiled to readable Java code, with the exception of certain constructor operations. To the JVM, Scala code and Java code are indistinguishable.</p>
          </div>
  </body>
</html>

Could you please post this as a new question, rather than an answer to the question above. It really is a separate question. Also, I'm not sure if there is a comment limit on a question but the comments section on my answer is getting very long and is in danger of being too long for anyone to bother reading.

jquery - Increase font size with JavaScript around fixed floated image...

javascript jquery html css ipad
Rectangle 27 0

If the Tablet application is really connecting via WIFI to a configured network printer, and the printer is only Network connected, and there is no other PC, then it means you have a bit more work cut out for you. There are a couple of approaches you can take. Either way you are going to have to have a PC on site. If you can print to the network printer on the local network, from a web browser, you have a shot with an easy approach. If you set up a PC with Web Application access, you can configure the same network printer on the local network, and then just create an html page with a stylesheet media=print that formats the order appropriately, then a javascript print call on button click should do it. This requires a PC logged in to the web site, and a person managing the orders and clicking print in order to get the order data to the printer using standard methods. The second approach is to identify the printer manufacturer, download the POS drivers for the printer model and get a class written up to handle acquiring, queuing, and releasing invoices to the printer, and then create a scheduled job that writes orders to the printer similar to a service. This would require a PC on-site as well, but would not require a person monitoring the orders and printing the queue. The second approach is going to cost the client significantly more money to develop.

php - Website to POS printer - Stack Overflow

php web-services web-applications printing point-of-sale
Rectangle 27 0

You can serve to the user's browser a webpage that includes the necessary Javascript code to perform the printing if the user clicks to request it, as shown for example here (a pretty dated article, but the key idea of using Javascript to call window.print has not changed, and the article has some useful suggestions, e.g. on making a printer-friendly page; you can locate lots of other articles mentioning window.print with a web search, if you wish).

Calling window.print (from the Javascript part of the page that your Python server-side code will serve) will actually (in all browsers/OSs I know) bring up a print dialog, so the user gets system-appropriate options (picking a printer if he has several, maybe saving as PDF instead of doing an actual print if his system supports that, etc, etc).

My web app requires a continuous stream of automatic printing i.e it prints even if the user is not actively looking at it. Can the user authorize the script once and then just have it print automatically?

@Ali, I believe most browsers will not allow that kind of interaction without extensions/plugins. Could you write such plug-ins or extensions for all kinds of browsers your users may want to use (unfortunately there's no standard for those so it will have to be done over and over again -- and the only way to do it in Python would probably be with MS Silverlight add-in for IE and Safari)...?

Can a firefox or Chrome extension achieve the printing?

@Ali, I suspect it can, but that's a better subject for one or two separate question since the issue has absolutely nothing to do with Python and everything to do with Firefox and Chrome extensions, thus needing completely different tags. Big questions in comment threads (esp. threads on questions with totally unrelated tags) are a terrible idea -- as should be obvious, the people who might best answer your new question may know 0 about Python (which is completely unrelated to that question) and thus be skipping this thread entirely!

web applications - python web script send job to printer - Stack Overf...

python web-applications printing
Rectangle 27 0

I'm going out on a limb here , since your question was not very detailed, that a) your receipt printer is a thermal printer that needs raw data, b) that "from javascript" you are talking about printing from the web browser and c) that you do not have access to send raw data from browser

Here is a Java Applet that solves all that for you , if I'm correct about those assumptions then you need either Java, Flash, or Silverlight http://code.google.com/p/jzebra/

how about printing from a web browser in a mobile device like android?

jquery - JavaScript receipt printing using POS Printer - Stack Overflo...

javascript jquery printing
Rectangle 27 0

You can't print a PDF document directly from browser using Javascript. The Javascript function window.print() use the browser printing function and this is not what you need. You can achieve your aim starting the print through Java Web Start. Put your PDF document directly into the jnlp so you can run a Java program that recieve the raw PDF document as argument. Now you're running in the system and no longer in the browser, so you can directly interface with printing driver through JAVA API. This seem quite simple but really it's not because the JAVA printing API doesn't accept a file as input but a particular data structure that implements the ava.awt.print.Pageable interface.

Exist a web service at www.pdfprint.it that do all the work for you. Here a snippet taken from the official documentation.

<?php

// 1. GET the jnlp file with curl

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://www.pdfprint.it/printPdf?auth=XXXX");        
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //return the transfer as a string       
$jnlp = curl_exec($ch);
curl_close($ch); 

$pdfDoc ="example.pdf";


//2. put in the jnlp your PDF document base64 encoded

$jnlp = str_replace("####", base64_encode(file_get_contents($pdfDoc)),$jnlp);


//3. echo the jnlp file 

header('Content-type: application/x-java-jnlp-file');

echo $jnlp;

You only need to get the jnlp file, put in your PDF document and send the jnlp to the browser. The JAVA program that run the printing will be dowloaded directly from the web service. You can also set some printing options as copies, sides, and so on

javascript - How to print a PDF from the browser - Stack Overflow

javascript html pdf
Rectangle 27 0

This has been asked several times already, and the result is always that you can't do it via normal web technologies (HTML + Javascript). The best you can do is open the print dialog, and that is by design. What you can do since you control the kiosks is create some kind of browser extension (like a flash object, .Net hosted control, java applet, etc) and make that available to your page, so that it handles the printing part for you.

c# - Printing to a client printer from a web app - Stack Overflow

c# .net asp.net javascript printing
Rectangle 27 0

You can't print a PDF document directly from browser using Javascript. The Javascript function window.print() use the browser printing function and this is not what you need. You can achieve your aim starting the print through Java Web Start. Put your PDF document directly into the jnlp so you can run a Java program that recieve the raw PDF document as argument. Now you're running in the system and no longer in the browser, so you can directly interface with printing driver through JAVA API. This seem quite simple but really it's not because the JAVA printing API doesn't accept a file as input but a particular data structure that implements the ava.awt.print.Pageable interface.

Exist a web service at www.pdfprint.it that do all the work for you. Here a snippet taken from the official documentation.

<?php

// 1. GET the jnlp file with curl

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://www.pdfprint.it/printPdf?auth=XXXX");        
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //return the transfer as a string       
$jnlp = curl_exec($ch);
curl_close($ch); 

$pdfDoc ="example.pdf";


//2. put in the jnlp your PDF document base64 encoded

$jnlp = str_replace("####", base64_encode(file_get_contents($pdfDoc)),$jnlp);


//3. echo the jnlp file 

header('Content-type: application/x-java-jnlp-file');

echo $jnlp;

You only need to get the jnlp file, put in your PDF document and send the jnlp to the browser. The JAVA program that run the printing will be dowloaded directly from the web service. You can also set some printing options as copies, sides, and so on

javascript - How to print a PDF from the browser - Stack Overflow

javascript html pdf
Rectangle 27 0

You can achieve this by creating a new page in a popup window or an <iframe>, writing a document into it containing whatever information and markup (and CSS) you need, and then having the page call "window.print()".

That would be a better answer with an example - especially one that shows popup.document.close()

the problem is entering the HTML inside pop-up window, should i be using "document.writeln" for each line... how do I place the content in the new window ? I can do this using ERB or something similar but I cannot use server-side interaction here as I am trying to build an offline app

Yes, you can use "document.write()" to build the page. You could build just a skeleton that way and then add more content with DOM manipulation, if you prefer.

javascript - Printing from Web Browser - Stack Overflow

javascript web-applications sproutcore
Rectangle 27 0

Sorry to inform you that these steps cannot be done simply using pure javascript. Imagine if web sites were capable of automatically sending print requests directly to users printers. You visit a malicious site and you could run out of toner and/or paper. You will have to install some plugin on the client browser that will perform the actual printing.

Just giving a print dialog is enough. After user hits print button form that print dialog, it should close.

Open PDF in a new window, print that PDF and Close the window. How to ...

javascript pdf