Twitter Bootstrap: Print content of modal window

JavascriptPrintingTwitter BootstrapModal Dialog

Javascript Problem Overview


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.

Javascript Solutions


Solution 1 - Javascript

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: https://stackoverflow.com/questions/468881/print-div-id-printarea-div-only

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

Solution 2 - Javascript

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).

Solution 3 - Javascript

I would suggest you try this jQuery plugin print element

It can let you just print the element you selected.

Solution 4 - Javascript

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 -->

Solution 5 - Javascript

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 https://stackoverflow.com/questions/468881/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!

Solution 6 - Javascript

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

Solution 7 - Javascript

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;
    }
}

Solution 8 - Javascript

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;
      }
}

Solution 9 - Javascript

@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>

Solution 10 - Javascript

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          
}); 

Solution 11 - Javascript

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMattSullView Question on Stackoverflow
Solution 1 - JavascriptComFreekView Answer on Stackoverflow
Solution 2 - Javascriptbostero2View Answer on Stackoverflow
Solution 3 - JavascriptmaxisamView Answer on Stackoverflow
Solution 4 - JavascriptdtrunkView Answer on Stackoverflow
Solution 5 - JavascriptKara JohnsonView Answer on Stackoverflow
Solution 6 - Javascriptalexoviedo999View Answer on Stackoverflow
Solution 7 - JavascriptPeter KerrView Answer on Stackoverflow
Solution 8 - JavascriptNadeemmnn MohdView Answer on Stackoverflow
Solution 9 - JavascriptAmin EmadiView Answer on Stackoverflow
Solution 10 - JavascriptFouad MekkeyView Answer on Stackoverflow
Solution 11 - JavascriptVincent TangView Answer on Stackoverflow