61

I'm developing a site using Bootstrap which has 28 modal windows with information on different products. I want to be able to print the information in an open modal window. Each window has an id.

<!-- firecell panel & radio hub -->
           <div class="modal hide fade" id="fcpanelhub">
              <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal">X</button>
                <h3>5000 Control Panel & Radio Hub</h3>
              </div>
              <div class="modal-body">
                <img src="../site/img/firecell/firecell-panel-info-1.png" alt=""/><hr/>
                <img src="../site/img/firecell/firecell-panel-info-2.png" alt=""/><hr/>
                <img src="../site/img/firecell/firecell-radio-hub-info-1.png" alt=""/><hr/>
                <img src="../site/img/firecell/firecell-radio-hub-info-2.png" alt=""/>
              </div>
              <div class="modal-footer">
                <a href="#" class="btn" data-dismiss="modal">Close</a>
              </div>    
           </div>

So if I add in a new button in modal-footer - 'print', and it's clicked I want that modal to print. Would I be right in saying javascript would be used? If so, how do I tell javascript to print only the open modal, and not the others?

All help appreciated.

MattSull
  • 5,514
  • 5
  • 46
  • 68
  • I had no end of trouble with JS-based solutions. Here is a CSS-only solution that worked pretty well for me with Bootstrap 4 Alpha. Tested in Chrome, FF, IE, Edge. https://gist.github.com/anonymous/2d81209429ae4240d1e9711cc5f86c99 – nw. Oct 16 '17 at 20:38

11 Answers11

80

Another solution

Here is a new solution based on Bennett McElwee answer in the same question as mentioned below.

Tested with IE 9 & 10, Opera 12.01, Google Chrome 22 and Firefox 15.0.
jsFiddle example

1.) Add this CSS to your site:

@media screen {
  #printSection {
      display: none;
  }
}

@media print {
  body * {
    visibility:hidden;
  }
  #printSection, #printSection * {
    visibility:visible;
  }
  #printSection {
    position:absolute;
    left:0;
    top:0;
  }
}

2.) Add my JavaScript function

function printElement(elem, append, delimiter) {
    var domClone = elem.cloneNode(true);

    var $printSection = document.getElementById("printSection");

    if (!$printSection) {
        $printSection = document.createElement("div");
        $printSection.id = "printSection";
        document.body.appendChild($printSection);
    }

    if (append !== true) {
        $printSection.innerHTML = "";
    }

    else if (append === true) {
        if (typeof (delimiter) === "string") {
            $printSection.innerHTML += delimiter;
        }
        else if (typeof (delimiter) === "object") {
            $printSection.appendChild(delimiter);
        }
    }

    $printSection.appendChild(domClone);
}​

You're ready to print any element on your site!
Just call printElement() with your element(s) and execute window.print() when you're finished.

Note: If you want to modify the content before it is printed (and only in the print version), checkout this example (provided by waspina in the comments): http://jsfiddle.net/95ezN/121/

One could also use CSS in order to show the additional content in the print version (and only there).


Former solution

I think, you have to hide all other parts of the site via CSS.

It would be the best, to move all non-printable content into a separate DIV:

<body>
  <div class="non-printable">
    <!-- ... -->
  </div>

  <div class="printable">
    <!-- Modal dialog comes here -->
  </div>
</body>

And then in your CSS:

.printable { display: none; }

@media print
{
    .non-printable { display: none; }
    .printable { display: block; }
}

Credits go to Greg who has already answered a similar question: Print <div id="printarea"></div> only?

There is one problem in using JavaScript: the user cannot see a preview - at least in Internet Explorer!

swbradshaw
  • 874
  • 9
  • 15
