Ask Ben: Print Part Of A Web Page With jQuery
Posted May 21, 2009 at 9:10 PM
Ben, great new look!! Quick question, I am implementing a jQuery modal pop-up in my app and I want the users to be able to print only the content of that modal window. Any idea? Thanks!
To be honest, I've never done this before, so I am not sure if the following solution is truly cross browser compliant. The jQuery plugin that I authored below was tested to be working in FireFox, IE 7, Safari, and the latest version of Chrome Beta (although Chrome seemed to have some issues with the images from time to time). Before we get into how it works, take a look at this demo video so you can see what I'm talking about:
| | | | | |
| | | |||
| | | |
As you can see, I created a jQuery plugin to handle the print() functionality. This way, we can make anything on the page printable using a jQuery selector. The caveat being that the containing element is not printed - only it's child elements get added to the print document. In the following page, when the DOM is ready, we find our link element and have it print (upon click) any element on the page with the class "printable:"
Launch code in new window » Download code as text file »
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <html>
- <head>
- <title>Print Part of a Page With jQuery</title>
- <script type="text/javascript" src="jquery-1.3.2.js"></script>
- <script type="text/javascript" src="jquery.print.js"></script>
- <script type="text/javascript">
-
- // When the document is ready, initialize the link so
- // that when it is clicked, the printable area of the
- // page will print.
- $(
- function(){
-
- // Hook up the print link.
- $( "a" )
- .attr( "href", "javascript:void( 0 )" )
- .click(
- function(){
- // Print the DIV.
- $( ".printable" ).print();
-
- // Cancel click event.
- return( false );
- }
- )
- ;
-
- }
- );
-
- </script>
-
- <style type="text/css">
-
- body {
- font-family: verdana ;
- font-size: 14px ;
- }
-
- h1 {
- font-size: 180% ;
- }
-
- h2 {
- border-bottom: 1px solid #999999 ;
- }
-
- .printable {
- border: 1px dotted #CCCCCC ;
- padding: 10px 10px 10px 10px ;
- }
-
- img {
- background-color: #E0E0E0 ;
- border: 1px solid #666666 ;
- padding: 5px 5px 5px 5px ;
- }
-
- a {
- color: red ;
- }
-
- </style>
- </head>
- <body>
-
- <h1>
- Print Part of a Page With jQuery
- </h1>
-
- <p>
- <a>Print Bio</a>
- </p>
-
- <div class="printable">
-
- <h2>
- Jen Rish
- </h2>
-
- <p>
- Jen Rish, upcoming fitness and figure model has some
- crazy developed legs!
- </p>
-
- <p>
- <img
- src="jen_rish_crazy_legs.jpg"
- width="380"
- height="570"
- alt="Jen Rish Has Amazing Legs!"
- />
- </p>
-
- <p>
- I bet she does some <strong>serious squatting</strong>!
- </p>
-
- </div>
-
- </body>
- </html>
And, here is the jQuery plugin, print(), that powers this solution:
Launch code in new window » Download code as text file »
- // Create a jquery plugin that prints the given element.
- jQuery.fn.print = function(){
- // NOTE: We are trimming the jQuery collection down to the
- // first element in the collection.
- if (this.size() > 1){
- this.eq( 0 ).print();
- return;
- } else if (!this.size()){
- return;
- }
-
- // ASSERT: At this point, we know that the current jQuery
- // collection (as defined by THIS), contains only one
- // printable element.
-
- // Create a random name for the print frame.
- var strFrameName = ("printer-" + (new Date()).getTime());
-
- // Create an iFrame with the new name.
- var jFrame = $( "<iframe name='" + strFrameName + "'>" );
-
- // Hide the frame (sort of) and attach to the body.
- jFrame
- .css( "width", "1px" )
- .css( "height", "1px" )
- .css( "position", "absolute" )
- .css( "left", "-9999px" )
- .appendTo( $( "body:first" ) )
- ;
-
- // Get a FRAMES reference to the new frame.
- var objFrame = window.frames[ strFrameName ];
-
- // Get a reference to the DOM in the new frame.
- var objDoc = objFrame.document;
-
- // Grab all the style tags and copy to the new
- // document so that we capture look and feel of
- // the current document.
-
- // Create a temp document DIV to hold the style tags.
- // This is the only way I could find to get the style
- // tags into IE.
- var jStyleDiv = $( "<div>" ).append(
- $( "style" ).clone()
- );
-
- // Write the HTML for the document. In this, we will
- // write out the HTML of the current element.
- objDoc.open();
- objDoc.write( "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">" );
- objDoc.write( "<html>" );
- objDoc.write( "<body>" );
- objDoc.write( "<head>" );
- objDoc.write( "<title>" );
- objDoc.write( document.title );
- objDoc.write( "</title>" );
- objDoc.write( jStyleDiv.html() );
- objDoc.write( "</head>" );
- objDoc.write( this.html() );
- objDoc.write( "</body>" );
- objDoc.write( "</html>" );
- objDoc.close();
-
- // Print the document.
- objFrame.focus();
- objFrame.print();
-
- // Have the frame remove itself in about a minute so that
- // we don't build up too many of these frames.
- setTimeout(
- function(){
- jFrame.remove();
- },
- (60 * 1000)
- );
- }
The plugin itself is not too complicated - we are creating an IFrame on the fly, writing the target HTML to its body, and then printing it. It's a simple concept, but there were some issues getting it to work in Internet Explorer (IE). The following caveats tripped me up at first:
- In IE, you have to focus() the IFrame before you print it other wise the top page prints.
- In IE, I kept getting errors when trying to use jQuery to write the STYLE tags to the IFrame head. As such, I had to write the html() of the STYLE tags as I was writing out the document. The Style tags are crucial to in the document because they will determine the look and feel of the printed content.
Once I got those two issues out of the way, the rest worked pretty smoothly. I am not sure if this is the best technique, but hopefully it will point you in the right direction.
Download Code Snippet ZIP File
Post Comment | Ask Ben | Permalink | Other Searches | Print Page
Newer Post
Getting IFRAME Window (And Then Document) References With contentWindow
Older Post
Converting XHTML To Text-Only Version Using ColdFusion And XSLT
Reader Comments
Ben, do you need the timeout before removing the frame? I did some checking with Safari and FF and found that the code beyond the print() call on the frame does not execute until the user finishes with the print dialog. Haven't had a chance to try this on IE, it's late and I don't feel like starting up my Windows VM :)
That's very clever and makes things so much easier from a developer's perspective.
I used to craft a print.css which makes use of the html media type print and hides all GUI elements on the page among with some other markup changes. It does not work flawlessly with all browsers, though.
I just use jqPrint:
http://plugins.jquery.com/project/jqPrint
@Todd,
That plugin looks good. Looking at their code, it is a better solution. I totally forgot about pulling over LINK tags :)
@Ryan,
I just assumed that you would need a timeout. I didn't actually test to see if it was necessary.
I'm sorry, I linked to the wrong plugin that I use. I use this one atm:
http://plugins.jquery.com/project/jPrintArea
Unfortunately, it looks to have been abandoned by the author.
@Todd,
Looks to be basically the same. I think they said that the first one you posted was actually an update to the second one you posted. But I could be making that up.
@Ben: Different authors, but improved upon. Yes, you're correct.
@Todd,
Looking at the code, I saw "contentWindow". I've never seen that before. Looks really useful - thanks for helping me learn some cool stuff :)
@Todd,
Thanks again man:
Dude you just saved me a ton of time! Very nice plugin!
Thanks
@Trung,
No problem my man!
Very cool work on this plugin. Just what I needed. But, is there any way to make this work with an input button instead of a text link?
I know this isn't related to jQuery but why not just have the printable version load in a coldfusion created PDF? It would eliminate all that code. Just saying...
@Jody,
It wouldn't really eliminate code as you would have to create a new CFM page for generating the PDF. This page would also have to know how to trim down the requested HTML to just the target section. I think going to a PDF would actually increase the code you would need.
@Vernon,
You just need to bind the click event to the button rather than the link.
I can't believe how easy it is to implement.
Thank you so much, you saved me time.
dont undesrstend why but when i`m printing in FF - it prints on two pages and when i`m printing in IE7 it printing all except my conent http://v8.dxloo.com/vehicle/2004/VOLKSWAGEN/PASSAT/GLS/WAGON/WVWVD63B14E220600/FOR-SALE-IN/FORKED_RIVER/NJ/200012184/
I am... kind of a Mr. Magoo type web designer...
I have a pretty simple question:
In the .printable style, do I have to include any attributes? Does this style HAVE to put in a dotted line or anything at all?
My design is all set up with borders, etc already.
I wonder if I can just enclose my existing divs (the ones I want to include in the print area) in the .printable div, which I do not want to add any formatting to the doc.
Is this clear?
thanks!
finn
@Finnerty,
The dotted line I added for the demo. You don't need it. Really, just the "printable" class should be enough for the jQuery script to hook into.
OK! :-)
on my way to the code, to try it all out. Thank you amigo!!
I'll let you know...
(...looks to his right and clucks... "Oh that Waldo... always worrying!")
fin
well... it almost works... I saved "snippet_2.txt" as "jquery.print.js" on my server.
I changed the links to both that and the jquery-1.3.2.js file in the head of my doc, and pasted your Head section parts into my Head, including the "initialize print link" stuff.
I put the .printable style into my CSS, changing the border to "none."
I put the div class="printable" tags enclosing what I want to print.
I enclosed a the words "print this page" between the a and /a tags.
OK!
The link does show the "javascript:void (0) that it should (as per the code in the Head) but.. no print.
I'm thinking I am not hooking up to your script correctly in my Head section, or at least, something is not making the connection to the script to do the work.
That's how it goes over here, if you have the time or inclination, maybe the problem is something really obvious, and I just need a nudge... thanks!!
OH YEAH!
You MADE MY WEEK!
I stripped out the <!-- comment at the top of "snippet_2 - jquery.print.js" and...
NICE!!!!!
Thank you amigo!!!
finn :-) :-) :-)
UH OH.
Now ALL the links on my page want to do the print thing. That has to be as a result of the function "$( "a" )" I assume.
What can I change that to? Can I add an ID or something to both the function and the tag used to call it?
so close...
finn
change $("a") to $(".printme a") and add class="printme" to all <a> elemnts which are supposed to initiate the printing routine. (you can have multiple classes assigned to one html element)
OK... done... the links on the page go where they are meant to go again.
But now the print link does not work. It isn't communicating the class attribute I guess...
Not sure what's up, unless the fact that the link is also enclosed in a span class... but that doesn't seem like a problem to me...
< span class="archiveLink" >< a class="printme" >print this page< /a >< /span >
I tried switching the $(".printme a") to $("a .printme")but that was a no-go...
I added the .printme class to my stylesheet, with no attributes... nada...
so... I am just bumbling around...
finn
Sorry for confusing you. That was my fault.
You can fix it by moving the class attribute printme to the outer span element.
Like this:
< span class="archiveLink" class="printme" >< a >print this page< /a >< /span >
together with this selector:
$(".printme a")
Or you can use $("a.printme") (you nearly got it right!) and have the printme class assigned to the a-element instead.
sorry Ben for flooding your blog comments ;)
Happy Happy Joy Joy!!!!
I had to make one little adjustment...
I didn't tell you EVERYTHING...
here's what works:
< div id="archiveLinkDiv" class="printme" >
< span class="archiveLink" >< a >print this page< /a >< /span > |
< /div >
I had the span enclosed in a div... so the "printme" hook is working off of that..
I TOLD you I was a Mr. Magoo!
You are generous and very kind. Thankyou VERY much!
finn
@Finnerty,
Glad you got it working - yeah, you have to tell the jQuery selector exactly what link to hook up otherwise, it might hook up all of them!
@Martin,
Thanks for jumping in there to help out!
Hi,
is it intended to omit
objDoc.write( "<body>" );
Either way, my original page uses <link> tags to it's stylesheet. You know how to get them?
/HW
@Henrik,
It should be writing the opening body tag in the above example. I'm not sure why that part is not working for you.
As for LINK tags, those should work the same as with the Style tags. You can probably just swap "style" for "link" in the example.
Nice plugin it help a lot... thnx....
Do you have some code on how to change the page setup... like changing portrait to landscape?
@Eljoe,
I do not know of any way to do that, sorry.
thanks for the plugin :)
Everything is working great, except, how do I get rid of the URL printed at the top of the page?
thanks
@Jeff,
I am not sure there is any way to do that, at least not that I know of; that is a function of the browser's printing mechanism itself.
Hi,
Do you know of any way I can hide an image from the printed page, without removing it from the underlying page? I've currently got the following which prints without the image, but (obviously) the image flickers when the print dialogue is displayed:
$( "#LeftContent h1 img").css('display', 'none');
$( "#LeftContent" ).print();
$( "#LeftContent h1 img").css('display', 'block');
@Darren you can do this via css. search for css media types.
it goes something like this:
@media print {
/* style sheet for print goes here
it only applies to the printed page*/
}
Hi Martin,
I do have a print.css file however I don't believe it's referenced when using the print link. If I use File > Print, it prints as intended, using the css file, but not using the javascript link. Is there something glaring that I'm missing?
Thank you for your reply,
Kind regards,
Darren.
Hi Darren, does your "temp document DIV" include the print.css, too? It's used for styling the iframe.
# objDoc.open();
# objDoc.write( "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">" );
# objDoc.write( "<html>" );
# objDoc.write( "<body>" );
# objDoc.write( "<head>" );
# objDoc.write( "<title>" );
# objDoc.write( document.title );
# objDoc.write( "</title>" );
# objDoc.write( jStyleDiv.html() );
# objDoc.write( "</head>" );
# objDoc.write( this.html() );
# objDoc.write( "</body>" );
# objDoc.write( "</html>" );
# objDoc.close();
Hi Martin,
Nope, I wasn't referencing the print css file at all... Turns out I was indeed missing something glaringly obvious!
Thank you very much for your help.
Kind regards,
Darren.
@Martin, @Darren,
Print style sheets are one of those awesome things that I keep forgetting to take any advantage of when I build stuff.
Still can't get it to reference the css file, even though it's now included in the temp div (I've tried absolute, and relative links, different locations in the head for the link, and single then no quotes around the attributes...:
objDoc.open();
objDoc.write( "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">" );
objDoc.write( "<html>" );
objDoc.write( "<body>" );
objDoc.write( "<head>" );
objDoc.write( "<title>" );
objDoc.write( document.title );
objDoc.write( "</title>" );
objDoc.write( jStyleDiv.html() );
objDoc.write( "<link rel=stylesheet href=/css/print.css type=text/css media=print />" );
objDoc.write( "</head>" );
objDoc.write( this.html() );
objDoc.write( "</body>" );
objDoc.write( "</html>" );
objDoc.close();
Any ideas?
Kind regards,
Darren.
Hi Darren, it looks like you're missing the double quotes.
I think this should work:
objDoc.write( "<link rel=\"stylesheet\" href=\"/css/print.css\" type=\"text/css\" media=\"print\" />" );
Hi Martin,
Nope, that's not doing it for me either! I got around the image problem by using...
objDoc.write( "<style type=\"text/css\"> img {display:none;}</style> ");
...but haven't been able to reference the css file correctly. Thanks again for all your help though!
Kind regards,
Darren.
Hi Darren, nice to read you made it. I've never used the referencing feature myself.
This would be an option as well:
objDoc.write( "<style type=\"text/css\"> @import url(\"./css/print.css\") </style>");
Best Regards
-Martin




