I'm the author of printThis, a jquery plugin for printing.
https://github.com/jasonday/printThis
I have a user that has brought up an issue, that I have been unable to crack and unfortunately, I am unable to share the page (privacy concerns).
On the user's site, the issue presents on some pages in IE, but not others. The print is failing to happen, as the iframe remains empty.
The error in IE is within jQuery:
contents: function (a) {
return f.nodeName(a,
"iframe") ? a.contentDocument || a.contentWindow.document : f.makeArray(a.childNodes)
}
Using logging, I was able to determine it was failing around this line:
var $doc = $("#" + strFrameName).contents();
But again, this only happens on some pages and I have been unable to recreate in any instance outside of this user's site.
My question: Is there a better approach here? or a method to make the $doc
object more bulletproof?
// -----------------------------------------------------------------------
// printThis v1.1
// Printing plug-in for jQuery
//
// Resources (based on) :
// jPrintArea: http://plugins.jquery.com/project/jPrintArea
// jqPrint: https://github.com/permanenttourist/jquery.jqprint
// Ben Nadal: http://www.bennadel.com/blog/1591-Ask-Ben-Print-Part-Of-A-Web-Page-With-jQuery.htm
//
// Dual licensed under the MIT and GPL licenses:
// http://www.opensource.org/licenses/mit-license.php
// http://www.gnu.org/licenses/gpl.html
//
// (c) Jason Day 2012
//
// Usage:
//
// $("#mySelector").printThis({
// debug: false, //show the iframe for debugging
// importCSS: true, // import page CSS
// printContainer: true, // grab outer container as well as the contents of the selector
// loadCSS: "path/to/my.css" //path to additional css file
// });
//
// Notes:
// - the loadCSS option does not need @media print
//------------------------------------------------------------------------
(function($) {
var opt;
$.fn.printThis = function (options) {
opt = $.extend({}, $.fn.printThis.defaults, options);
var $element = (this instanceof jQuery) ? this : $(this);
// if Opera, open a new tab
if ($.browser.opera)
{
var tab = window.open("","Print Preview");
tab.document.open();
}
// add dynamic iframe to DOM
else
{
var strFrameName = ("printThis-" + (new Date()).getTime());
var $iframe = $("<iframe id='" + strFrameName +"' src='about:blank'/>");
if (!opt.debug) { $iframe.css({ position: "absolute", width: "0px", height: "0px", left: "-600px", top: "-600px" }); }
$iframe.appendTo("body");
}
// allow iframe to fully render before action
setTimeout ( function () {
if ($.browser.opera)
{
var $doc = tab.document;
} else
{
var $doc = $("#" + strFrameName).contents();
}
// import page css
if (opt.importCSS)
{
$("link[rel=stylesheet]").each(function(){
var href = $(this).attr('href');
if(href){
var media = $(this).attr('media') || 'all';
$doc.find("head").append("<link type='text/css' rel='stylesheet' href='" + href + "' media='"+media+"'>");
}
});
}
// add another stylesheet
if (opt.loadCSS)
{
$doc.find("head").append("<link type='text/css' rel='stylesheet' href='" + opt.loadCSS + "'>");
}
//add title of the page
if (opt.titlePage)
{
$doc.find("head").append('<title>'+opt.titlePage+'</title>');
}
//grab outer container
if (opt.printContainer) { $doc.find("body").append($element.outer()); }
else { $element.each( function() { $doc.find("body").append($(this).html()); }); }
//$doc.close();
// print
($.browser.opera ? tab : $iframe[0].contentWindow).focus();
setTimeout( function() { ($.browser.opera ? tab : $iframe[0].contentWindow).print(); if (tab) { tab.close(); } }, 1000);
//removed iframe after 60 seconds
setTimeout(
function(){
$iframe.remove();
},
(60 * 1000)
);
}, 333 );
}
$.fn.printThis.defaults = {
debug: false, //show the iframe for debugging
importCSS: true, // import page CSS
printContainer: true, // grab outer container as well as the contents of the selector
loadCSS: "", //path to additional css file
titlePage: "" //add title to print page
};
jQuery.fn.outer = function() {
return $($('<div></div>').html(this.clone())).html();
}
})(jQuery);
UPDATE
Issue to due to document.domain
This type of page has document.domain
set and IE does not inherit document.domain
from the parent.
To fix that portion, I changed the iframe creation to standard javascript and set the source to write document.domain
on iframe creation.
var printI= document.createElement('iframe');
printI.name = "printIframe";
printI.id = strFrameName;
document.body.appendChild(printI);
printI.src = "javascript:document.write('<head><script>document.domain=\"mydomain.com\";</script></head><body></body>')";
var $iframe = $("#" + strFrameName);
So this fixes the access denied, however now the frame won't print. I've tried a lot of different methods for accessing the object, however none of them are working.
A) how would you access the frame in this scenario (i've tried most of the methods outlined on SO) to get IE to recognize and print
or
B) can anyone think of a better way to get the document.domain into the iframe on creation with jQuery? (can't be afterwards, as the access denied issue will come up)
Issue is due to IE not inheriting the parent document.domain.
Unfortunately, once you get into this murky area, it took some specific hacks to get this to work properly.
Basically doing a check for if document.domain is being explicitly set and the browser is IE.
Full updated plugin:
https://github.com/jasonday/printThis
(function ($) {
var opt;
$.fn.printThis = function (options) {
opt = $.extend({}, $.fn.printThis.defaults, options);
var $element = this instanceof jQuery ? this : $(this);
var strFrameName = "printThis-" + (new Date()).getTime();
if(window.location.hostname !== document.domain && navigator.userAgent.match(/msie/i)){
// Ugly IE hacks due to IE not inheriting document.domain from parent
// checks if document.domain is set by comparing the host name against document.domain
var iframeSrc = "javascript:document.write(\"<head><script>document.domain=\\\"" + document.domain + "\\\";</script></head><body></body>\")";
var printI= document.createElement('iframe');
printI.name = "printIframe";
printI.id = strFrameName;
printI.className = "MSIE";
document.body.appendChild(printI);
printI.src = iframeSrc;
} else {
// other browsers inherit document.domain, and IE works if document.domain is not explicitly set
var $frame = $("<iframe id='" + strFrameName +"' name='printIframe' />");
$frame.appendTo("body");
}
var $iframe = $("#" + strFrameName);
// show frame if in debug mode
if (!opt.debug) $iframe.css({
position: "absolute",
width: "0px",
height: "0px",
left: "-600px",
top: "-600px"
});
// $iframe.ready() and $iframe.load were inconsistent between browsers
setTimeout ( function () {
var $doc = $iframe.contents();
// import page stylesheets
if (opt.importCSS) $("link[rel=stylesheet]").each(function () {
var href = $(this).attr("href");
if (href) {
var media = $(this).attr("media") || "all";
$doc.find("head").append("<link type='text/css' rel='stylesheet' href='" + href + "' media='" + media + "'>")
}
});
//add title to iframe
if (opt.pageTitle) $doc.find("head").append("<title>" + opt.pageTitle + "</title>");
// import additional stylesheet
if (opt.loadCSS) $doc.find("head").append("<link type='text/css' rel='stylesheet' href='" + opt.loadCSS + "'>");
// grab $.selector as container
if (opt.printContainer) $doc.find("body").append($element.outer());
// otherwise just print interior elements of container
else $element.each(function () {
$doc.find("body").append($(this).html())
});
if($iframe.hasClass("MSIE")){
// check if the iframe was created with the ugly hack
// and perform another ugly hack out of neccessity
window.frames["printIframe"].focus();
setTimeout(function () {
$doc.find("head").append("<script> window.print(); </script>");
}, 500 );
} else {
// proper method
$iframe[0].contentWindow.focus();
$iframe[0].contentWindow.print();
}
//remove iframe after print
if (!opt.debug) {
setTimeout(function () {
$iframe.remove();
}, 1000);
}
}, 333 );
};
// defaults
$.fn.printThis.defaults = {
debug: false, // show the iframe for debugging
importCSS: true, // import parent page css
printContainer: true, // print outer container/$.selector
loadCSS: "", // load an additional css file
pageTitle: "" // add title to print page
};
// $.selector container
jQuery.fn.outer = function () {
return $($("<div></div>").html(this.clone())).html()
}
})(jQuery);