ComFreek
  • 29,044
  • 18
  • 104
  • 156
  • 1
    But what if you have a modal popup with printable area over the original printable area ? – maxisam Aug 29 '12 at 16:01
  • Okay I understand what you're doing with the printable and non-printable DIV. Maybe I should have been clearer in my question: Would I have to write a separate JS `onClick()` function for every modal window, or is there a way where one function can handle all the modals? - if 'print' is clicked for modal A, print the printable area for modal A, if modal B, print the printable area for modal B, and so on. – MattSull Aug 29 '12 at 16:02
  • @maxisam Just put the modal DIV into the `printable` wrapper. That's no problem because the modal dialog (probably) uses `position: absolute, ...`. @MattSull87: You have change the CSS classes dynamically via JavaScript. – ComFreek Aug 29 '12 at 16:04
  • But what if the modal doesn't cover the whole background print area ? well, I guess you would use JS to remove unwanted printable area. – maxisam Aug 29 '12 at 16:06
  • @ComFreek would have links to any resources on how to do this? or even a quick example? – MattSull Aug 29 '12 at 17:20
  • 2
    @MattSull87 I've added another solution - this should work better! Here is an example: http://jsfiddle.net/95ezN/1/ – ComFreek Aug 29 '12 at 19:49
  • I think you have put a lot of effort, and it is a good solution for sure. However, I just don't understand the difference between your 2nd solution and mine. They both need JS. BTW, print element plugin can do something more like let you load different CSS to overwrite the current CSS for the printing part. – maxisam Aug 30 '12 at 16:09
  • @maxisam My second solution does not need JavaScript (unless you want a button which triggers printing). Also, the user can see a print preview. (BTW if the user users Google Chrome, he can always see a print preview!). But you're right - if you use my first solution, you could also use that jQuery plugin! – ComFreek Aug 31 '12 at 08:15
  • In the former solution, if `non-printable` is a class, why are you using `#` in CSS? Isn't `.` (point) the solution? – Bagata May 18 '13 at 01:23
  • You're welcome, but you have forgotten to change `#printable { display: none; }` to `.printable { display: none; }`. – Bagata May 18 '13 at 16:09
  • @Bagata Thanks again ;) BTW, anyone is free to edit posts here thus if you see such minor mistakes, just correct them. – ComFreek May 19 '13 at 11:51
  • 1
    @waspinator My "print" element (the element you supply to `printElement()`) ands its children are nothing special. Just use `getElementById()` or `querySelector[All]()`. – ComFreek Apr 29 '14 at 16:11
  • @ComFreek. sorry I changed my question slightly. how would you access a child element within the print element and modify it before printing? I need to rerender a child since it may be resized during printing. – waspinator Apr 29 '14 at 16:15
  • 1
    @waspinator Call `printElement(yourContainer);`, modify your child element and eventually call `window.print()`. `printElement()` only prepares the printing process, but it does not trigger the print itself. – ComFreek Apr 29 '14 at 16:18
  • @ComFreek, sorry could you see what I'm doing wrong? I seem to be modifying the original document instead of the print version. what do I call getElementByID() on? http://jsfiddle.net/95ezN/119/ – waspinator Apr 29 '14 at 16:31
  • @waspinator [Let us continue the discussion in chat](http://chat.stackoverflow.com/rooms/51696/discussion-on-q-12181825-print-modal-window). – ComFreek Apr 29 '14 at 16:39
  • @ComFreek Hi.. This is ur implementation http://jsfiddle.net/neerajswarnkar/RcWKL/ .. What if I want to add some title on page and if I want to show some images and maps in print page.. Please tell me c!! – Javascript Coder May 14 '14 at 05:54
  • @CrazyAboutJavascript [Here](http://jsfiddle.net/95ezN/125/) is a fairly easy solution with a CSS class (.printOnly). I would be careful to use this without prior consideration of SEO effects. (What does Google think about the hidden h2?) An alternative solution is to use JS to dynamically (i.e. directly before printing, [example](http://jsfiddle.net/95ezN/121/)) insert elements. – ComFreek May 14 '14 at 14:44
  • @ComFreek +1 for your solution and Thanks for the same .. :) – Javascript Coder May 15 '14 at 06:07
  • Putting the @ media print in the css seems to affect the base page not just the modal. Meaning if the modal isn't visible and someone tries to print, its empty. Is it possible to enable/disable the @ media styles to only be in affect *IF* the modal is present? – Bennett Dill Apr 20 '15 at 21:59
  • 1
    @BennettDill You could add a class to via JavaScript if the modal is currently shown: `if (modalShown) { body.classList.add('printModal'); }`. Then, you add `body.printModal` to the CSS rules, so they only take effect when you're actually printing the modal dialog. – ComFreek Apr 21 '15 at 06:29
  • I have a long content in a modal popup, when I try to print it i can see that it is printed just the first part repeated equally in 8 different pages. Any idea why the rest is lost and the initial part is repeated in 8 pages? The only interesting thing is that it fully fills the printing page space as it was the limit and then it is repeated everytihg in the following one and so on. – fede72bari Apr 06 '20 at 16:21
  • this doesn't account if the user just hits CTRL+P on the page – Vincent Tang Jun 23 '21 at 16:36
10

Here's an option using a JQuery extension I made based on the code by waspinator in the comments of the accepted answer:

jQuery.fn.extend({
    printElem: function() {
        var cloned = this.clone();
        var printSection = $('#printSection');
        if (printSection.length == 0) {
            printSection = $('<div id="printSection"></div>')
            $('body').append(printSection);
        }
        printSection.append(cloned);
        var toggleBody = $('body *:visible');
        toggleBody.hide();
        $('#printSection, #printSection *').show();
        window.print();
        printSection.remove();
        toggleBody.show();
    }
});

$(document).ready(function(){
    $(document).on('click', '#btnPrint', function(){
        $('.printMe').printElem();
    });
});

JSFiddle: http://jsfiddle.net/95ezN/1227/

This can be useful if you don't want to have this applied to every single print and just do it on your custom print button (which was my case).

bostero2
  • 375
  • 2
  • 8
9

With the currently accepted solution you cannot print the page which contains the dialog itself anymore. Here's a much more dynamic solution:

JavaScript:

$().ready(function () {
    $('.modal.printable').on('shown.bs.modal', function () {
        $('.modal-dialog', this).addClass('focused');
        $('body').addClass('modalprinter');

        if ($(this).hasClass('autoprint')) {
            window.print();
        }
    }).on('hidden.bs.modal', function () {
        $('.modal-dialog', this).removeClass('focused');
        $('body').removeClass('modalprinter');
    });
});

CSS:

@media print {
    body.modalprinter * {
        visibility: hidden;
    }

    body.modalprinter .modal-dialog.focused {
        position: absolute;
        padding: 0;
        margin: 0;
        left: 0;
        top: 0;
    }

    body.modalprinter .modal-dialog.focused .modal-content {
        border-width: 0;
    }

    body.modalprinter .modal-dialog.focused .modal-content .modal-header .modal-title,
    body.modalprinter .modal-dialog.focused .modal-content .modal-body,
    body.modalprinter .modal-dialog.focused .modal-content .modal-body * {
        visibility: visible;
    }

    body.modalprinter .modal-dialog.focused .modal-content .modal-header,
    body.modalprinter .modal-dialog.focused .modal-content .modal-body {
        padding: 0;
    }

    body.modalprinter .modal-dialog.focused .modal-content .modal-header .modal-title {
        margin-bottom: 20px;
    }
}

Example:

<div class="modal fade printable autoprint">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
        <h4 class="modal-title">Modal title</h4>
      </div>
      <div class="modal-body">
        <p>One fine body&hellip;</p>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
        <button type="button" class="btn btn-primary" onclick="window.print();">Print</button>
      </div>
    </div><!-- /.modal-content -->
  </div><!-- /.modal-dialog -->
</div><!-- /.modal -->
dtrunk
  • 4,685
  • 17
  • 65
  • 109
  • 1
    Hi, your solution is good, but in my case, my modal content is higher than the back page height, so, in the print view, the content from my modal is being cut, partial. Do you have any solution for this? Example: the print version needs 2 pages for all modal content, but its only considering one page (because of the height of the background "bodys" page). – Massa Feb 26 '18 at 14:43
  • 1
    @Massa, I was battling with this also. It seems that the problem is with the `position: fixed` of the modal. The solution I found is, when printing, to change the position ot the modal to `absolute` **and** set `bottom: auto`. – interDist Sep 18 '18 at 07:45
  • @Massa: Have you found a solution for printing modal content into multiple pages. – BVS Apr 15 '21 at 18:17
  • Also struggling the same was @Massa was... – Ashimema Jun 24 '22 at 13:12
8

I would suggest you try this jQuery plugin print element

It can let you just print the element you selected.

maxisam
  • 21,975
  • 9
  • 75
  • 84
  • The sample on the website does **not** work for me with IE 10 (Windows 8 RTM). – ComFreek Aug 29 '12 at 15:54
  • Really ? I haven't tried IE10 yet, but it sounds weird that it doesn't work with IE10. I used it on IE7-9 without problem. Do you try it on other browser to make sure you didn't miss something else ? – maxisam Aug 29 '12 at 15:58
  • Okay, it works fine now! That was because GitHub does not send correct MIME types and IE 10 then blocks the script. [Working jsFiddle here](http://jsfiddle.net/9wx9j/). – ComFreek Aug 29 '12 at 16:00
  • Glad you figure it out. BTW, I hate the fact that now I need to test 4 different IEs. – maxisam Aug 29 '12 at 16:02
  • Yeah, that's troublesome. But IE 10 has really better support for more HTML 5 features! – ComFreek Aug 29 '12 at 16:06
  • 1
    This is IMHO the best solution with the less hassle and code addition. Just incluse this file http://code.jquery.com/jquery-migrate-1.0.0.js after your javascript to avoid browser errors. – Mohamed Anis Dahmani Feb 10 '15 at 11:51
  • 1
    When using printElement, make sure to set the popup-setting so it wont use an iFrame, as it doesn't work correctly in IE from my experience. `$(elementToPrintId).printElement({printMode: 'popup'});` – cederlof Oct 19 '15 at 10:51
  • Bets solution for me. – Reddirt Dec 01 '15 at 16:09
7

This is a revised solution that will also work for modal windows rendered using a Grails template, where you can have the same modal template called multiple times (with different values) in the same body. This thread helped me immensely, so I thought I'd share it in case other Grails users found their way here.

For those who are curious, the accepted solution didn't work for me because I am rendering a table; each row has a button that opens a modal window with more details about the record. This led to multiple printSection divs being created and printed on top of each other. Therefore I had to revise the js to clean up the div after it was done printing.

CSS

I added this CSS directly to my modal gsp, but adding it to the parent has the same effect.

<style type="text/css">
   @media screen {
        #printSection {
           display: none;
        }
   }

   @media print {
        body > *:not(#printSection) {
           display: none;
        }
        #printSection, #printSection * {
            visibility: visible;
        }
        #printSection {
            position:absolute;
            left:0;
            top:0;
        }
   }
