最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Print function only works after second click - Stack Overflow

programmeradmin0浏览0评论

I have this function to print a DIV.
Whenever the page is loaded and I click in a "Print" link I have, the DIV is shown to be printed without CSS.
If I close Chrome's print visualization page and click in the "Print" link again, the DIV has CSS applied.

Any ideas why?

Javascript

function printDiv(divId) {

  var printDivCSSpre =
'<link href="/static/assets/vendor/sb-admin-2-1.0.7/bower_ponents/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">' +
'<link href="/static/assets/vendor/sb-admin-2-1.0.7/dist/css/sb-admin-2.css" rel="stylesheet">' +
'<div style="width:1000px; padding-right:20px;">';

  var printDivCSSpost = '</div>';

  $('body').append('<iframe id="print_frame" name="print_frame" width="0" height="0" frameborder="0" src="about:blank"></iframe>');

    $("link").clone().appendTo($("#print_frame").contents().find("head"));
window.frames["print_frame"].document.body.innerHTML =
    printDivCSSpre + document.getElementById(divId).innerHTML + printDivCSSpost;
  window.frames["print_frame"].window.focus();
  var windowInstance = window.frames["print_frame"].window;
  windowInstance.print();
}

HTML

<a id="print" href="#">
    <i class="fa fa-print"></i> Print
</a>
<script>
    $('#print').click(function () {
        printDiv('report')
    })
</script>

<div id="report" class="report">
    <p># Generated Table#</p>
</div>

First click:

.jpg

Closing the print preview page and clicking again in print

.jpg

I have this function to print a DIV.
Whenever the page is loaded and I click in a "Print" link I have, the DIV is shown to be printed without CSS.
If I close Chrome's print visualization page and click in the "Print" link again, the DIV has CSS applied.

Any ideas why?

Javascript

function printDiv(divId) {

  var printDivCSSpre =
'<link href="/static/assets/vendor/sb-admin-2-1.0.7/bower_ponents/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">' +
'<link href="/static/assets/vendor/sb-admin-2-1.0.7/dist/css/sb-admin-2.css" rel="stylesheet">' +
'<div style="width:1000px; padding-right:20px;">';

  var printDivCSSpost = '</div>';

  $('body').append('<iframe id="print_frame" name="print_frame" width="0" height="0" frameborder="0" src="about:blank"></iframe>');

    $("link").clone().appendTo($("#print_frame").contents().find("head"));
window.frames["print_frame"].document.body.innerHTML =
    printDivCSSpre + document.getElementById(divId).innerHTML + printDivCSSpost;
  window.frames["print_frame"].window.focus();
  var windowInstance = window.frames["print_frame"].window;
  windowInstance.print();
}

HTML

<a id="print" href="#">
    <i class="fa fa-print"></i> Print
</a>
<script>
    $('#print').click(function () {
        printDiv('report')
    })
</script>

<div id="report" class="report">
    <p># Generated Table#</p>
</div>

First click:

https://i.sstatic/G6RU4.jpg

Closing the print preview page and clicking again in print

https://i.sstatic/NLQqS.jpg

Share Improve this question edited Feb 15, 2017 at 4:03 Denis Tsoi 10.4k8 gold badges41 silver badges58 bronze badges asked Feb 6, 2017 at 13:08 Ev.Ev. 1,5671 gold badge24 silver badges49 bronze badges 9
  • A full working example would go a long way towards helping us help you. – lleaff Commented Feb 6, 2017 at 13:10
  • OK. Just added. – Ev. Commented Feb 6, 2017 at 13:32
  • Could you please make a fiddle? Maybe then it would be easier for us to assist... – Jan Legner Commented Feb 9, 2017 at 18:21
  • jsfiddle/ytzcwykz/6 – Ev. Commented Feb 13, 2017 at 13:43
  • 1 Use inline CSS instead. Reason: When we PRINT or save as PDF if fails to fetch external css Files, So we have to use Inline css – Ashish Yadav Commented Feb 15, 2017 at 7:55
 |  Show 4 more ments

6 Answers 6

Reset to default 2

This happens because when you call your printDiv() function, css is also written using inner HTML and in this scenario CSS is not applied during first click because you wrote CSS to the elements even when they do not exist inside DIV.

The function to work as desired has to write DIV contents first and then CSS should be applied. I would say write css after contents of DIV or load on top of your HTML page and just write DIV contents.

Hope that helps.

Every thing is right just change the sequence. In browser debugger on first click it didn't show 'print_frame' in sources section while in second click it does (I am using chrome devtool).

So load in memory frame with css attributes during onload:

