printThis.js 14 KB

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