| 1 |
/* |
| 2 |
* wpbc_js_print v2.0.0 |
| 3 |
* @desc Printing plug-in for jQuery |
| 4 |
* @author Jason Day |
| 5 |
* @author Samuel Rouse |
| 6 |
* |
| 7 |
* Resources (based on): |
| 8 |
* - jPrintArea: http://plugins.jquery.com/project/jPrintArea |
| 9 |
* - jqPrint: https://github.com/permanenttourist/jquery.jqprint |
| 10 |
* - Ben Nadal: http://www.bennadel.com/blog/1591-Ask-Ben-Print-Part-Of-A-Web-Page-With-jQuery.htm |
| 11 |
* |
| 12 |
* Licensed under the MIT licence: |
| 13 |
* http://www.opensource.org/licenses/mit-license.php |
| 14 |
* |
| 15 |
* (c) Jason Day 2015-2022 |
| 16 |
* |
| 17 |
* Usage: |
| 18 |
* |
| 19 |
* $("#mySelector").wpbc_js_print({ |
| 20 |
* debug: false, // show the iframe for debugging |
| 21 |
* importCSS: true, // import parent page css |
| 22 |
* importStyle: true, // import style tags |
| 23 |
* printContainer: true, // grab outer container as well as the contents of the selector |
| 24 |
* loadCSS: "path/to/my.css", // path to additional css file - use an array [] for multiple |
| 25 |
* pageTitle: "", // add title to print page |
| 26 |
* removeInline: false, // remove all inline styles from print elements |
| 27 |
* removeInlineSelector: "body *", // custom selectors to filter inline styles. removeInline must be true |
| 28 |
* printDelay: 1000, // variable print delay |
| 29 |
* header: null, // prefix to html |
| 30 |
* footer: null, // postfix to html |
| 31 |
* base: false, // preserve the BASE tag, or accept a string for the URL |
| 32 |
* formValues: true, // preserve input/form values |
| 33 |
* canvas: true, // copy canvas elements |
| 34 |
* doctypeString: '...', // enter a different doctype for older markup |
| 35 |
* removeScripts: false, // remove script tags from print content |
| 36 |
* copyTagClasses: true // copy classes from the html & body tag |
| 37 |
* copyTagStyles: true, // copy styles from html & body tag (for CSS Variables) |
| 38 |
* beforePrintEvent: null, // callback function for printEvent in iframe |
| 39 |
* beforePrint: null, // function called before iframe is filled |
| 40 |
* afterPrint: null // function called before iframe is removed |
| 41 |
* }); |
| 42 |
* |
| 43 |
* Notes: |
| 44 |
* - the loadCSS will load additional CSS (with or without @media print) into the iframe, adjusting layout |
| 45 |
*/ |
| 46 |
; |
| 47 |
(function($) { |
| 48 |
|
| 49 |
function appendContent($el, content) { |
| 50 |
if (!content) return; |
| 51 |
|
| 52 |
// Simple test for a jQuery element |
| 53 |
$el.append(content.jquery ? content.clone() : content); |
| 54 |
} |
| 55 |
|
| 56 |
function appendBody($body, $element, opt) { |
| 57 |
// Clone for safety and convenience |
| 58 |
// Calls clone(withDataAndEvents = true) to copy form values. |
| 59 |
var $content = $element.clone(opt.formValues); |
| 60 |
|
| 61 |
if (opt.formValues) { |
| 62 |
// Copy original select and textarea values to their cloned counterpart |
| 63 |
// Makes up for inability to clone select and textarea values with clone(true) |
| 64 |
copyValues($element, $content, 'select, textarea'); |
| 65 |
} |
| 66 |
|
| 67 |
if (opt.removeScripts) { |
| 68 |
$content.find('script').remove(); |
| 69 |
} |
| 70 |
|
| 71 |
if (opt.printContainer) { |
| 72 |
// grab $.selector as container |
| 73 |
$content.appendTo($body); |
| 74 |
} else { |
| 75 |
// otherwise just print interior elements of container |
| 76 |
$content.each(function() { |
| 77 |
$(this).children().appendTo($body) |
| 78 |
}); |
| 79 |
} |
| 80 |
} |
| 81 |
|
| 82 |
// Copies values from origin to clone for passed in elementSelector |
| 83 |
function copyValues(origin, clone, elementSelector) { |
| 84 |
var $originalElements = origin.find(elementSelector); |
| 85 |
|
| 86 |
clone.find(elementSelector).each(function(index, item) { |
| 87 |
$(item).val($originalElements.eq(index).val()); |
| 88 |
}); |
| 89 |
} |
| 90 |
|
| 91 |
var opt; |
| 92 |
$.fn.wpbc_js_print = function(options) { |
| 93 |
opt = $.extend({}, $.fn.wpbc_js_print.defaults, options); |
| 94 |
var $element = this instanceof jQuery ? this : $(this); |
| 95 |
|
| 96 |
var strFrameName = "wpbc_js_print-" + (new Date()).getTime(); |
| 97 |
|
| 98 |
if (window.location.hostname !== document.domain && navigator.userAgent.match(/msie/i)) { |
| 99 |
// Ugly IE hacks due to IE not inheriting document.domain from parent |
| 100 |
// checks if document.domain is set by comparing the host name against document.domain |
| 101 |
var iframeSrc = "javascript:document.write(\"<head><script>document.domain=\\\"" + document.domain + "\\\";</s" + "cript></head><body></body>\")"; |
| 102 |
var printI = document.createElement('iframe'); |
| 103 |
printI.name = "printIframe"; |
| 104 |
printI.id = strFrameName; |
| 105 |
printI.className = "MSIE"; |
| 106 |
document.body.appendChild(printI); |
| 107 |
printI.src = iframeSrc; |
| 108 |
|
| 109 |
} else { |
| 110 |
// other browsers inherit document.domain, and IE works if document.domain is not explicitly set |
| 111 |
var $frame = $("<iframe id='" + strFrameName + "' name='printIframe' />"); |
| 112 |
$frame.appendTo("body"); |
| 113 |
} |
| 114 |
|
| 115 |
var $iframe = $("#" + strFrameName); |
| 116 |
|
| 117 |
// show frame if in debug mode |
| 118 |
if (!opt.debug) $iframe.css({ |
| 119 |
position: "absolute", |
| 120 |
width: "0px", |
| 121 |
height: "0px", |
| 122 |
left: "-600px", |
| 123 |
top: "-600px" |
| 124 |
}); |
| 125 |
|
| 126 |
// before print callback |
| 127 |
if (typeof opt.beforePrint === "function") { |
| 128 |
opt.beforePrint(); |
| 129 |
} |
| 130 |
|
| 131 |
// $iframe.ready() and $iframe.load were inconsistent between browsers |
| 132 |
setTimeout(function() { |
| 133 |
|
| 134 |
// Add doctype to fix the style difference between printing and render |
| 135 |
function setDocType($iframe, doctype){ |
| 136 |
var win, doc; |
| 137 |
win = $iframe.get(0); |
| 138 |
win = win.contentWindow || win.contentDocument || win; |
| 139 |
doc = win.document || win.contentDocument || win; |
| 140 |
doc.open(); |
| 141 |
doc.write(doctype); |
| 142 |
doc.close(); |
| 143 |
} |
| 144 |
|
| 145 |
if (opt.doctypeString){ |
| 146 |
setDocType($iframe, opt.doctypeString); |
| 147 |
} |
| 148 |
|
| 149 |
var $doc = $iframe.contents(), |
| 150 |
$head = $doc.find("head"), |
| 151 |
$body = $doc.find("body"), |
| 152 |
$base = $('base'), |
| 153 |
baseURL; |
| 154 |
|
| 155 |
// add base tag to ensure elements use the parent domain |
| 156 |
if (opt.base === true && $base.length > 0) { |
| 157 |
// take the base tag from the original page |
| 158 |
baseURL = $base.attr('href'); |
| 159 |
} else if (typeof opt.base === 'string') { |
| 160 |
// An exact base string is provided |
| 161 |
baseURL = opt.base; |
| 162 |
} else { |
| 163 |
// Use the page URL as the base |
| 164 |
baseURL = document.location.protocol + '//' + document.location.host; |
| 165 |
} |
| 166 |
|
| 167 |
$head.append('<base href="' + baseURL + '">'); |
| 168 |
|
| 169 |
// import page stylesheets |
| 170 |
if (opt.importCSS) $("link[rel=stylesheet]").each(function() { |
| 171 |
var href = $(this).attr("href"); |
| 172 |
if (href) { |
| 173 |
var media = $(this).attr("media") || "all"; |
| 174 |
$head.append("<link type='text/css' rel='stylesheet' href='" + href + "' media='" + media + "'>"); |
| 175 |
} |
| 176 |
}); |
| 177 |
|
| 178 |
// import style tags |
| 179 |
if (opt.importStyle) $("style").each(function() { |
| 180 |
$head.append(this.outerHTML); |
| 181 |
}); |
| 182 |
|
| 183 |
// add title of the page |
| 184 |
if (opt.pageTitle) $head.append("<title>" + opt.pageTitle + "</title>"); |
| 185 |
|
| 186 |
// import additional stylesheet(s) |
| 187 |
if (opt.loadCSS) { |
| 188 |
if ($.isArray(opt.loadCSS)) { |
| 189 |
jQuery.each(opt.loadCSS, function(index, value) { |
| 190 |
$head.append("<link type='text/css' rel='stylesheet' href='" + this + "'>"); |
| 191 |
}); |
| 192 |
} else { |
| 193 |
$head.append("<link type='text/css' rel='stylesheet' href='" + opt.loadCSS + "'>"); |
| 194 |
} |
| 195 |
} |
| 196 |
|
| 197 |
var pageHtml = $('html')[0]; |
| 198 |
|
| 199 |
// CSS VAR in html tag when dynamic apply e.g. document.documentElement.style.setProperty("--foo", bar); |
| 200 |
$doc.find('html').prop('style', pageHtml.style.cssText); |
| 201 |
|
| 202 |
// copy 'root' tag classes |
| 203 |
var tag = opt.copyTagClasses; |
| 204 |
if (tag) { |
| 205 |
tag = tag === true ? 'bh' : tag; |
| 206 |
if (tag.indexOf('b') !== -1) { |
| 207 |
$body.addClass($('body')[0].className); |
| 208 |
} |
| 209 |
if (tag.indexOf('h') !== -1) { |
| 210 |
$doc.find('html').addClass(pageHtml.className); |
| 211 |
} |
| 212 |
} |
| 213 |
|
| 214 |
// copy ':root' tag classes |
| 215 |
tag = opt.copyTagStyles; |
| 216 |
if (tag) { |
| 217 |
tag = tag === true ? 'bh' : tag; |
| 218 |
if (tag.indexOf('b') !== -1) { |
| 219 |
$body.attr('style', $('body')[0].style.cssText); |
| 220 |
} |
| 221 |
if (tag.indexOf('h') !== -1) { |
| 222 |
$doc.find('html').attr('style', pageHtml.style.cssText); |
| 223 |
} |
| 224 |
} |
| 225 |
|
| 226 |
// print header |
| 227 |
appendContent($body, opt.header); |
| 228 |
|
| 229 |
if (opt.canvas) { |
| 230 |
// add canvas data-ids for easy access after cloning. |
| 231 |
var canvasId = 0; |
| 232 |
// .addBack('canvas') adds the top-level element if it is a canvas. |
| 233 |
$element.find('canvas').addBack('canvas').each(function(){ |
| 234 |
$(this).attr('data-printthis', canvasId++); |
| 235 |
}); |
| 236 |
} |
| 237 |
|
| 238 |
appendBody($body, $element, opt); |
| 239 |
|
| 240 |
if (opt.canvas) { |
| 241 |
// Re-draw new canvases by referencing the originals |
| 242 |
$body.find('canvas').each(function(){ |
| 243 |
var cid = $(this).data('printthis'), |
| 244 |
$src = $('[data-printthis="' + cid + '"]'); |
| 245 |
|
| 246 |
this.getContext('2d').drawImage($src[0], 0, 0); |
| 247 |
|
| 248 |
// Remove the markup from the original |
| 249 |
if ($.isFunction($.fn.removeAttr)) { |
| 250 |
$src.removeAttr('data-printthis'); |
| 251 |
} else { |
| 252 |
$.each($src, function(i, el) { |
| 253 |
el.removeAttribute('data-printthis'); |
| 254 |
}); |
| 255 |
} |
| 256 |
}); |
| 257 |
} |
| 258 |
|
| 259 |
// remove inline styles |
| 260 |
if (opt.removeInline) { |
| 261 |
// Ensure there is a selector, even if it's been mistakenly removed |
| 262 |
var selector = opt.removeInlineSelector || '*'; |
| 263 |
// $.removeAttr available jQuery 1.7+ |
| 264 |
if ($.isFunction($.removeAttr)) { |
| 265 |
$body.find(selector).removeAttr("style"); |
| 266 |
} else { |
| 267 |
$body.find(selector).attr("style", ""); |
| 268 |
} |
| 269 |
} |
| 270 |
|
| 271 |
// print "footer" |
| 272 |
appendContent($body, opt.footer); |
| 273 |
|
| 274 |
// attach event handler function to beforePrint event |
| 275 |
function attachOnBeforePrintEvent($iframe, beforePrintHandler) { |
| 276 |
var win = $iframe.get(0); |
| 277 |
win = win.contentWindow || win.contentDocument || win; |
| 278 |
|
| 279 |
if (typeof beforePrintHandler === "function") { |
| 280 |
if ('matchMedia' in win) { |
| 281 |
win.matchMedia('print').addListener(function(mql) { |
| 282 |
if(mql.matches) beforePrintHandler(); |
| 283 |
}); |
| 284 |
} else { |
| 285 |
win.onbeforeprint = beforePrintHandler; |
| 286 |
} |
| 287 |
} |
| 288 |
} |
| 289 |
attachOnBeforePrintEvent($iframe, opt.beforePrintEvent); |
| 290 |
|
| 291 |
setTimeout(function() { |
| 292 |
if ($iframe.hasClass("MSIE")) { |
| 293 |
// check if the iframe was created with the ugly hack |
| 294 |
// and perform another ugly hack out of neccessity |
| 295 |
window.frames["printIframe"].focus(); |
| 296 |
$head.append("<script> window.print(); </s" + "cript>"); |
| 297 |
} else { |
| 298 |
// proper method |
| 299 |
if (document.queryCommandSupported("print")) { |
| 300 |
$iframe[0].contentWindow.document.execCommand("print", false, null); |
| 301 |
} else { |
| 302 |
$iframe[0].contentWindow.focus(); |
| 303 |
$iframe[0].contentWindow.print(); |
| 304 |
} |
| 305 |
} |
| 306 |
|
| 307 |
// remove iframe after print |
| 308 |
if (!opt.debug) { |
| 309 |
setTimeout(function() { |
| 310 |
$iframe.remove(); |
| 311 |
|
| 312 |
}, 1000); |
| 313 |
} |
| 314 |
|
| 315 |
// after print callback |
| 316 |
if (typeof opt.afterPrint === "function") { |
| 317 |
opt.afterPrint(); |
| 318 |
} |
| 319 |
|
| 320 |
}, opt.printDelay); |
| 321 |
|
| 322 |
}, 333); |
| 323 |
|
| 324 |
}; |
| 325 |
|
| 326 |
// defaults |
| 327 |
$.fn.wpbc_js_print.defaults = { |
| 328 |
debug: false, // show the iframe for debugging |
| 329 |
importCSS: true, // import parent page css |
| 330 |
importStyle: true, // import style tags |
| 331 |
printContainer: true, // print outer container/$.selector |
| 332 |
loadCSS: "", // path to additional css file - use an array [] for multiple |
| 333 |
pageTitle: "", // add title to print page |
| 334 |
removeInline: false, // remove inline styles from print elements |
| 335 |
removeInlineSelector: "*", // custom selectors to filter inline styles. removeInline must be true |
| 336 |
printDelay: 1000, // variable print delay |
| 337 |
header: null, // prefix to html |
| 338 |
footer: null, // postfix to html |
| 339 |
base: false, // preserve the BASE tag or accept a string for the URL |
| 340 |
formValues: true, // preserve input/form values |
| 341 |
canvas: true, // copy canvas content |
| 342 |
doctypeString: '<!DOCTYPE html>', // enter a different doctype for older markup |
| 343 |
removeScripts: false, // remove script tags from print content |
| 344 |
copyTagClasses: true, // copy classes from the html & body tag |
| 345 |
copyTagStyles: true, // copy styles from html & body tag (for CSS Variables) |
| 346 |
beforePrintEvent: null, // callback function for printEvent in iframe |
| 347 |
beforePrint: null, // function called before iframe is filled |
| 348 |
afterPrint: null // function called before iframe is removed |
| 349 |
}; |
| 350 |
})(jQuery); |
| 351 |
|