Printing at Server running Web Applications

Unofficial Content

Server-side Printing for Web Applications



Introduction

When developing a Web application, which runs on a client-side browser, the printing problem is really a problem.

Context

You're not running an application on your PC, and are actually running a browser. In addition, this browser has HTML code that was generated by an application on the server.

This is so by design, and for security and privacy reasons, browsers don't have full access to all your local PC resources.

Therefore, the printing solution is not as easy as "print using a VB application" because it's not running your application, it's running a browser.

There's a long list of possible solutions:

  • A small Java/Net application running on the PC that is called from the browser. Actually, the Java/Net application is your application running on the client PC. This way, your application has full access to the PC's resources and can manage the printer. This document explains how to do that Please note that momentarily, this link points to the Spanish version.
  • A JavaScript that uses the Browser Print functionality. Forget about a customized print layout because the page breaks, headers, etc. will be defined for the browser.
  • 3rd-party solutions. For example, generating a PDF file that users can print later.

Scenarios

The solution to be used depends on many contextual reasons. Are the application's users "occasional" (typically, when developing Internet applications), or are they frequent users working in an intranet application? Are esthetics, including format and layout, important or is it enough just to have the information printed?

All these solutions have something in common because they assume that the printer is connected to the client PC.
This article, on the other hand, focuses on another scenario: the client needs to print on the server-side (the printer is connected to the server).
If the application is being run on an intranet, the printer connected to the server could be available from the client-side. However, the application could be running through the Internet or, for any reason, the printers may not be available from the client-side.
The first case, when the application is running on an intranet with access to server-connected printers, is equivalent to that of the printer connected directly to the client. Therefore, the solution could be one of those mentioned above.

The second case, in which the application is running through the Internet or doesn't have access to server-connected printers, is the scenario studied in this article.

The "qprint" Solution

General Scheme

This solution is fairly simple. Consider that when running a Web application, the client is actually running an application on the server-side and a browser on the client-side.
As shown in the figure below, the idea is that the application (a web panel) calls a report. This report, which runs on the server-side, generates a PDF file. Then, a daemon running on the server-side sends this PDF to the printer.

img/wiki_up//solution schema.jpg


Sample Source

A sample using the .NET Generator (Yi Version) has been published at www.gxopen.com.

The following are the most important pieces of code included in the published sample.

Web panel event which calls the report
For each report executed, a record is inserted into a table.

Before calling the report, a program (PQInsert) is called to insert a record into a table (QueueToPrint). This program returns the filename and fileid inserted.
This filename is then passed to the report, which generates the PDF file using this filename.
After the report is finished, a program changes the status from (QueueToPrint) to "Ready to be printed."

Event 'Customers RPT'
    PQInsert.Call("RCustomers", &PrintFileID, &PrintFileName )
    RCustomers.Call(&PrintFileName)
    PQChangeStatus.Call(&PrintFileID, "T") 
    commit
EndEvent  // 'Customers RPT'


Sumarizing, to call a report, follow these steps:
1. Before calling the report, call the PQinsert program that passes the report name (or whatever you want to use as filename).
2. After calling the report, call the PQChangeStatus program.

Note: none of the programs should include COMMIT (Commit On Exit=No) because the LUW is managed by the caller program.

Report that generates the PDF file
Every report should:
  • Receive, at least, the filename - Parm(&PrintFileName);
  • Assign this filename to the output - Output_file(&PrintFileName?? ,"pdf");
  • Send the output directly to a file: "Report Output=Only to file" property.

Daemon that prints the PDF Files
This program (QPrintDaemon) has an endless loop checking whether there are files to be printed (record in the QueueToPrint?? table).

do while 1>0 // endless loop
   &loops= &loops 1
    msg(concat("printing loop #", str(&loops), " "), status)
    for each
        where PrintFilePrintedSuccessfully='T'      // files to be printed
            msg(concat("printing ", PrintFileName," "),status)
            &Printed= PrintDocument(PrintFileName)  // printing the file
            If &Printed=0               
                PrintFilePrintedSuccessfully="P"    // if it was successfully printed
                PrintFileDatePrinted=now()          // updating the printed date and the status
                msg("successfully printed", status) 
            else
                msg("print failed",status)
            endif
        commit
    endfor
    &seconds=sleep(5)   // waiting 5 seconds to search again (files to be printed)
enddo


NOTES:
  • The filename returned by the QInsert program doesn't include the complete path. Remember this because the daemon should run in the same directory where the report generates the files; otherwise, files will not be found. To solve this, change the QInsert program to generate the complete path and avoid this problem.
  • The Web application may be a Java or .NET application; however, the daemon's language must support the "printdocument" function. For example, . NET. More info.
  • Commit commands of the 'Customers RPT' event at the Web panel calling the report and at QPrintDaemon were included in a For each group. This may not work with DBMS other than MS SQL, such as Oracle, because the commit command causes cursors to close. It's recommended that you have the commit command outside the For each group.

Possible Improvements for the Solution

Improvements to the Daemon

Somewhere I saw (perhaps at gxopen.com) a sample on how to include a GX.NET as a Windows Services. This could be a great idea to improve the solution by running the daemon as a service, not as an endless loop.
Using GX.Java you can use Jsrvany

What's New in Yi Version

Many related features have been included in the Yi version, such as file and directory management, upload control. Maybe in future betas, these features will allow us to include the PDF directly into the QueueToPrint?? table, instead of having a PDF file in a folder. This change should avoid the "where is the file?" problem. Any program accessing the database can print out the PDF because it's included in the table.

Where to Print the PDF

I haven't found a solution to configure the printer to be used for each user or report. I thought about changing the default printer before printing the PDF, but didn't try because it didn't sound good enough to me.
Perhaps somebody smarter than me can find the way to do that.

Removing Files

After printing a PDF, the file can be removed. I used the "DeleteFile" function but Acrobat Reader gave me an awful, unknown error (something like a GPF). Then I decided to leave the files there until removed at night or every time the daemon is started. Disk space is cheap enough to enjoy this luxury wink