Tips for printing from web applications


Web developers usually have to format user content for printing; for example, accountants might want physical copies of online ledgers while teachers might need lecture note printouts.

The challenge lies in getting consistent print output across a range of browsers and their never-ending stream of subtle nuances.

This post approaches printing from three viewpoints: tooling, JavaScript and CSS. It also describes the pitfalls and quirks to watch out for when printing across the big 5 browser platforms (i.e. Chrome, Edge, Firefox, Internet Explorer and Safari).

1. Tooling

Quick question – what does this page look like when printed?

Did you do a CTRL + P to figure that out? Print previews work excellently if you want to ‘see’ what a page looks like when printed. But what if you wanted to interact with the page and change it? Say you wanted to toy with the CSS or even debug JavaScript. Clearly, the preview option comes short; even if it doesn’t, going to preview mode every time you make changes is not fun.

Good news, Chrome provides print emulation tooling which allows you to style a page for printing. To activate the print emulation mode, do the following:

  • Press F12 to bring up the Chrome dev tools
  • Press Esc to bring up the extra tabs
  • Select the Emulation tab
  • Select the media option
  • Check the CSS media check box
  • Select print (or any other media target you desire)

chrome tools

The Chrome emulation mode should reveal the major print issues however, you should sanity check other browsers’ in print preview mode. This should catch the few remaining quirks, things like forgetting –webkit prefixes for Safari.

JavaScript

Media Listeners

The big 5 browsers all have media listener support, this allows detecting media properties such as orientation, resolution and viewport dimensions. For example, the snippet below will be triggered when the browser width falls below 960px.

var widthHandler = function(mql) {
    if(mql.matches) {
        console.log('Viewport width is <= 960px');
    } else {
        console.log('Viewport width is > 960px');
    }
}

var mql = window.matchMedia('(width: 960px)');
mql.addListener(widthHandler);

Chrome and Safari

The interesting aspect is the print media query option; Chrome and Safari can detect print events via these listeners. Unfortunately, IE, FF and Edge, even though they support other media queries, do not offer print media support.

Safari tends to fire the media listener event ‘after’ the print dialog appears. This effectively renders any desired print pre-processing useless – what’s the use if the print dialog is already visible?

Chrome offers the best support, modification of print options (e.g. paper type or layout) will still trigger appropriate print events; this is very useful if you want to restyle your page dynamically based on print options. Alas, only Chrome gives this option.

Here is how you attach handlers for the Chrome/Safari scenario.

var printHandler = function(mql) {
    if(mql.matches) {
        console.log('Print');
    } else {
        console.log('Not print');
    }
};

var mql = window.matchMedia('print');
mql.addListener(printHandler);

When you are all done, remember to clean things up by calling mql.removeListener(printHandler).

IE, FF and Edge

These 3 browsers expose the onbeforeprint and onafterprint events.

window.onbeforeprint = function () {
    console.log('Print started');
};

window.onafterprint = function () {
    console.log('PRINT DONE');
};

You would expect the onafterprint handler to be called immediately after the print dialog is disposed right? Nope, it is nearly always invoked immediately after onbeforeprint. Invocation order: onbeforeprint -> onafterprint -> print dialog opens.

Print events have a mind of their own…

Cross-browser printing

Merging both approaches gives a combination that should work well in most scenarios. See below:

function beforePrint () {
    console.log('before print');
}
function afterPrint() {
    console.log('after print');
}
window.onbeforeprint = beforePrint;
window.onafterprint = afterPrint;

var printHandler = function(mql) {
    if(mql.matches) {
        beforePrint();
    } else {
       afterPrint();
    }
};

var mql = window.matchMedia('print');
mql.addListener(printHandler);

CSS media

CSS media queries provide very powerful print styling capabilities. You should handle most of the styling issues with CSS and wrap up the thornier edge cases with JavaScript.

The introduction of the print media block allows you to control and override existing DOM styles. This makes it possible to hide certain elements on print, elements to new positions or even change the entire page’s layout.

@media print {
    body {
        width: 960px !important;
    }
}

Now in print mode, your page will have a width of 960px. Go ahead, go toy and play with this.

Note that you might need to add !important to make sure print styling overrides existing styling. For inline styles, !important might not work however increasing the specificity of the CSS selectors would eventually work.

Happy printing!

Related

Detecting Print Requests with JavaScript

MDN documentation on CSS Media

How to set up a print style sheet

Automate Builds using GruntJS


I already wrote about the awesomeness of GruntJS and here is how to set up your own Grunt system.

1. Installation

GruntJS runs on nodejs so you need that installed first. Once nodejs and npm are installed, use the following command to install Grunt (the -g flag installs it globally).

npm install -g grunt-cli

This installs the grunt cli tool, next you need to create your project deployment. Navigate to your target project directory and type the following commands:

npm init

npm install grunt

You can safely use the defaults suggested during the npm init process; this process creates a package.json file which contains information about the licence, repository, author and blah blah blah. You can also use pre-created package.json files to tell the node package manager what dependencies to install.

Make sure you keep the package.json file safe – it can be used to recreate your project or share project details with other developers. If you get weird errors, try prefixing the commands with sudo.

Type grunt now and if you this error: ‘A valid Gruntfile could not be found…‘; then give yourself a pat on the back! 🙂

2. Using Grunt

Creating a Gruntfile is easy; it’s mostly ‘JSON-y’ syntax; however you’ll need to install dependencies and extra features you need. The location of the Gruntfile does not matter: when you run grunt, it will walk up the directory tree until it finds a Gruntfile. So just drop the Gruntfile in the root of your project and you can grunt happily from any sub-directory.

When installing new plugins, remember to add the –save-dev switch so that your package.json file is updated. For example, to add the requirejs plugin, you’d type something like this:

npm install grunt-contrib-requirejs –save-dev

Once you’ve installed the plugins, you need to instruct Grunt to load these plugins. Adding grunt.loadNpmTasks(‘grunt-contrib-requirejs’) to your Gruntfile will fix this.

Next, you need to specify the tasks in the Gruntfile; you set up the plugin’s config options – details such as location of input files, the name and location of its output and some other options depending on the plugin. After this, you register the task with the grunt.registerTask() hook. See the sample config file for a demo.

3. Sample Grunt config

The Gist below shows a sample commented Gruntfile. The default task is run whenever you type grunt at the CLI; however to run other tasks you need to specify the task name; in the sample file below, typing grunt test will run the jshint:js and qunit tasks.

4. What do these tasks mean?

  • JsHint: This will run a static analysis check of your code to see if it complies with standards; it helps to spot hidden errors and will save you some pain. It is possible to specify different targets specified which allows you to run different tasks as you wish e.g. against some subset of your development framework.
  • Cssmin: This minifies CSS files; the options show how much the size has changed.
  • QUnit: Runs qunit tests on the specified directories against the phantomjs headless browser
  • compass: Compiles SASS files to CSS; allows you to pick up config options from a .rb config file although you can overwrite them too.

And that’s it! 🙂

There is a lot more than mentioned, so feel free to do some exploration and hey! Remember to tell me about it too!!

To whet your appetite, here is one: it is possible to read values from the package.json file.

How it all started


My very first language was FORTRAN; I took a programming course in it in 2006 and was instantly hooked. I didn’t have a computer however I worked in a computer centre so I could program whenever I was free.

After getting my laptop, I delved into C; however I  was ‘advised’ to drop C as it wasn’t so popular anymore. So I switched to C++ and it was fun till I got into the murky waters. I remember being puzzled when I started OOP in C++ (similar thing happened when I started learning functional programming); coming from a procedural background, I just couldn’t seem to understand the concept of an object; was it a new data type? Was it something else? Why could I call methods on data? Objects or not, I continued with C++.

A year later, I was learning Java even though I was not quite finished with C++, why? I was taking a course which was being taught in Java. I switched again without having mastered C or C++ and resolved never to leave Java until I mastered it, no matter what anybody said, did or suggested. I stuck with Java; learning stuff, writing programs and just trying out ideas.  I remember trying to implement a Sudoku game in Java – some intensive brainstorming huh? :D.

I discovered PHP/MySQL during my internship; it was the language of choice at my firm and I had to learn it. My boss never took no for an answer and I had to provide solutions whether it seemed possible or not. Initially I thought his demands were harsh however I realised that my attitude was the cause – I gave up too easily; Alhamdulilah, with this realisation, I became a better person. I learnt PHP, wrote some cool algorithms and built a couple of software systems there. I also stumbled upon some neat PHP tricks, JavaScript and  got a lot of hours under my programming belt. After my internship I took up a project in Drupal and started another from the scratch: Gbanjo. Well, Gbanjo is a story for another day.

In my last year at school; I wanted to learn Python and actually started but I decided to focus more on my studies and since I was holding a number of administrative positions; I didn’t want my grades to fall.

Now; I’m outta school presently and I’m programming. This year I have learnt more about PHP, Java, JavaScript and Android. The road has been long but I thank God for helping me and want to learn more. I still have to learn C/C++/C#/python and maybe Ruby, Scala or Scheme. For the present; I’m learning JavaScript and I have to finish this before starting with another language. One of my past mistakes was trying to learn too many things at the same time and not just focusing on one thing and learning it well.

Here are a few facts I’ve learnt from my journey; I’m still learning anyway 🙂

  • Do what you are passionate about
  • Give it all your best; your very best, don’t ever give up!
  • Don’t listen to what people say; God determines success not people
  • Learn to focus; Rome wasn’t built in a day; you can’t learn everything at once
  • Work hard – of course, there’s no gain without pain.
  • Pray harder – I believe in God and He answers prayers

What’s your own story?