Thursday, September 4, 2008

Printing From The Web With CSS

All I Want To Do is Print!

Printing from the web has always been kind of hack. I have yet to find a silver bullet that allows users to manage how they go about printing web content. This post will speak to two pretty decent techniques and what they're good at.

There's Wheat, and There's Chaff

When it comes to printing a web page there's traditionally a small section that you want to print and the rest of the page (navigation, footers, etc...) you could really do without. The following two techniques deal with doing just that, allowing the user to print specific sections that you dictate.

Hidden IFrame

The first technique involves putting a hidden IFrame in the page. When it comes time to print we inject the content we want into the hidden IFrame and call print on the IFrame. This is great because it requires very little effort on our part and we can cherry pick content we want to print.

Consider the following example:

A bunch of content that will get printed by button Print1.
Other content that will get printed by button Print2.

The following JavaScript needs to be included in the page:

function printContent(iframeId, elementId)
{
   var element = document.getElementById(elementId);
   if (element != null && window.frames[iframeId] != null)
   {
      var content = "<html><body>"+element.innerHTML+"</body></html>";
      window.frames[iframeId].document.open();
      window.frames[iframeId].document.clear();
      window.frames[iframeId].document.write(content);
      window.frames[iframeId].document.close();
      window.frames[iframeId].focus();
      window.frames[iframeId].print();
   }
   else
   {
      alert("Unable to locate print resources, request aborted.");
   }
}

Then you simply need to throw down a hidden iframe like below:

<iframe id="printIframe" name="printIframe" scrolling="auto" frameborder="0" style="height: 0px; width: 0px;" />

And then some container that contains your content and a button to call print on the iframe:

<div id="printContent1">A bunch of content that will get printed by Print1</div>

<button onclick="printContent('printIframe','printContent1')">Print1 </button>

And there you have it, the ability to print discrete page sections. What's even better is that most of the CSS is honored during the print. The mild issue with this is that because the printing is actually done from the iframe as opposed to the original document, the user is left high and dry should they try to do a print preview from the web. There's also a chance that if your CSS selectors are driven by a hierarchy of html elements (ie p div.content) and you don't include all the html in the hierarchy, it won't get applied during the print.

CSS and the @Media Attribute

Another way to print out from the web is to use the @Media CSS attribute to specify a different set of CSS for the printer. This well supported attribute allows you to apply different CSS to a document if the client is a computer, mobile device, printer, web tv etc... more information can be found here. That is, when the user visits your page a normal set of CSS is used, and when they go to print (or print preview) some additional CSS or an entirely different set of CSS is used, it's your call.

The problem with using the @Media attribute is that without any help you'd need to reauthor the stylesheet selectively hiding navigation areas while showing other areas. Fortunately we can take the lazy route by writing some JavaScript.

Here's a JavaScript/@Media Print technique which I'm going to share. Essentially we do 3 things.

  1. Add a script link to the following JavaScript.
    <script src="PrintHelper.js" type="text/javascript"></script>
  2. Add a CSS style that look like this:
    @media print
    {
       .DontPrintMe
       {
          display: none;
       }
    }
  3. Add a "PrintMe" class to any block of content you want to print:
    <div class="PrintMe">Printable Content</div>

Essential the JavaScript does two things after the document has finished loading.

  • It looks at any element that has the class "PrintMe" and adds that same class to all that elements ancestors (parents) and all that elements descendents.
  • After that it takes all other elements that don't have this class and puts a "DontPrintMe" class on them.
  • Now when the user goes to print, only elements (and their children) that you specifically asked to show in the print will. All others will be hidden (because of the CSS rule we added).

The best thing about this solution is that it's flexible AND it honors the print preview in browsers so users know exactly what's going to be printed. I liked this solution so much that I implemented it in my blog and now all my posts are print/preview savvy.

Wrap it up

During this post we looked at selective printing on the web with two different techniques.

In the first technique we injected specific pieces of content into an iframe and then called print() on the iframe in JavaScript. This was really straight forward to implement and allowed us to selectively print areas of the screen as long as the user prints by clicking a HTML button that we created. This also doesn't honor print preview, but this technique is pretty simple to set up.

In the second technique we used JavaScript to walk the document and tag all content that we didn't specifically mark as areas we wanted to print. We then added a rule that hides all those areas with CSS using the @Media print rule. This has the added benefit of working anytime the document is printed and honoring the browser's print preview.

I have to give a special thanks to James Fedor who taught me about the IFrame printing, some of the code you saw in this post is derived from his work.

That's it, hope it helped someone.

My Best,
Tyler Holmes


No comments: