Thursday, August 12, 2010

Printing HTML Documents Using Customised CSS and JavaScript

Recently, I was working on some Ruby on Rails project where users wanted to be able to print nice reports. I should admit that the gem solutions I found that time disappointed me because:
  • Most required a lot of time to understand than I had to make them work.
  • Some solutions required extra gems (and plugins) that I could not get because of usual gem error: ERROR: could not find gem XXX locally or in a repository!
One interesting fact was that the users had a defined report format which they wanted both screen layouts and printouts to be modelled from! An additional challenge was that the application was developed for use on a mouseless touchscreen computer that always open the browser in fullscreen mode. So, it would be difficult to manually select the Print command on the File menu of the browser. So after making a some Internet research, I got several solutions that I integrated to come up with mine that addresses the users' need. I present the solution here in a tutorial format so that it is easy to follow.
  1. Document Layout
Let us create a simple-and-easy-to-follow report layout using HTML tags as follows:

<html>
    <head>
    <title>The Application Name</title>
    <script src="/javascripts/report_printer.js" type="text/javascript"> </script>
    <link href="/stylesheets/report.css" media="screen" rel="stylesheet" type="text/css" />
    </head>
    <body>
    <div id="report">
        <div id="metaData" onclick="javascript:printContent('document');" style="float: right;">
            <a> Print Report</a>
        </div>
        <div id="document">
            <div id="reportHeader"> Organization Name </div>
            <div id="reportSubHeader"> Report Name and/or Description </div>
            <div id="dataTable">
                <!-- the data table is placed here -->
            </div>
        </div>
    </div>
</body>
</html>
Linked to this file is a standalone CSS file, report.css, that contains CSS definitions for each of the div ids and classes. In this example, the CSS file is placed in stylesheets sub-directory. The metaData div contains a hyper-linked button for printing the report. The metaData div customizes the hyper-link to a button. This div is significant to the whole printing process as we will see in a moment.
  1. Adding Printing Functionality using JavaScript
With the CSS, everything looks nice on the screen. However, there are two problems:
  1. The Print command on the File menu produces clumsy output.
  2. It would be difficult to manually select the Print command on the File menu of the browser since the application is developed for use on a mouseless touchscreen computer that always open the browser in fullscreen mode.
In order to print, we create a transitional pop-up window that opens upon clicking print button and closes after the printing action completes, whether successfully or not. All this pop-up window does is initiate an onload action via print_win() function. This is a simple Javascript function that uses the default DOM print()and close() functions to print and close respectively. Here is the print_win() function:
   function print_win(){
      window.print();
      window.close();
   }
Now we let Javascript create and destroy the pop-up window. This Javascript is invoked upon clicking the print button defined in metaData div. The pop-up window will get its data from the innerHTML of the element whose id is passed as argument to printContent()function. This implies we have to make sure that all data that we would like to be printed is placed in the right div, otherwise any data outside it will not be printed. In our example, the data is placed in the document div.
We should also remember that since print_win() function belongs to the pop-up window, it will be embedded within the outer Javascript that creates the pop-up window. We place our code in report_printer.js in the javascripts sub-directory. Here is the code that does it all:
     function printContent(id){
     var data = document.getElementById(id).innerHTML;
     var popupWindow = window.open('','printwin',
          'left=100,top=100,width=400,height=400');
     popupWindow.document.write('<HTML>\n<HEAD>\n');
     popupWindow.document.write('<TITLE></TITLE>\n');
     popupWindow.document.write('<URL></URL>\n');
     popupWindow.document.write('<script>\n');
     popupWindow.document.write('function print_win(){\n');
     popupWindow.document.write('\nwindow.print();\n');
     popupWindow.document.write('\nwindow.close();\n');
     popupWindow.document.write('}\n');
     popupWindow.document.write('<\/script>\n');
     popupWindow.document.write('</HEAD>\n');
     popupWindow.document.write('<BODY onload="print_win()">\n');
     popupWindow.document.write(data);
     popupWindow.document.write('</BODY>\n');
     popupWindow.document.write('</HTML>\n');
     popupWindow.document.close();
  }
Our application is ready for printing. Try printing your document using the print button.
  1. Improving the Output
Trying to print the document shows that the data is rightly captured but not well structured as required. Let us add report.css to the pop-up window in print mode using some Javascript code. We modify and add a simple statement in our Javascript code: popupWindow.document.write("<link href='/stylesheets/report.css' media='print' rel='stylesheet' type='text/css' />\n"); We also add a dummy line that formats the screen layout of document on the pop-up window so that it looks better. Notice that the line is similar to the previous one except that we specify the media as screen. Our function now looks like this:

     function printContent(id){
     var data = document.getElementById(id).innerHTML;
     var popupWindow = window.open('','printwin',
          'left=100,top=100,width=400,height=400');
     popupWindow.document.write('<HTML>\n<HEAD>\n');
     popupWindow.document.write('<TITLE></TITLE>\n');
     popupWindow.document.write('<URL></URL>\n');
     popupWindow.document.write("<link href='/stylesheets/report.css' media='print' rel='stylesheet' type='text/css' />\n");
    popupWindow.document.write("<link href='/stylesheets/report.css' media='screen' rel='stylesheet' type='text/css' />\n");
     popupWindow.document.write('<script>\n');
     popupWindow.document.write('function print_win(){\n');
     popupWindow.document.write('\nwindow.print();\n');
     popupWindow.document.write('\nwindow.close();\n');
     popupWindow.document.write('}\n');
     popupWindow.document.write('<\/script>\n');
     popupWindow.document.write('</HEAD>\n');
     popupWindow.document.write('<BODY onload="print_win()">\n');
     popupWindow.document.write(data);
     popupWindow.document.write('</BODY>\n');
     popupWindow.document.write('</HTML>\n');
     popupWindow.document.close();
  }

We have finished successfully. Your application should be able to produce documents that are as nice as your original screen versions. You can print directly or to files, whether PDF, PostScript, etc.

Happy coding!!