</style>

Adding it to the site-wide CSS killed the print functionality in other parts of the site. I got this from ComFreak's accepted answer (based on Bennett McElwee answer), but it is revised using ':not' functionality from fanfavorite's answer on Print <div id=printarea></div> only? . I opted for 'display' rather than 'visibility' because my invisible body content was creating extra blank pages, which was unacceptable to my users.

js

And this to my javascript, revised from ComFreak's accepted answer to this question.

function printDiv(div) {    
    // Create and insert new print section
    var elem = document.getElementById(div);
    var domClone = elem.cloneNode(true);
    var $printSection = document.createElement("div");
    $printSection.id = "printSection";
    $printSection.appendChild(domClone);
    document.body.insertBefore($printSection, document.body.firstChild);

    window.print(); 

    // Clean up print section for future use
    var oldElem = document.getElementById("printSection");
    if (oldElem != null) { oldElem.parentNode.removeChild(oldElem); } 
                          //oldElem.remove() not supported by IE

    return true;
}

I had no need for appending elements, so I removed that aspect and changed the function to specifically print a div.

HTML (gsp)

And the modal template. This prints the modal header & body and excludes the footer, where the buttons were located.

<div class="modal-content">
    <div id="print-me"> <!-- This is the div that is cloned and printed -->
        <div class="modal-header">
            <!-- HEADER CONTENT -->
        </div>
        <div class="modal-body">
             <!-- BODY CONTENT -->
        </div>
    </div>
    <div class="modal-footer">
                                 <!-- This is where I specify the div to print -->
        <button type="button" class="btn btn-default" onclick="printDiv('print-me')">Print</button>
        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
    </div>