var windowInstance;
$(function(){
    $('body').append('<iframe id="print_frame" name="print_frame" width="0" height="0" frameborder="0" src="about:blank"></iframe>');
    $("link").clone().appendTo($("#print_frame").contents().find("head"));
    windowInstance = window.frames["print_frame"].window;

});

and onClick just append html

$('#print').click(function () {
    var divId = 'report';
    var printDivCSSpre ='<div id="printReportDiv" style="width:1000px; padding-right:20px;">';
    var printDivCSSpost = '</div>';

    window.frames["print_frame"].document.body.innerHTML = printDivCSSpre + document.getElementById(divId).innerHTML + printDivCSSpost;
    window.frames["print_frame"].window.focus();
    windowInstance.print();

});

updated jsfiddle

Try this one. The problem mainly arises because the css has not been applied to the page when the print mand is initiated. setTimeout is one way to solve it as others have mentioned but it is really not possible to predict how much delay you will need. Slow internet connections will require high delays before you fire the print statement. The following code, however, only fires the print event after the css has been properly applied to the iframe.

$('#print').click(function () {

    if($("#print_frame").length == 0) {
        $('#report').after('<iframe id="print_frame" name="print_frame" width="0" height="0" frameborder="0" src="about:blank"></iframe>');
    }

    var $head = $("#print_frame").contents().find("head");

        // for now for ease I will just empty head
        // ideally you would want to check if this is not empty
        // append css only if empty
        $head.empty();
        $.ajax({
            url : "https://dl.dropboxusercontent./u/7760475/reports.css",
            dataType: "text",
            success : function (reports) {
                // grab css and apply its content to the iframe document
                $head.append('<style>'+reports+'</style>');
                $.ajax({
                    url : "https://dl.dropboxusercontent./u/7760475/bootstrap.css",
                    dataType: "text",
                    success : function (bootstrap) {
                        // grab another css and apply its content to the iframe document
                        // there may be better ways to load both css files at once but this works fine too
                        $head.append('<style>'+bootstrap+'</style>');
                        // css has been applied
                        // clone your div and print
                        var $body = $("#print_frame").contents().find('body');
                            // empty for ease
                            // but later append content only if empty
                            $body.empty();

                        $("#report").clone().appendTo($body);
                        $('#print_frame').get(0).contentWindow.print();
                    }
                });
            }
        });
});

Use inline CSS instead. Reason: When we PRINT or save as PDF if fails to fetch external css Files, So we have to use Inline css. edited your file please see: jsfiddle/ytzcwykz/18/

As other people mentioned it is hard to see your problem without seeing the working example of a problem, but just guessing from the code:

  1. Browser is not able to load the CSS before your print() call.
  2. Browser is not able to render the CSS before your print() call.

Keeping that in mind changing your JS function that way might do the trick

function printDiv(divId) {
    $("link").clone().appendTo($("#print_frame").contents().find("head"));
    window.frames["print_frame"].document.body.innerHTML =
        printDivCSSpre + document.getElementById(divId).innerHTML + printDivCSSpost;
    window.frames["print_frame"].window.focus();
    var windowInstance = window.frames["print_frame"].window;
    setTimeout(function() {
        windowInstance.print();
    }, 0);
}

The idea behind this function is to let browser execute it's code after we added changed the HTML/CSS code in the window - see Why is setTimeout(fn, 0) sometimes useful?

WARNING: this approach is not tested for your particular problem, and it might also not work because we escape/leave the mouse-click call-stack, calling print() method might be not possible out of user-interaction stack.

UPDATE: after looking in the posted jsfiddle - my assumption was correct, the browser needs some time to load and render the CSS, that is why calling the print() right after changing iframe contents doesn't give the desired result. There are 3.5 ways to solve that:

  1. Use events to identify when iframe's document and window has finished loading and rendering. I tried two approaches, and failed so far, need to read docs more carefully about when document and window are behiving during the loading sequence:
    • we can do that from outside of iframe, i.e. listen to events of iframe element and it's children
    • we can do that from inside of iframe, i.e. add little javascript snippet inside which will send a message to the parent window when loading is done.
  2. Consider forming the print result different, how about print style-sheets? I.e. add one more style sheet with print-media query to the parent doc and just call print on it?
  3. Consider forming an iframe which is already loaded and ready to be printed, but replace just the table contents inside it.

As others mentioned, The problem here is that the CSS files used are external resources and browser takes time to download and cache it locally. Once it is cached, it would serve faster and that's why it works fine from the second click.

As Anton mentioned, setTimeout is the key here! You may probably increase the timeout seconds to make that work. I tried setting it to 500ms and that worked,

setTimeout(function(){windowInstance.print();},500);
发布评论

评论列表(0)

  1. 暂无评论