Je suis l'auteur de printThis, un plugin JQuery pour l'impression.
https://github.com/jasonday/printThis
J'ai un utilisateur qui a soulevé un problème, que je n'ai pas pu résoudre et malheureusement, je ne peux pas partager la page (problèmes de confidentialité).
Sur le site de l'utilisateur, le problème apparaît sur certaines pages d'IE, mais pas sur d'autres. L'impression échoue car l'iframe reste vide.
L'erreur dans IE est dans jQuery:
contents: function (a) {
return f.nodeName(a,
"iframe") ? a.contentDocument || a.contentWindow.document : f.makeArray(a.childNodes)
}
À l'aide de la journalisation, j'ai pu déterminer qu'il échouait autour de cette ligne:
var $doc = $("#" + strFrameName).contents();
Mais encore une fois, cela ne se produit que sur certaines pages et je n'ai pas été en mesure de le recréer en dehors du site de cet utilisateur.
Ma question: Y a-t-il une meilleure approche ici? ou une méthode pour rendre l'objet $doc
plus résistant aux balles?
// -----------------------------------------------------------------------
// 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);
METTRE &AGRAVE; JOUR
Problème à cause de document.domain
Ce type de page a document.domain
défini et IE n'hérite pas de document.domain
du parent.
Pour corriger cette partie, j'ai modifié la création d'iframe en javascript standard et paramétré la source pour écrire document.domain
lors de la création d'iframe.
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);
Donc, cela corrige l'accès refusé, mais maintenant, le cadre ne sera pas imprimé. J'ai essayé beaucoup de méthodes différentes pour accéder à l'objet, mais aucune d'entre elles ne fonctionne.
A) comment accéder au cadre dans ce scénario (j'ai essayé la plupart des méthodes décrites dans SO) pour que IE soit reconnu et imprimé
ou
B) Quelqu'un peut-il penser à un meilleur moyen de placer le document.domain dans l'iframe lors de la création avec jQuery? (ne peut pas être après, car le problème d'accès refusé sera soulevé)
Le problème est dû au fait que IE n'hérite pas du document.domain parent.
Malheureusement, une fois que vous avez pénétré dans cette zone trouble, il a fallu quelques piratages spécifiques pour que cela fonctionne correctement.
Effectuer une vérification pour savoir si document.domain est explicitement défini et si le navigateur est IE.
Plugin complètement mis à jour:
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);
Dans votre code, vous utilisez setTimeout
pour exécuter votre fonction après le chargement de l'iframe.
// allow iframe to fully render before action
setTimeout ( function () {
...
}, 333 ); //333ms
mais ceci est une erreur car vous ne savez pas si le temps imparti est suffisant pour charger l’iframe ou non. L'exécution de Javascript est asynchrone, donc rien ne garantit que setTimeout
décale l'exécution de la fonction jusqu'au chargement de l'iframe. Depuis le temps de chargement est différent pour différentes pages. Certains ne peuvent pas exécuter le code correctement, pointant vers la ligne que vous trouvez être à l'origine des erreurs.
var $doc = $("#" + strFrameName).contents(); //only after loading
La méthode correcte consiste à utiliser l'événement load
ou onload
pour savoir si l'objet DOM a été chargé correctement ou non.
<script>
document.getElementById("myframe").onload = function() {
alert("myframe is loaded");
};
</script>
//or
<iframe id="myFrame" onload="myFunction();"></iframe>
Tant que vous définissez iframe src, la même origine doit être vérifiée par rapport à l'élément parent, même si vous la définissez comme 'about: blank'. J'imagine que IE échoue lors de la vérification correcte ou que du javascript a été exécuté et que le document.location est défini sur un emplacement différent de celui de l'iframe créé.
Que diriez-vous de ne pas définir src du tout comme suit? ça devrait quand même marcher.
var $iframe = $("<iframe id='" + strFrameName +"'/>");
$iframe.appendTo("body");
var $iframeDoc = $iframe[0].contentWindow.document;
$iframeDoc.open();
$iframeDoc.write("foo");
$iframeDoc.close();
Cette réponse a déjà été énoncée dans la question initiale UPDATE, mais je voulais ajouter une réponse plus succincte à la question initiale relative à la contournement de l'erreur d'autorisation refusée SCRIPT70 (je l'ai rencontré sur IE11/Win7 avec JQuery 3.2.1).
Au lieu de $('<iframe .../>').appendTo($('body'))
Faire ceci:
var $iframe = $('<iframe .../>');
document.body.appendChild($iframe[0]);
Réponse tirée d'ici: https://bugs.jquery.com/ticket/13936#comment:28
IE fonctionne avec iframe comme tous les autres navigateurs (du moins pour les fonctions principales). Vous devez juste garder un ensemble de règles:
lorsque toutes les ressources iframe sont chargées, changez document.domain pour qu'il soit identique à celui défini dans parent. (Vous devez le faire ultérieurement car la définition de domaine entraînera l'échec de la demande de la ressource iframe)
vous pouvez maintenant faire une référence pour la fenêtre parente: var winn = window.parent