</div>

I hope that helps someone!

Community
  • 1
  • 1
Kara Johnson
  • 79
  • 1
  • 6
  • Thank you Kara! This one solved a gnarly problem I was having. Also, if you cancel out of the print functionality, then try and print again, @comFreek's solution was causing errors for me (not his code, it's an Angular thing). – Hairgami_Master Sep 02 '15 at 14:22
  • @Hairgami_Master Glad to be of help! It took me a long time to get this working, so it felt wrong not to share. – Kara Johnson Sep 09 '15 at 15:22
4

I just use a bit of jQuery/javascript:

html:

<h1>Don't Print</h1>

<a data-target="#myModal" role="button" class="btn" data-toggle="modal">Launch modal</a>

<div class="modal fade hide" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"      aria-hidden="true">
  <div class="modal-header">
    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
     <h3 id="myModalLabel">Modal to print</h3>
  </div>
  <div class="modal-body">
    <p>Print Me</p>
  </div>
  <div class="modal-footer">
    <button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
    <button class="btn btn-primary" id="printButton">Print</button>
  </div>
</div>

js:

$('#printButton').on('click', function () {
    if ($('.modal').is(':visible')) {
        var modalId = $(event.target).closest('.modal').attr('id');
        $('body').css('visibility', 'hidden');
        $("#" + modalId).css('visibility', 'visible');
        $('#' + modalId).removeClass('modal');
        window.print();
        $('body').css('visibility', 'visible');
        $('#' + modalId).addClass('modal');
    } else {
        window.print();
    }
});

