Last month my Print Stylesheets Tips & Tricks article on Smashing Magazine received an interesting question: what if a user wants to print individually separate sections of a web page?
The questioner gave a good example: a page that presents multiple recipes (those used in the example above supplied by my student Jenelle Bucholz, from her recent 1st year site project). The HTML for the recipes would look something like this:
<article id="article1">
<h1>Light New York Cheesecake</h1>
<h2>Ingredients</h2>
<h3>Crust</h3>
<ul>
<li>⅔ cup all-purpose flour
…
</ul>
</article>
<article id=article2>
<h1>Zucchini Brownies</h1>
<h2>Ingredients</h2>
<ul>
<li>2 eggs
<li>1 tbsp. vanilla
…
</ul>
</article>
And the default CSS like this:
body {
display: flex;
font-family: Edel Sans, Arial, sans-serif;
}
article {
margin: 0;
position: relative;
padding-bottom: 2rem;
}
article img {
width: 100%; height: auto;
}
article h1 {
line-height: 1.4;
text-align: center;
}
article h2 {
font-size: 1.4rem;
border-bottom: 2px solid rgb(133,183,174);
margin: 1rem auto;
padding: .3rem;
width: 8rem;
text-align: center;
font-weight: 200;
}
article ul, article ol {
font-size: 1.2rem;
line-height: 1.6;
}
article ul, article ol {
padding-left: 0;
}
article ol {
padding-left: 1rem;
}
article ul {
list-style-type: none;
}
article button {
position: absolute;
bottom: 0; right: 1rem;
padding: .5rem;
}
(I’m using flexbox for convenience, but there are plenty of alternative ways to place the recipes side-by-side).
Creating Print Buttons
The buttons are inserted at the bottom of each article:
<button>Print recipe</button>
It’s possible to make the each button a simple print action at this stage:
<button onclick="window.print()">Print recipe</button>
Written that way, the buttons will print out the entire page, but our goal is more ambitious that this. Because I want to keep the code as clean as possible, I’ll do all of the work in JavaScript at the end of the document, rather than inline for each element.
Allowing For Printing Choices
A visitor will probably want to print out a single recipe for the sake of convenience. In this case, I assume that the site owner wants page components such as <header>
elements and comments to print out when the user clicks Print, but no other articles aside from the one associated with the button. As a start, I’ll add the following to the end of the document:
<script>
function printSection() {
var parentArticle = this.parentNode,
articles = document.getElementsByTagName("article");
for (var i = 0; i < articles.length; i++) {
if (articles[i] !== parentArticle) {
articles[i].style.display = "none";
}
window.print();
}
var printRequestors = document.querySelectorAll("article button:last-of-type");
for (var i = 0; i < printRequestors.length; i++) {
printRequestors[i].addEventListener("click", printSection, false);
}
</script>
Very simply, the script attaches a click event to the last button in each article (we’re assuming that’s the Print button, although we could also identify the buttons with a class
to be on the safe side). When a button is clicked, it calls the printSection function, which:
- Determines the parent article of that particular button.
- Turns off the display of all other articles.
- Prints the resulting page.
This works, but it leaves the browser window looking like the page that has just printed, i.e. with the other recipes removed. In reality, we want the display: none
rule to affect articles only during print, not on the screen. Also, we don’t want the Print button itself to be visible on the printed page.
The last part is easy to do, via adding a media query to our main stylesheet:
@media print {
article button:last-of-type {
display: none;
}
}
The other goal is a little trickier: unfortunately, we can’t just command JavaScript with parentArticle.print();
. But we can create a new style sheet at the end of our document that contains added rules for print. The JavaScript changes to:
function removePrintStyles() {
previousExclusions = document.getElementById("printexclusions");
if (previousExclusions) {
previousExclusions.parentNode.removeChild(previousExclusions);
}
function printSection() {
var css = document.createElement("style"),
parentArticle = this.parentNode,
articles = document.getElementsByTagName("article");
css.id = "printexclusions";
css.innerHTML += "@media print {\n"
for (var i = 0; i < articles.length; i++) {
if (articles[i] !== parentArticle) {
css.innerHTML += "#"+articles[i].id + "{ display: none; }\n"; }
}
css.innerHTML += "}\n"
removePrintStyles();
document.body.appendChild(css);
window.print();
removePrintStyles();
}
var printRequestors = document.querySelectorAll("article button:last-of-type");
for (var i = 0; i < printRequestors.length; i++) {
printRequestors[i].addEventListener("click", printSection, false);
}
In short, the rewritten script creates a new print stylesheet at the bottom of the document, eliminating the presentation of any other recipes when the Print
button is clicked; thereafter, if the button for another recipe is clicked, the previous stylesheet is removed and rewritten. The stylesheet is removed after any individual print action, as otherwise it will influence the printing of the entire page, if the user decides to take that route.
Improvements
There are several assumptions in the script: that every <article>
element on the page has a unique id
; that visitors are using a modern browser (due to the use of document.querySelectorAll
and addEventListener
), and that JavaScript itself is working. These may not be reasonable assumptions in all cases, but each could be addressed if they were felt to be an issue.
Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/DKdkx