Using CSS and/or jQuery for Printed Pages with Page Breaks

17.7k views Asked by At

I have a dynamically generated html page which is designed to be printed.

I would like to create page breaks based upon div sections - where any given div - if it DOES NOT fully fit on the page, insert a page break before it.

In theory, anywhere from a single div, up to perhaps 10, may fit on a single printed page, so I am thinking I will need to use jQuery to to the insertions after the page is loaded.

If this were a desktop application, I would approach it something like this:

  1. Measure the page width and height (using printer object of some sort).
  2. Measure each div height - and subtract that from page total remaining height
  3. if ( remaining_space - div_height > 0 ) { // put it on page } else { //insert page break first }

Is there any way using jQuery, CSS, raw JavaScript, or anything else, that would get me to this scenario?

5

There are 5 answers

0
OneNerd On BEST ANSWER

Ok, I spent past day or so figuring this out, so I wanted to post my solution to this in case anyone else needed the answer.

Generally speaking here is what I did.

  1. Generated output as normal (not printer-intended)
  2. Created 2 style sheets (one for printer, and one for screen). Measurements were in inches (not pixels) for all page elements to be turned into printed output.
  3. Using jQuery, I did the following:

-> called function that appends initial page to DOM - something like this

// assumes old_page_id is existing element in DOM, and var page_id = 1;
$j("<div class='page' id='" + page_id + "'><div class='print_area'></div></div>")
.insertAfter('#' + old_page_id);

-> measure height of div that is the page (in my case, $('.page').height(); )

-> ran a function to do the insertions of divs and new pages - something like this

$('div_class').each(
 function() {
  // measure height of $(this)
  // add it to running total of used space on existing page
  // if sum total exceeds remaining space, create new page, and add to that one
  // if still room left, add to this one
 }
);

Note that each page div (in my case, class='page') in the printer stylesheet has this:

page-break-after: always;

This is how I got it to create a new page on the printer where I wanted.

After running jQuery function above, I hid the original section that contained div elements I wanted to move into printed pages. Note I could not hide this section before hand because my jQuery measurements would not produce valid results (in my case, I placed all the divs inside a div wrapper with id of 'hide_me', and set the style to height:1px; overflow:auto; ).

I realize this is very 50,000 ft view, but hopefully it is helpful to you.

0
Neil On

You could always insert an empty DIV tag with the the CSS attribute {pageBreakBefore: 'always'} or {pageBreakAfter: 'always'} at points in your HTML where you have calculated in jQuery the page boundaries to be by inspecting the height of the DIVs or whatever. But you'd need to have a fairly intimate knowledge of the page content, and probably specify your font sizes etc. as "pt" instead of "px".

See a similar query here though:

Enforce Print Page Breaks with CSS

1
rajesh pillai On

Not exactly what you are looking for, but have a look at this http://www.javascriptkit.com/dhtmltutors/pagebreak.shtml

With an additional dash of JS, may be possible what you want to try to achieve.

2
Javier Figueroa On

You could try using this plugin to print the elements of a page. http://www.recoding.it/wp-content/uploads/demos/jqprint-demo.htm

This could be helpful if you follow the same logic you suggested for a desktop application. You can wrap your "pages" in divs and that's what you would tell jqPrint to print. The page breaks could be another element or also a div but with a class to let you know it's page break. For example:

<div id="#page1" class="page">
...
</div>
</div id="#break1" class="break"> </div>

etc..

then

$('#page1').jqprint(); 

I don't know if you're familiar with jQuery. If you're not let us know.

0
Jon Jay Obermark On

The point of print-pagination, in my mind, is to not split things badly. And usually, what you want not to split badly most often is tabular data. So this is an approach that assumes you have changed the <div>'s into table formatting and grouped the sets of rows that should not be split into <tbody> groupings.

We want to mark headers and footers for the code to find, and we do not necessarily want them to appear right where they are described

This invokes itself at page load, but could instead be called right before printing.

<script>
    $().ready(function () {
        $('table.paged').each(function (i, o) {
            BreakPages($(o));
        })
    });
    function BreakPages(jSource) {
        $('body').width("7in");
        var perInch = $('body').width() / 7.5;
        var nPageHeight = 9 * perInch;

        var jHeader = null;
        var jFooter = null;
        var jWrapper = jSource.clone().empty();
        var jPage = null;
        var iPage = 0;
        jSource.after("<div></div>");
        var jFixed = jSource.next();

        function substed(jStuff) {
            var jCopy = jStuff.clone();
            jCopy.find('span.pageNo').html(" " + iPage + " ").removeClass('pageNo');
            return jCopy;
        }

        jSource.children().each(
            function (iChunk, oChunk) {
                var jChunk = $(oChunk);
                if (jChunk.hasClass('footer')) {
                    jFooter = jChunk;
                }
                if (jChunk.hasClass('header')) {
                    jHeader = jChunk;
                    jFooter = jChunk.nextUntil('.footer').last().next();
                }
                if (!jChunk.hasClass('sample')) {
                    var nHeight = jChunk.height();
                    if (jPage)
                        nHeight += jPage.height();
                    if (jFooter)
                        nHeight += jFooter.height();
                    if (nHeight > nPageHeight) {
                        if (jFooter)
                            jPage.append(substed(jFooter));
                        jPage.after("<p style='page-break-after:always; height:0; width:0'>&nbsp</p>");
                        jPage = null;
                    }
                    if (!jPage) {
                        iPage++;
                        jPage = jWrapper.clone();
                        jFixed.append(jPage);
                        if (jHeader && jHeader !== jChunk)
                            jPage.append(substed(jHeader));
                    }
                    jPage.append(jChunk);
                }
            }
        );
        jFixed.find('span.pageCount').html(" " + iPage + " ");
        jFixed.find('span.pageNo').html(" " + iPage + " ");
        jSource.remove();
    }
</script>

We lay out the table to be paginated like this.

<body>
    <table>
        <tbody class="header">
            PAGE ONE HEADER
        </tbody>
        <tbody class="header sample">
            LATER PAGE HEADER (IF RELEVANT)
        </tbody>
        <tbody class="together">
            ROW GROUP 1
        </tbody>

        <tbody class="together">
            ROW GROUP N
        </tbody>
        <tbody class="footer">
           FOOTER
        </tbody>
    </table>
</body>

We scan over the <tbody>'s and stuff a footer and a header before any element that is destined to break across the end of a page.

We mark headers and footers with css classes and keep track of the possibility of changing headers and footers by putting them at the locations in the row flow where they need to change, but keeping them invisible with another css class.

Annoyingly some implementations of page breaking only apply to strictly "block" elements, so it is not defined on tbody. Therefore I have applied it to a <p> tag class appended to each page table.