here is the fiddle

Zoe
  • 27,060
  • 21
  • 118
  • 148
alexoviedo999
  • 6,761
  • 1
  • 26
  • 17
  • 1
    The issue I noticed with this is the page contents (if long) will still be taking up print space. – Aibrean May 12 '15 at 13:38
1

Heres a solution with no Javascript or plugin - just some css and one extra class in the markup. This solutions uses the fact that BootStrap adds a class to the body when a dialog is open. We use this class to then hide the body, and print only the dialog.

To ensure we can determine the main body of the page we need to contain everything within the main page content in a div - I've used id="mainContent". Sample Page layout below - with a main page and two dialogs

<body>
 <div class="container body-content">

  <div id="mainContent">
       main page stuff     
  </div>
  <!-- Dialog One -->
  <div class="modal fade in">
   <div class="modal-dialog">
    <div class="modal-content">
          ...
    </div>
   </div>
  </div>

  <!-- Dialog Two -->
  <div class="modal fade in">
   <div class="modal-dialog">
    <div class="modal-content">
          ...
    </div>
   </div>
  </div>

 </div>
</body>

Then in our CSS print media queries, I use display: none to hide everything I don't want displayed - ie the mainContent when a dialog is open. I also use a specific class noPrint to be used on any parts of the page that should not be displayed - say action buttons. Here I am also hiding the headers and footers. You may need to tweak it to get exactly want you want.

@media print {
    header, .footer, footer {
        display: none;
    }

    /* hide main content when dialog open */
    body.modal-open div.container.body-content div#mainContent {
        display: none;
    }

    .noPrint {
        display: none;
    }
}
Peter Kerr
  • 1,649
  • 22
  • 33
0

I was facing two issues Issue 1: all fields were coming one after other and Issue 2 white space at the bottom of the page when used to print from popup.

