13

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)

4

5 に答える 5

7

コードではsetTimeout、iframe が読み込まれた後に関数を実行するために使用しています。

// allow iframe to fully render before action
setTimeout ( function () {
...
}, 333 );  //333ms

しかし、与えられた時間が iframe をロードするのに十分かどうかわからないので、これは間違いです。Javascript の実行は非同期であるため、setTimeoutiframe が読み込まれるまで関数の実行がオフセットされる保証はありません。ロード時間はページごとに異なるため。コードを正しく実行できないものもあり、エラーの原因となっている行を指しています。

var $doc = $("#" + strFrameName).contents();  //only after loading

正しい方法は、イベントを使用するか、DOM オブジェクトが正しく読み込まれたかどうloadonloadを知ることです。

<script>
document.getElementById("myframe").onload = function() {
  alert("myframe is loaded");
};
</script>
//or
<iframe id="myFrame" onload="myFunction();"></iframe>
于 2013-02-24T17:23:33.197 に答える
5

iframe srcを設定している限り、「about:blank」として設定した場合でも、同じオリジンを親要素に対して検証する必要があります。IEが適切なチェックに失敗するか、JavaScriptが実行され、document.locationをiframeが作成されたものとは異なるものに設定したと思います。

次のようにsrcをまったく設定しないのはどうですか?それでも動作するはずです。

var $iframe = $("<iframe id='" + strFrameName +"'/>");
$iframe.appendTo("body");
var $iframeDoc = $iframe[0].contentWindow.document;

$iframeDoc.open();
$iframeDoc.write("foo");
$iframeDoc.close();
于 2013-02-22T19:45:22.307 に答える
4

この問題は、IE が親の document.domain を継承していないことが原因です。

残念ながら、この暗い領域に入ると、これを適切に機能させるにはいくつかの特定のハックが必要でした.

基本的に、document.domain が明示的に設定されていて、ブラウザーが IE であるかどうかをチェックします。

完全に更新されたプラグイン:

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);
于 2013-03-04T20:09:34.417 に答える
0

IE は、他のすべてのブラウザーと同様に iframe で動作します (少なくとも主要な機能については)。一連のルールを守る必要があります。

  • iframe (iframe の親について知る必要がある js の部分) に JavaScript をロードする前に、親の document.domain が変更されていることを確認してください。
  • すべての iframe リソースが読み込まれたら、document.domain を親で定義されているものと同じになるように変更します。(ドメインを設定すると iframe リソースのリクエストが失敗するため、後で行う必要があります)

  • 親ウィンドウの参照を作成できるようになりました: var winn = window.parent

  • 親 HTML を操作するために、親 HTML への参照を作成できるようになりました: var parentContent = $('html', winn.document)
  • この時点で、IE の親ウィンドウ/ドキュメントにアクセスできる必要があり、必要に応じて変更できます。
于 2016-02-21T17:06:10.467 に答える