I Resolved this by

making display none to all body * elements most of them go for visibility hidden which creates space so avoid visibility hidden

    @media print {
        body * {
           display:none;
        width:auto;
        height:auto;
        margin:0px;padding:0px; 
        }
        #printSection, #printSection * {
            display:inline-block!important;
        }
        #printSection {
            position:absolute;
            left:0;
            top:0;  
            margin:0px; 
            page-break-before: none;
            page-break-after: none;
            page-break-inside: avoid;      
        }
#printSection .form-group{

      width:100%!important;
      float:left!important;
      page-break-after: avoid;
    }
#printSection label{
        float:left!important;
        width:200px!important;
        display:inline-block!important;
      }

#printSection .form-control.search-input{
        float:left!important;
        width:200px!important;
        display: inline-block!important;
      }
}
Nadeemmnn Mohd
  • 713
  • 5
  • 14
0

@media print{
 body{
  visibility: hidden; /* no print*/
 }
 .print{
  
  visibility:visible; /*print*/
 }
}
<body>
  <div class="noprint"> <!---no print--->
  <div class="noprint"> <!---no print--->
  <div class="print">   <!---print--->
  <div class="print">   <!---print--->


</body>
Amin Emadi
  • 17
  • 3
0

you can download printThis lib from this source https://github.com/jasonday/printThis/blob/0a7f799693af8a8303bf0b8df0efc80c2694af81/printThis.js and include it into your html page

Call the following jquery to print all the content including the content that is not viewable. You may include your css files in an array if you have multiple css files.

$("#modalDiv").printThis({ 
    debug: false,              
    importCSS: true,             
    importStyle: true,         
    printContainer: true,       
    loadCSS: "../css/style.css", 
    pageTitle: "My Modal",             
    removeInline: false,        
    printDelay: 333,            
    header: null,             
    formValues: true          
}); 
Zoe
  • 27,060
  • 21
  • 118
  • 148
Fouad Mekkey
  • 138
  • 1
  • 7
0

So I have a react app that lives as a webcomponent on multiple legacy sites. None of these solutions were exactly what I was looking for. There are downsides to the accepted answer. Say we have this CSS:

@media print {
  body * {
    visibility:hidden;
  }
  #printSection, #printSection * {
    visibility:visible;
  }
  #printSection {
    position:absolute;
    left:0;
    top:0;
  }
}

body * {visibility : hidden} will prevents users from printing the page if they hit CTRL+P.

This also doesn't account for multiple print UX workflows as well, incase a user might need to print a page off a modal and a drawer that are on the same page. That's a rare case but again this isn't accounted for

The solution

The solution I chose instead is to apply a top level CSS class on the body. In my React Modal component, I use a useEffect call

import React, { useEffect } from 'react'

export const ModalPane = (props) => {
  useEffect(() => {
    const body = document.querySelector('body')
    if (body) {
      body.classList.add('hide-on-print')
    }
    return () => {
      if (body) {
        body.classList.remove('hide-on-print')
      }
    }
  }, [])

  const handlePrint = () => {
    // add any prework or analytics tracking here etc
    window.print()
  }

  return (
    <div className="print-me">
      <h1>Modal content stuff here</h1>
      <button type="button" onClick={handlePrint}>Print</button>
    </div>
  )
}

Once this modal is rendered (aka the user is looking at the intended modal), only the contents of the modal will be printed. It doesn't matter if they hit the print button or press CTRL+P

This applies a class called hide-on-print on the body element. This media style then get applied, which is scoped specifically for this print UX instance (you can add more classes to body for other specific print queries)

@media print {
  body.hide-for-print {
    visibility: hidden !important;
  }
  body.hide-for-print .print-me {
    visibility: visible;
  }
}

TLDR

use a react useEffect component that applies hide-for-print class only when its rendered. Wrap the @print media query behind this class. This handles all intended UX print use cases and can scale to any other specific print UX cases

Vincent Tang
  • 3,758
  • 6
  • 45
  • 63